Multiple Pages
- 12 minutes to read
Multipage product support is required by a wide range of printing products, from two-sided business cards and booklets to full-fledged magazines and photobooks. The multipage products are not just about adding an ability to switch between pages in the editor. For example, books usually contain complex formatted text, including text wrapped around images, which has to be supported by the editor. Another aspect of the multipage product support is the editor speed. Since photobooks sometimes contain tens or even hundreds of pages, the editor's back end has to be optimized to process such an amount of graphics. The size of a book can be up to hundreds of megabytes or even a gigabyte, so it requires special techniques to work with such volumes of data. Fortunately, the Design Editor implements all these functionalities behind the scenes. All you need to do is set up a template for each page of your product.
In this topic, we elaborate on several ways to open and configure multipage products in the Design Editor and explain how to work with pages (surfaces) when the product is loaded. The rendering of multipage products is discussed in the corresponding topic.
Loading Multipage Products
You can create multipage products in a similar way as you do for the single page ones. Likewise, you use the loadEditor method, but the arguments you pass there differ.
Foremost, let us recall how you create single page products from PSD templates. You pass a string with a template name (without the extension) to the loadEditor
method, the templates should be placed in the ~\assets\designs\ folder. The following example illustrates loading a single page template into the editor.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Example of loading the editor</title>
<!-- The IFrame API script. IMPORTANT! Do not remove or change the ID. -->
<script id="CcIframeApiScript" type="text/javascript" src="http://example.com/CustomersCanvas/Resources/Generated/IframeApi.js">
</script>
</head>
<body>
<!-- The iframe to display the editor in. -->
<iframe id="editorFrame" width="100%" height="800px"></iframe>
</body>
<script>
// Initializing product with only one template "mytemplate.psd".
let productDefinition = { surfaces: ["mytemplate"] };
// Getting the iframe element to display the editor in.
let iframe = document.getElementById("editorFrame");
let editor = null;
// Loading the editor.
CustomersCanvas.IframeApi.loadEditor(iframe, productDefinition)
// 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("The editor failed to load with an exception: ", error);
});
</script>
</html>
To load a multipage product, the only thing you should change in this code is how you initialize the productDefinition variable. Let us look at several ways to set it up.
Loading Pages from a Folder
If your product consists of a significant amount of pages, it would be convenient to organize the templates into folders and load the entire folder as a product. To do this, create a subfolder in the ~\assets\designs\ folder and put your templates there. After that, create an object implementing the ISurfacesFromFolder interface with the designFolder
property set to this folder, and assign it to the surfaces
property.
let productDefinition = {
surfaces: {
designFolder: "myphotoBook"
}
};
The product pages are arranged alphabetically, by the template file names. For example, if template files are named a.psd
, b.psd
, and c.psd
, then a.psd
is the first page, b.psd
is the second one, and c.psd
is the third page.
Also, you can set a page mockups and preview mockups via the ISurfacesFromFolder.mockupFolder
and ISurfacesFromFolder.previewMockupFolder
properties.
let productDefinition = {
surfaces: {
designFolder: "myphotoBook",
mockupFolder: { down: "pageMockups" },
previewMockupFolder: "pagePreviewMockups"
}
};
Configuring Each Page Individually
Creating products from folders is easy, but it has certain disadvantages. For example, if there are many products and a few templates, it is unreasonable to copy the same files between many folders. To avoid this, it is possible to pass an array of PSD filenames to surfaces
. The following example illustrates the easiest way to define a two-sided business card.
let productDefinition = {
surfaces: ["businesscard_side1", "businesscard_side2"]
};
The pages appear in the same order as they are passed here.
Creating a Product from State Files
You can pass names of state files instead of PSD filenames to the loadEditor
method.
let productDefinition = [
"7a6ecf23-1286-4e90-8f18-c7c1c77e3cb0",
"565d5aa3-74ba-4d6a-860f-620410049847",
"ed0678cc-ed84-4471-99ac-355360a7b51c"
];
A state file may contain a number of surfaces. As a result, the editor combines all surfaces from these states into a single product. The page order in the resulting product will be the following: all pages from the first state loaded in the order as they went into it, all pages from the second state, and so on.
When you need certain surfaces from a state file, you can refer to them by index in the product definition. Note that the indexes start from 0
.
let productDefinition = {
surfaces: [
{ // Specify a state file name.
stateId: "ee9293bc-c887-433d-a5bd-454888f07a8a",
// Select the third surface.
surface: 2
},
{ // Specify a state file name. By default, the first page is applied.
stateId: "ed867273-5679-4927-b718-a53e92d6802c"
}
]
};
Loading Pages from a Multipage IDML File
You can organize your product pages into a multipage IDML template and load this IDML file as a product. In this case, specify productDefinition
by using the ISurfacesFromMultipageTemplate interface with the file
property set to this file, and assign it to the surfaces
property.
let productDefinition = {
surfaces: {
file: "myphotoBook"
}
};
Loading Products with Different Page Orientation
Some multipage products may have pages with a different orientation. You can load and edit such products in the Design Editor. To avoid additional processing when printing, you can configure them so that all product pages are rendered in either portrait or landscape orientation.
let productDefinition = {
surfaces: [
// Apply the default page orientation to the front page.
"Flyer_front",
{
printAreas: [ {
designFile: "Flyer_back",
// Rotate the hi-res output at 90 degrees clockwise.
hiResOutput: { RotateMode: "Rotate90" }
} ],
// Rotate the preview image of the back page at 90 degrees.
proofImage: { RotateMode: "Rotate90" }
}
]
};
You can also change the page orientation for all products in clientConfig.json or for a single product by using the IRenderingProperty interface. If the RotateMode
is specified in the editor's configuration and at the same time in a product definition, then the rotation angles are not combined, and the angle specified on a per-page basis is applied.
Specifying Additional Properties
You can configure safety lines, folding lines, and spines in the same manner as you do for single page products. For example, the following snippet sets up a folding line, which is displayed on each page.
let productDefinition = {
surfaces: {
designFolder: "myphotoBook",
foldingLines: [{
isVertical: false,
pen: { color: "green" }
}]
}
};
Such settings can be product-wide or specific to a certain page. If safety lines are configured both for a product and for a page, then the page settings override the product-wide ones. Let us illustrate this. The following product definition specifies three pages, where the first and the second pages have a safety line with the same parameters, but the third page has its own safety line.
let productDefinition = {
surfaces: [
// These pages have default safety lines.
"page1", "page2",
// The page3 has custom safety lines.
{ designFile: "page3",
safetyLines: [
{ margin: 15, color: "green" },
{ margin: 7.5, color:"red" }
]
}
]};
The following snippet demonstrates how to show safety lines on all pages except for the second one.
let productDefinition = {
defaultSafetyLines: [{
margin: 18,
color: "rgba(0,255,0,1)",
altColor: "rgba(255,255,255,0)"
}],
surfaces: [ "page1", { designFile: "page2", safetyLines: [] }, "page3" ]
};
Navigating in Multipage Products
When a multipage product is loaded into the web-to-print editor, the navigation area automatically appears in the Bottom Toolbar. If a product contains more than two pages, the navigation area displays the pagination control that allows switching between pages using page thumbnails and scrolling them left/right with the navigation buttons.
This screenshot illustrates the default configuration of the pagination control, which is defined in the ~\Configuration\clientConfig.json file.
"BottomToolbar": {
"surfaceSwitch": {
"enabled": true,
"showThumbnails": true,
"showSurfaceNames": false,
"scrollPageButtonsEnabled": true,
"toggleSurfaceButtonsEnabled": false,
"firstAndLastButtonsEnabled": true
}
}
Defining Page Names
In clientConfig.json or through the IBottomToolbarConfig interface, you can set the toggleSurfaceButtonsEnabled
property to true
to enable buttons for switching between product pages. If showSurfaceNames
is true
, then you can specify page names in the navigation area, passing the name
property to a surface definition as the following example shows.
let productDefinition = {
surfaces: [
{
name: "Left",
printAreas: [{ designFile: "TriFold_Side1" }]
},
{
name: "Middle",
printAreas: [{ designFile: "TriFold_Side2" }]
},
{
name: "Right",
printAreas: [{ designFile: "TriFold_Side3" }]
}
]
};
When you load a multipage product through the ISurfacesFromMultipageTemplate and ISurfacesFromFolder interfaces, you can specify either names
or name
property to enable page names in the navigation area. The names
array must contain as many elements as there are pages in the product. The following example specifies names for four product pages if the photoBook folder contains exactly four PSD files.
let productDefinition = {
surfaces: {
designFolder: "photoBook",
names: ["Cover", "Enjoy Life", "Adventure Awaits", "How We Met"]
}
};
For a two-page IDML template, you can specify names
and mockups as follows:
let productDefinition = {
surfaces: {
file: "flyer",
names: [ "Side A", "Side B" ],
pageTemplates: [{
mockup: {
up: "flyer-frame",
down: "background"
},
location: { X: "8", Y: "72" }
}]
}
};
The name
property of ISurfacesFromMultipageTemplate
and ISurfacesFromFolder
allows you to use a name mask for product pages. The value must contain the {0}
placeholder, which will be replaced with a page number, starting from 1
.
let productDefinition = {
surfaces: {
designFolder: "myphotoBook",
name: "Page {0}"
}
};
As a result, the navigation area will look as follows:
Navigating in Two Page Products
If a print product has only two pages, the editor replaces the navigation area with the Front side and Back side buttons by default. You can redefine these button names in the translations.json file, using the FRONT_SIDE
and BACK_SIDE
strings.
Configuring the Pagination Control for Certain Number of Pages
The Design Editor allows you to customize the navigation area for products consisting of a certain number of pages. For example, to change the default appearance of the two-page control, set up the specificForSurfaceCount property as follows:
"BottomToolbar": {
"surfaceSwitch": {
...
"specificForSurfaceCount": {
"2": {
"showSurfaceNames": false,
"showThumbnails": true
}
}
}
}
Here, "2"
stands for two-page products.
Manipulating Multipage Products
Customer's Canvas IFrame API allows you to manipulate product pages after the editor has loaded. You can get a list of pages, switch the editor from one page to another, and add or remove pages at runtime.
To control product pages, you should get a product model of an editor instance using the Editor.getProduct method. A product contains an array of surfaces (this is what pages are called in the API) and methods for manipulating them. For example, to swap two pages in a product, you should just swap two corresponding elements in the product.surfaces array.
Getting a Product and Its Surfaces
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Example of getting the product surfaces</title>
<!-- The IFrame API script. IMPORTANT! Do not remove or change the ID. -->
<script id="CcIframeApiScript" type="text/javascript" src="http://example.com/CustomersCanvas/Resources/Generated/IframeApi.js">
</script>
</head>
<body>
<!-- The iframe to display the editor in. -->
<iframe id="editorFrame" width="100%" height="800px"></iframe>
</body>
<script>
// Initializing product with only one template "mytemplate.psd".
let productDefinition = { surfaces: ["mytemplate"] };
// Getting the iframe element to display the editor in.
let iframe = document.getElementById("editorFrame");
let editor = null;
let product = null;
// Loading the editor.
CustomersCanvas.IframeApi.loadEditor(iframe, productDefinition)
// If the editor has been successfully loaded.
.then(function (e) {
// Retrieving a reference to the editor.
editor = e;
// Getting a product model.
editor.getProduct()
// If we have successfully obtained the product.
.then(function(p) {
product = p;
// Logging a number of pages.
console.log("The product contains " + product.surfaces.length + " page(s)");
});
})
// If there was an error thrown then show the message.
.catch(function (error) {
console.error("The editor failed to load with an exception: ", error);
});
</script>
</html>
Adding Surfaces
The following example shows how to add a blank page and a page with a specific template to a product at runtime.
// Loading the editor.
CustomersCanvas.IframeApi.loadEditor(iframe, productDefinition)
// If the editor has been successfully loaded.
.then(function (e) {
// Retrieving a reference to the editor.
editor = e;
// Getting a product model.
editor.getProduct()
// If we have successfully obtained the product.
.then(function(product) {
// Adding a blank surface.
product.addSurface(
{
// Defining the surface size in points (1 point = 1/72 inch).
width: 720, height: 504
})
// If the surface has not been added to the product.
.catch(function (error) {
console.error("Failed to add an empty surface with an exception: ", error);
});
// Adding a surface based on a template.
product.addSurface(
{
// Defining the template file.
printAreas: [{ designFile: "postcard_side2"}]
})
.catch(function (error) {
console.error("Failed to add a surface from a template with an exception: ", error);
});
});
})
// If there was an error thrown then show the message.
.catch(function (error) {
console.error("The editor failed to load with an exception: ", error);
});
Removing Surfaces
The following example shows how to remove two pages from a product at runtime: the first one and the one which is currently selected in the editor.
// Making sure that at least one surface remains in the product after removing a page.
if (product.surfaces.length > 1) {
// Removing the current surface.
product.removeSurface(product.currentSurface)
.then(function(result) {
console.log("Current surface removed");
})
.catch(function (error) {
console.error("Removing the surface failed with exception: ", error);
});
}
// Making sure that at least one surface remains in the product after removing a page.
if (product.surfaces.length > 1) {
// Removing the first surface.
product.removeSurface(product.surfaces[0])
.then(function(result) {
console.log("The first surface removed");
})
.catch(function (error) {
console.error("Removing the surface failed with exception: ", error);
});
}
Programmatically Switching Pages in the Editor
The following example switches the editor to the next page relative to the currently selected one.
editor.getProduct()
.then(function (product) {
// Getting the current surface index.
const indexOfCurrentSurface = product.surfaces.lastIndexOf(product.currentSurface);
let indexOfNextSurface = indexOfCurrentSurface + 1;
if (indexOfNextSurface > (product.surfaces.length - 1))
indexOfNextSurface = 0;
// Switching to the next surface.
return product.switchTo(product.surfaces[indexOfNextSurface]);
})
.then(function(result) {
console.log("Surface switched");
})
.catch(function (error) {
console.error("Switching the surfaces failed with exception: ", error);
});