Rendering Multipage Products

This topic dwells on two aspects of multipage products: rendering hi-res output files and displaying proof images.

Rendering Hi-res Ouput Files

The only distinctive feature of rendering multipage products is the number of hi-res output files you get. You can get a single hi-res output file containing all pages or multiple files, one per each page in the product. This behavior is configured via the two parameters: hiResOutputToSeparateFiles and hiResOutputFileFormat. The hiResOutputToSeparateFiles parameter specifies if an output file should be created for each page of a product. And the hiResOutputFileFormat parameter is involved because the only hi-res output format in Customer's Canvas which can contain multiple pages is PDF. Let us consider both configurations.

Rendering Single File

To create a single hi-res output file containing all pages of a product, set the output type to PDF and disable rendering to separate files. In this case the output file will be named result.pdf.

To make this configuration default for all products edit the ~\Configuration\clientConfig.json file:

JavaScript
"rendering": {
    "hiResOutputToSeparateFiles": false,
    "hiResOutputFileFormat": "PDF",
    ...
},

To change these parameters on product level without changing global settings in the ~\Configuration\clientConfig.json file use the IConfiguration interface in Iframe API:

JavaScript
configuration = { rendering: {
                    hiResOutputToSeparateFiles: false,
                    hiResOutputFileFormat: "PDF"
                }};

Rendering Multiple Files

To create multiple hi-res output files, one for each page, enable rendering to separate files; output file type can be any of the supported ones.

To make this configuration default for all products edit the ~\Configuration\clientConfig.json file:

JavaScript
"rendering": {
    "hiResOutputToSeparateFiles": true,
    "hiResOutputFileFormat": "JPEG",
    ...
},

To change these parameters on product level without changing global settings in the ~\Configuration\clientConfig.json file use the IConfiguration interface in Iframe API:

JavaScript
configuration = { rendering: {
                    hiResOutputToSeparateFiles: true,
                    hiResOutputFileFormat: "JPEG"
                }};

Displaying Proof Images

In multipage products proof images for all pages should be available to a user so that they could approve them and give the product "go-ahead" for printing. The code sample in this section represents a three-step wizard that contains the design page, the approval page that allows a user to view proof images for all pages, and the finish page. Its workflow includes the following steps:

  1. A user creates a design.
  2. The user initializes transfer to the approval page. The system saves the product and displays proof images.
  3. The user can return to editing the product. In this case the system restores the product using the state ID got on the previous step.
  4. The user approves the design. The system displays the finish page containing links to hi-res output and return-to-edit URL.

