Introduction Into IFrame API

Customer's Canvas offers two APIs: the old API, which is based on query string, and the new JavaScript one - IFrame API. All Customer's Canvas 3.x releases will fully support both of the APIs. In the next major release, we will stop adding new features to the query-string API and eventually make it discontinued.

In the query-string API products and the editor are configured through the query-string arguments. This API is easy to use and does not require JavaScript coding skills, but it has two major shortcomings. It does not allow for changing product configuration at runtime without reloading the designer. And it restricts product configuration due to limited query string length in browsers. The query-string API is described in the Old Query-string API topic.

The new IFrame API is a JavaScript script named IframeApi.js. You can find it in the Customer's Canvas folder by the following path: ~\Resources\SPEditor\Scripts\IFrame\. The main advantages of the IFrame API are:

  • The IFrame API includes all the functionality of the query-string API.
  • It is much more suitable for complex configurations, for example, for multipage products.
  • The IFrame API offers much more flexibility when configuring the designer and print products.
  • It allows changing product configuration at runtime, namely, add/remove surfaces, change mockups, etc.

The IFrame API is written in TypeScript and compiled into JavaScript. So, if you use TypeScript to develop your application and integrate Customer's Canvas into it, you may find the IframeApi.d.ts definition file useful.

Quick Start

The key function of the IFrame API is loadEditor. It displays the web-to-print editor in the given Iframe element, loads the given product to the editor, and configures the editor. The two mandatory parameters here are the Iframe element of the HTMLIFrameElement type and a product to load into the editor. The product should be defined via IProductDefinition. Let us see how it works.

The easiest Customer's Canvas use case is to display the editor and load a product into it. Using the IFrame API it requires only two steps:

  1. Defining a product through IProductDefinition.
  2. Loading the editor with the product using loadEditor.

The following snippet loads the double-sided business card into the editor:

JavaScript
<!DOCTYPE html>

<html lang="en">
<head>
    <meta charset="utf-8" />
    <!-- The IFrame API script. IMPORTANT! Do not remove or change the id. -->
    <script id="CcIframeApiScript" type="text/javascript" src="http://example.com/Resources/SPEditor/Scripts/IFrame/IframeApi.js">
    </script>
</head>
<body>
    <!-- The iframe to display the editor into. -->
    <iframe id="editorFrame" width="100%" height="800px"></iframe>
</body>

<script>
//Defining product.
productDefinition = {
    //This safety line is applied to all surfaces of the product.
    defaultSafetyLines: [{
        margin: 8.5,
        color: 'rgba(0,255,0,255)',
        altColor: 'rgba(255,255,255,0)',
        stepPx: 5,
        widthPx: 1
    }],
    surfaces: [
        //The first surface - the front side of the business card.
        {
            printAreas: [{ designFile: "BusinessCard2_side1" }]
        },
        //The second surface - the back side of the business card.
        {
            printAreas: [{ designFile: "BusinessCard2_side2" }]
        }]
};
//Getting the iframe element to display the editor into.
var iframe = document.getElementById("editorFrame");
//Loading the editor.
var editor = null;
CustomersCanvas.IframeApi.loadEditor(iframe, productDefinition).then(function(e) {editor = e});
</script>
</html>

Note, it is not enough to link the IframeApi.js URL to get it working. You should also specify the URL to your Customer's Canvas instance so that it could make the IFrame API calls properly. The simplest way to do it is to add id="CcIframeApiScript" to the script tag which links the iframe.js to your page, like in the code snippet above. This way Customer's Canvas can detect it automatically. If for any reason you do not want to add the id to the script tag, you can configure the URL to the Customer's Canvas instance as follows:

JavaScript
<script> 
    CustomersCanvas = {
        IframeApi: {
            editorUrl: "http://example.com/" 
        }
    }; 
</script> 
<script type="text/javascript" src="http://example.com/Resources/SPEditor/Scripts/IFrame/IframeApi.js">
</script>
<script>
    //....
    var editor = null;
    CustomersCanvas.IframeApi.loadEditor(iframe, productDefinition).then(function(e) {editor = e});
</script>

Changing Product Configuration at Runtime

One of the major advantages of the IFrame API is the ability to manage products at runtime. It allows:

  • adding and deleting surfaces (pages) at runtime
  • switching between product pages
  • replacing mockups
  • replacing products in the editor without reloading the entire page

Here we discuss adding a surface to a product. The sample use case is offering a user to add back side to an ordered postcard. How a user sees it:

  1. A user sees a single-sided postcard loaded into the editor.
  2. The user customizes the postcard.
  3. The user can add back side to the postcard or just get the single-sided postcard.
  4. To add a back side the user should make some action; in our sample - to click the Add Back Side button.
  5. If the user adds the back side to the postcard, then both sides are loaded into the editor and can be customized.
  6. The user approves the product and orders it.

