White paper: Templates in web-to-print.  Free download

Rendering Multipage Products

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

Rendering Hi-res Output 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. 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 a Single File


To render your product to a multipage PDF file (three and more pages), you need a license with multipage product support.

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.

"rendering": {
    "hiResOutputToSeparateFiles": false,
    "hiResOutputFileFormat": "PDF"

To change these parameters on a product level without changing global settings in the clientConfig.json file, use the IConfiguration interface in Iframe API.

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; the output file type can be any of the supported ones.

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

"rendering": {
    "hiResOutputToSeparateFiles": true,
    "hiResOutputFileFormat": "JPEG"

To change these parameters on a product level without changing global settings in the clientConfig.json file, use the IConfiguration interface in Iframe API.

configuration = {
    rendering: {
        hiResOutputToSeparateFiles: true,
        hiResOutputFileFormat: "JPEG"

Displaying the Proof Images

In multipage products, proof images for all pages should be available to the user so that they can approve them and give the product the "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. The user creates a design.
  2. The user can save the design using the Save button. In this case, the system calls the saveProduct method.
  3. The user initializes the transfer to the approval page. The system displays proof images.
  4. The user can return to editing the product. In this case, the system restores the product using the state ID received in the previous step.
  5. The user approves the design. The system displays the finish page containing the link to the hi-res output.

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.

Alternatively, you can download this sample as a zip file and extract its content into the root folder of Customer's Canvas.

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <meta charset="utf-8" />
    <title>Photobooks - The sample page - Customer's Canvas</title>

    <script type="text/javascript" src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
    <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/Generated/IframeApi.js">

    <script type="text/ecmascript">
        $(function () {
            // Defining the product.
            var productDefinition = {surfaces: { "designFolder": "photoBook" } };

            // Defining the editor configuration.
            var configuration = { widgets: { FinishButton: { mode: "Disabled" } }};

            var editorFrame = $("#editorFrame");

            var editor = null;

            // Loading the product into the editor.
            function loadProduct(product) {

                CustomersCanvas.IframeApi.loadEditor(editorFrame[0], product, configuration)
                    // If the editor has been successfully loaded.
                    .then(function (e) {
                        editor = e;
                    // If there was an error thrown when loading the editor.
                    .catch(function (error) {
                        console.error("Load failed with exception: ", error);

            // Loading the product defined above into the editor.

            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.
            $("#editorPage #saveButton").click(function () {

                    // If the product has been successfully saved.
                    .then(function (result) {
                        // Saving the product state info.
                        saveData = result;
                    // If there was an error thrown when saving the product.
                    .catch(function (error) {
                        saveData = error;
                        console.error("Product saving failed with exception: ", error);

            // Getting links to the proof images.
            $("#editorPage #nextButton").click(function () {

                    // If proof images have been successfully received.
                    .then(function (result) {
                        // Saving proof images info.
                        ApprovePage.approveData = result;

                        // Go to the approval page.
                    // If there was an error thrown while getting the proof images.
                    .catch(function (error) {
                        ApprovePage.approveData = error;

            // Finishing the product customization.
            $("#approvePage #approveButton").click(function () {

                    // If the product customization has been successfully completed.
                    .then(function (result) {
                        // Saving hi-res output info.
                        renderData = result;

                        // Go to the finish page.
                    // If there was an error thrown while finishing the product customization.
                    .catch(function (error) {
                        renderData = error;

            // Opening a new product in the original state into the editor.
            $("#finishOrderPage #newDesign").click(function () {

            // Reopening the product in the editor.
            $("#approvePage #lnkEditAgain").click(function () {

            // Initializing the design and finish pages with the product info.

            // Initializing the finish page with the 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]);

            // The wizard navigation.

            function goToPage(to) {
            window.onpopstate = function (e) { setupPage(e.state); }

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

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

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

                workflowPages[currentPage].elements.fadeOut(function () {

                    currentPage = page;

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

            window.ApprovePage = {
                // Displaying the 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.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 and 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.

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

                proofImg.on("load", function () {

                    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) {
                    if (window.console)

                // 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);


    <div id="wrapper">
        <div id="content">
            <!-- The design page -->
            <div id="editorPage" class="area">
                <div id="iframeWrapper">
                    <iframe id="editorFrame" width="100%" height="800px"></iframe>
                <div id="saveAndNextButtonsWrapper">
                    <!-- The Save button -->
                    <input id="saveButton" type="button" class="btn btn-info btn-lg" value="Save" />
                    <!-- The Finish design button -->
                    <input id="nextButton" type="button" class="btn btn-success btn-lg" value="Finish design >" />
            <!-- The 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 class="row">
                        <div class="col-md-1 btnContainer left">
                            <input id="leftBtn" type="button" class="btn arrowBtn" />
                        <div class="col-md-10 proofImgContainer">
                            <img class="proofImg" />
                        <div class="col-md-1 btnContainer right">
                            <input id="rightBtn" type="button" class="btn arrowBtn" />

                <div class="agree">

                        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.

                    <div id="approveButtonWrapper">
                        <input id="approveButton" type="button" class="btn btn-success btn-lg approveButton" value="Approve >" />
                <div class="return">
                    <a id="lnkEditAgain">&lt; I want to make some changes</a>

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

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.

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

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

/* The Approve page
    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


IFrame API Reference