To make this 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.
HTML
<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <meta charset="utf-8" />
    <title>Sample business card - Customer`s Canvas</title>
    
    <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>

    <script type="text/ecmascript">
        $(function () {
            //Defining the product.
            productDefinition = {surfaces: { "designFolder": "photoBook" } };
            
            //Defining the editor configuration.
            configuration = { widgets: {FinishButton: {mode: "Disabled"}}};
        
            var editorFrame = $("#editorFrame");

            var editor = null;

            var frameParent = editorFrame.parent();

            //Loading the 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 has been successfully 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 defined above product into the editor.
            loadProduct(productDefinition);
            
            var currentPage = "design";

            //JSON Object containing data generated by finishProductDesign.
            var renderData = null;

            //JSON Object containing data generated by saveProduct.
            var saveData = null;
            
            //Saving the product and getting links to the proof images.
            $("#editorPage #nextButton").click(function () {
                frameParent.mask("Loading...");
                //Saving a product.
                editor.saveProduct()
                    //If the product has been successfully saved.
                    .then(function (result) {
                        //Saving product state.
                        saveData = result;
                })
                    //If there was an error thrown when saving the product
                    .catch(function (error) {
                        frameParent.mask("Server error.");
                        saveData = error;
                        console.error("Product saving failed with exception: ", error);
                });
                //Getting proof images.
                editor.getProofImages()
                    //If proof images has been successfully got.
                    .then(function (result) {
                        //Saving proof images info.
                        ApprovePage.approveData = result;

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

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

            //Completing product customization.
            $("#approvePage #approveButton").click(function () {
                frameParent.mask("Loading...");
                editor.finishProductDesign()
                    //If product customization has been successfully completed.
                    .then(function (result) {
                        //Saving hi-res output info.
                        renderData = result;

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

            //Load the product in the original state in the editor.
            $("#finishOrderPage #newDesign").click(function () {
                goToPage("design");
            });
            
            //Reopening the product in the editor.
            $("#approvePage #lnkEditAgain").click(function () {
                loadProduct(saveData.stateId);
                goToPage("design");
            });
            
            //Initializing the design and finish pages with the product info.

            //Initializing the finish page with print-ready output URL and a link for reopening the product in the editor for further editing.
            function setFinishPageData() {
                $("#finishOrderPage #hiResLink").attr("href", renderData.hiResOutputUrls[0]);
                $("#finishOrderPage #linkForFeatureEdit").attr("href", renderData.returnToEditUrl);
            };
            
            //Loading product into the editor.
            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: ApprovePage.setData, onPageReady: ApprovePage.fixMask },
                "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>

    <script id="approvePageModule" type="text/ecmascript">
        (function () {
            var currentProofIndex = 0;
            
            var maskTimeout = null;

            window.ApprovePage = {
                //Displaying proof image.
                setPreviewImage: function (index) {
                    var proofImageUrls = ApprovePage.approveData.proofImageUrls;

                    if (index < proofImageUrls.length && index >= 0) {
                        if (ApprovePage.proofImg.attr("src") != proofImageUrls[index]) {
                            ApprovePage.proofImg.hide();
                                                        
                            if (maskTimeout !== null) {
                                clearTimeout(maskTimeout);
                                maskTimeout = null;
                            }
                            
                            maskTimeout = setTimeout(function () {
                                maskTimeout = null;
                                ApprovePage.proofImg.parent().mask("Loading ...");
                            }, 250);
                            
                            ApprovePage.proofImg.attr("src", proofImageUrls[index]);
                        }

                        currentProofIndex = index;
                        $('#approvePage #pageNumber').text(currentProofIndex + 1 + " of " + proofImageUrls.length);
                        $("#approvePage #leftBtn").prop('disabled', index == 0);
                        $("#approvePage #rightBtn").prop('disabled', index == proofImageUrls.length - 1);
                    }
                },

                //Initializing the approval page.
                setData: function () {
                    //Displaying/hiding navigation buttons
                    if (ApprovePage.approveData.proofImageUrls.length === 1) {
                        $('#approvePage .arrowBtn').css("display", "none");
                        $('#approvePage #pageNumber').css("display", "none");
                    } else {
                        $('#approvePage .arrowBtn').css("display", "inline-block");
                        $('#approvePage #pageNumber').css("display", "block");
                    }
                    //Displaying the proof image of the first page.
                    ApprovePage.setPreviewImage(0);
                },

                fixMask: function () {
                    var proofImgContainer = $("#approvePage .proofImgContainer");
                    if (proofImgContainer.isMasked()) {
                        var maskText = $(".loadmask-msg", proofImgContainer).text();
                        proofImgContainer.mask(maskText);
                    }
                }
            };

            $(function () {
                var proofImg = $("#approvePage .proofImg");
                ApprovePage.proofImg = proofImg;

                proofImg.on("load", function () {
                    if (maskTimeout !== null) {
                        clearTimeout(maskTimeout);
                        maskTimeout = null;
                    }
                    else {
                        proofImg.parent().unmask();
                    }

                    proofImg.show(null);
                    
                    var parentHeight = proofImg.parent().height();
                    var imgHeight = proofImg[0].height;
                    
                    proofImg.css("margin-top", imgHeight <= parentHeight ? (parentHeight - imgHeight) / 2 + "px" : 0);

                }).on("error", function (e) {
                    proofImg.parent().mask("Error");

                    if (window.console)
                        console.log(e);
                });

                //Display the previous proof image.
                $("#approvePage #leftBtn").click(function () {
                    ApprovePage.setPreviewImage(currentProofIndex - 1);
                });

                //Display the next proof image.
                $("#approvePage #rightBtn").click(function () {
                    ApprovePage.setPreviewImage(currentProofIndex + 1);
                });
            });

        })();
    </script>
</head>
    
<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">
                    <!-- 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>
                    <div class="row">
                        <div class="col-md-12">
                            <div id="pageNumber"></div>
                        </div>
                    </div>
                    <div class="row">
                        <div class="col-md-1 btnContainer left">
                            <input id="leftBtn" type="button" class="btn arrowBtn" />
                        </div>
                        <div class="col-md-10 proofImgContainer">
                            <img class="proofImg" />
                        </div>
                        <div class="col-md-1 btnContainer right">
                            <input id="rightBtn" type="button" class="btn arrowBtn" />
                        </div>
                    </div>
                </div>

                <div class="agree">
                    <h1>Approval</h1>

                    <h3>The product will be printed exactly as it appears above. By clicking the "Approve" button below, I agree that spelling, content and layout are correct.
                    </h3>

                    <div id="approveButtonWrapper">
                        <input id="approveButton" type="button" class="btn btn-success btn-lg approveButton" value="Approve >" />
                    </div>
                </div>
                <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>
                        <!-- a link to reopen the product in the web-to-print editor -->
                        <li>You can return to designing the product by initializing the editor using <a id="linkForFeatureEdit">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 should copy the following CSS snippet and save it to the index.css file in the same folder where the index.html is placed.

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;
}

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

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

    #approvePage .arrowBtn {
        width: 40px;
        height: 88px;
        background-size: 40px 88px;
        background-position: center;
        background-repeat: no-repeat;
        background-color: rgba(67, 40, 229, 0.16);

    }

    #approvePage .btnContainer {
        height: 88px;
        position: absolute;
        top: 50%;
        margin-top: -44px;
    }
    #approvePage .left {
        left: 0;
    }
    #approvePage .right {
        right: 0;
    }

    #approvePage #rightBtn {
        background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M27%2C22L27%2C22L5%2C44l-2.1-2.1L22.8%2C22L2.9%2C2.1L5%2C0L27%2C22L27%2C22z'%20fill%3D'%23ff0000'%2F%3E%3C%2Fsvg%3E");
    }

    #approvePage  #leftBtn {
        background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M0%2C22L22%2C0l2.1%2C2.1L4.2%2C22l19.9%2C19.9L22%2C44L0%2C22L0%2C22L0%2C22z'%20fill%3D'%23ff0000'%2F%3E%3C%2Fsvg%3E");
    }


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

    #approvePage .proofImgContainer {
        height: 600px;
        margin-top: 10px;
        margin-left: 8.33333333%;
        margin-right: 8.33333333%;
        
    }

    #approvePage .row {
        position: relative;
    }

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

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

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

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

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

    #approvePage .agree .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