What is happening inside:

  1. A postcard is loaded into the editor. Use IProductDefinition to define the postcard and loadEditor to load it and display the editor.
  2. Define the back side of the postcard using ISurfaceTemplate. The back side is a part of the postcard, so it should be represented by a surface and since it has a template file, we choose ISurfaceTemplate.
  3. In the function that handles clicking the Add Back Side button use Product.getProduct to get the product loaded in the editor and Product.addSurface to add a surface (back side) to the product. When the surface is added successfully, replace the old product (single-sided postcard) with the new one (double-sided postcard).
  4. When the user approves the product, the proof images are displayed. The Editor.getProofImages method returns links to proof images.
  5. When the user orders the product, the product is saved and links to hi-res output are stored. The Editor.finishProductDesign method returns links to hi-res output.

The following sample implements the functionality discussed above. To make the sample work do the following:

  1. Copy and paste the code to the index.html file.
  2. Replace example.com with the name of the site where your Customer's Canvas instance is hosted and save the file.
  3. Open index.html in a browser.
JavaScript
<!DOCTYPE html>

<html lang="en">
<head>
    <meta charset="utf-8" />
    <script type="text/javascript" src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
    <script type="text/javascript" src="http://example.com/Resources/Libs/jquery/loadmask/jquery.loadmask.min.js"></script>
    <link href="http://example.com/Resources/Libs/jquery/loadmask/jquery.loadmask.css" rel="stylesheet" type="text/css" />
    <link href="index.css" rel="stylesheet" /> 
    <!-- The IFrame API script. IMPORTANT! Do not remove or change the id. -->
    <script id="CcIframeApiScript" type="text/javascript" src="http://example.com/Resources/SPEditor/Scripts/IFrame/IframeApi.js">
    </script>
</head>

<script>
$(function () {
    //Defining the postcard
    productDefinition = { surfaces: [
            { printAreas: [{ designFile: "postcard" }] }
        ]};
    
    //Defining the back side of the postcard, which is the optional and can be added to the order by a user.
    backSide = { 
        //Defining template file.
        printAreas: [{ designFile: "postcard_side2"}]
    };
    //Defining the editor configuration.
    configuration = { widgets: {FinishButton: {mode: "Disabled"}}, canvas: {color: "white"}};
    
    var editorFrame = $("#editorFrame");
    
    var editor = null;
    
    var frameParent = editorFrame.parent();
    
    //Loading product into the editor.
    function loadProduct(product) {
        frameParent.mask("Loading...");
        //Loading the editor.
        CustomersCanvas.IframeApi.loadEditor(editorFrame[0], product, configuration, 
                                            function () { frameParent.mask("The product is being configured for the first launch. It may take a while.") })
            //If the editor is loaded.
            .then(function (e) {
            editor = e;
            frameParent.unmask();
        })
            //If there was an error thrown when loading the editor.
            .catch(function (error) {
            frameParent.mask("An error occured.");
            console.error("Load failed with exception: ", error);
        });
    }
    
    //Loading the postcard.
    loadProduct(productDefinition);
    
    var currentPage = "design";

    //JSON Object returned from getProofImages callback.
    var approveData = null;
    
    //JSON Object returned from finishProductDesign callback.
    var renderData = null;

    //JSON Object returned from saveProduct callback.
    var saveData = null;

    //Adding the evelope.
    $("#editorPage #addBackSide").click(function () {
        editor.getProduct()
            //When we get the product.
            .then(function(product) {
                //If postcard has no back side.
                if (product.surfaces.length < 2) {
                    //Adding the back to the loaded product.
                    return product.addSurface(backSide)
                        //If the back side is added to the postcard.
                        .then(function(newProduct) {
                            //Replace the loaded product with the new one.
                            product = newProduct;
                        });
                }
            })
            //If there was an error while getting product or adding the back side to the postcard.
            .catch(function (error) {
                frameParent.mask("An error occured.");
                console.error("Adding surface failed with exception: ", error);
            });
    });
    
    //Saving product and getting links to proof images.
    $("#editorPage #nextButton").click(function () {
        frameParent.mask("Loading...");
        //Saving a product.
        editor.saveProduct()
            //If the product is saved correctly.
            .then(function (result) {
                //Saving product state info.
                saveData = result;
        })
            //If there was an error thrown when saving the product
            .catch(function (error) {
                frameParent.mask("Server error.");
                saveData = error;
        });
        //Getting proof images.
        editor.getProofImages()
            //If proof images a get correctly.
            .then(function (result) {
                //Saving proof images info.
                approveData = result;

                //Go to the approval page.
                goToPage("approve");

                frameParent.unmask();
        })
            //If there was an error while getting proof images.
            .catch(function (error) {
                frameParent.mask("Server error.");
                approveData = error;
        });
    });

    //Getting links to hi-res output.
    $("#approvePage #approveButton").click(function () {
        frameParent.mask("Loading...");
        //Getting links to hi-res output.
        editor.finishProductDesign()
            .then(function (result) {
                //Saving hi-res output info.
                renderData = result;

                //Go to the finish page.
                goToPage("finish");
                frameParent.unmask();
        })
            //If there was an error while finishing product.
            .catch(function (error) {
                frameParent.mask("Server error.");
                renderData = error;
        });
    });

    //Open a new product in the designer.
    $("#finishOrderPage #newDesign").click(function () {
        goToPage("design");
    });
    
    //Reopening the product in the designer.
    $("#approvePage #lnkEditAgain").click(function () {
        loadProduct(saveData.stateId);
        goToPage("design");
    });
    
    //Initializing the approval and the finish pages with the product info.

    //Initializing the approval page with links to the proof images.
    function setApprovePageData() {
        var previewElements = $("#approvePage .previewImg").attr("src", "");
        for (var i = 0; i < approveData.proofImageUrls.length && i < previewElements.length; i++) {
            previewElements[i].setAttribute("src", approveData.proofImageUrls[i]);
        }
    };

    //Initializing the finish page with print-ready output URL.
    function setFinishPageData() {
        $("#finishOrderPage #hiResLink").attr("href", renderData.hiResOutputUrls[0]);
    };
    
    function setEditorPageData() {
        loadProduct(productDefinition);
    };
    
    //Wizard navigation.
    
    function goToPage(to) {
        setupPage(to);
    }
    window.onpopstate = function (e) { setupPage(e.state); }

    var workflowPages = {
        "design": { elements: $("#editorPage"), setData: setEditorPageData },
        "approve": { elements: $("#approvePage"), setData: setApprovePageData },
        "finish": { elements: $("#finishOrderPage"), setData: setFinishPageData }
    }

    function setupPage(page) {
        var destPageData = workflowPages[page]

        if (typeof destPageData.setData === "function")
            destPageData.setData();

        workflowPages[currentPage].elements.fadeOut(function () {
            destPageData.elements.show();

            currentPage = page;
        });
    }
})
</script>

<body>
    <div id="wrapper">
        <div id="content">
        
            <!-- Design page -->
            <div id="editorPage" class="area">
                <div id="iframeWrapper">
                    <iframe id="editorFrame" width="100%" height="800px"></iframe>
                </div>
                <div id="saveAndNextButtonsWrapper">
                    <!-- Add Back Side button -->
                    <input id="addBackSide" type="button" class="btn btn-info btn-lg" value="Add Back Side" />
                    <!-- Finish design button -->
                    <input id="nextButton" type="button" class="btn btn-success btn-lg" value="Finish design →" />
                </div>
            </div>
            
            <!-- Approval page -->
            <div id="approvePage" class="area" style="display: none">
                <div class="container-fluid">
                    <h1>Approve Your Product</h1>
                        <!-- proof images -->
                        <img class="previewImg" id="preview" />
                        <img class="previewImg" id="previewPage2" />
                </div>
                <p>
                    <div id="approveButtonWrapper">
                    <input id="approveButton" type="button" class="btn btn-success btn-lg" value="Approve →" />
                    </div>
                </p>
                <div class="return">
                    <a id="lnkEditAgain">← I want to make some changes</a>
                </div>
            </div>

            <!-- Finish page -->
            <div id="finishOrderPage" class="area" style="display: none">
                <h1 class="">Your Product is Ready</h1>
                    <ul>
                        <!-- a link for downloading hi-res output -->
                        <li>The print-ready file can be downloaded from <a id="hiResLink">this link</a></li>
                    </ul>
                <div class="right">
                    <input id="newDesign" type="button" class="btn btn-info btn-lg" value="← New design" />
                </div>
            </div>
            
        </div>
    </div>
</body>
</html>

Additionally, you can copy the following CSS snippet and save it to the index.css file in the same folder where the index.html is placed. The styles will give your page a more user-friendly interface, however, it is not mandatory.

CSS
body {
    font: 12px/18px Arial, "Helvetica CY", "Nimbus Sans L", sans-serif;
    height: 100%;
    min-width: 960px;
}

.area,
.fluid {
    position: relative;
}

.area {
    width: 940px;
    margin: 0 auto;
}

#editorFrame {
    display: block;
    border: 0;
}

#content {
    padding: 0px 0px 100px;
}

#saveAndNextButtonsWrapper {
    text-align: right;
    margin-top: 10px;
}

#approvePage {
    font: 14px Tahoma,Arial,Helvetica,sans-serif;
}

#approvePage .previewImg {
    max-width: 600px;
    max-height: 600px;
    box-shadow: rgba(100, 100, 100, 0.8) 0 0 3px;
    margin-top: 1em;
}

#approvePage .container-fluid {
    text-align: center;
}

#approvePage .agree {
    border: dashed 2px #f00;
    text-align: left;
    padding: 4px 16px 16px;
    margin-top: 15px;
}

#approvePage #approveButtonWrapper {
    text-align: right;
}

#approvePage .return{
    margin-top: 20px;
    font-size: 14pt;
}

#approvePage .return a, a:active, a:focus {
    border-bottom: 1px solid rgb(169, 227, 149);
    color: rgb(100, 188, 70);;
}

#approvePage .return a:hover {
    color: rgb(43, 121, 16);
    text-decoration: none;
    cursor: pointer;
}

#finishOrderPage h3 {
    text-align: justify;
}

#finishOrderPage h1 {
    text-align: center;
}

#finishOrderPage .right {
    text-align: right;
}

#finishOrderPage ul {
    list-style-type: disc;
}

See Also

Manual

IFrame API Reference