Skip to main content

Dynamically generated subslides

Adjunct subslides give Content Developers the ability to generate additional subslides as required per individual preso. To achieve this, we will be using the application hook, selections. Subslide generation can be based on any data that is available to selections (eg. selected customer(s), contact(s), context and/or feed data etc.) and can be updated each time a presentation is edited.

Prepare the slide

In order to allow for the addition of adjunct subslides, the target slide requires a subslide-container element. The subslide-container is used as the destination container for the adjunct subslides.

<article id="slide_id" class="">
<header>
<h1>Slide title</h1>
</header>

<!-- "subslide-container" REQUIRED, destination for adjuncts -->
<div class="subslide-container"></div>

<footer class="slide-footer">
<div
class="navbtn-target down"
data-br-role="transition"
data-br-target="next"
></div>
<div
class="navbtn-target up inactive"
data-br-role="transition"
data-br-target="prev"
></div>
</footer>
</article>

As with standard slides, it is still possible to add other elements above and below that sit underneath and overlay the subslides.

Update the project file

In order for the slide to be registered on the server as an adjunct subslide container, it must be tagged as subslide_container in the project.yaml.

sections:
- key: were_innovative
title: "We're innovative"
slides:
- { key: our_agents, title: "Our agents", tags: [subslide_container] }

Generate subslides

The final step is adding the adjunct subslide(s) to our target slide using the application hook, selections.

Add selections interface to hooks

Let's set it up so that the CDK and LivePreso recognise that you want to deal with selections. First you will want to create a hooks file, use the hooks object in the project.yaml to define where in your deck your hooks.js file is located.

project.yaml

hooks: js/hooks.js

Second, we'll need a selections file. In general, you place this in js/hooks/selections.js. To this file you will add the bare minimum for selections to be loaded.

selections.js

export default {
onSave({ sections }) {
return sections;
},
};

hooks.js

Going back to our hooks.js file, you need to export the hook:

import Selections from "./hooks/selections.js";

export default {
selections: Selections,
};

That's it! The CDK and LivePreso now read selections.

Declare and attach subslides

Each slide contains the attribute subslides, which is an array of subslide objects. Each subslide object contains their own collection of attributes relevant to that subslide.

Selections code saves any changes made to the sections object. This includes adding new subslides, clearing all existing subslides, updating contents of the subslides etc.

In order to generate an adjunct subslide, the following attributes are required:

{
title: `Slide title`,
html_content: `<section class="subslide content page${pageNumber}">
${subslideContent}
</section>`,
slide: targetSlide.url,
enabled: true,
sequence: 0
}

See the adjunct subslides options reference for further breakdown.

Complete example:

Feed data includes a list of available products, selections.js loops through product data generating a table, pushing out new subslides as required by the length of the table.

export default {
onSave({ sections, feeds }) {
// Fetch target slide
var flattenedSlides = _.flatten(
_.map(sections, function (section) {
return section.slides;
})
);

var targetSlide = _.find(flattenedSlides, function (slide) {
return slide.key === "target_slide";
});

// Kill all existing subslides
targetSlide.subslides = [];

var productsList = feeds.products_list;

// Display a max of 10 products on each subslide
var productGroups = _.chunk(productsList, 10);

_.each(productGroups, function (productGroup, index) {
var pageNumber = index + 1;
var pageClass =
pageNumber > 9 ? `page${pageNumber}` : `page0${pageNumber}`;

var $productTable = $("</table>");

_.each(productGroup, function (product) {
var $tableRow = $("</tr>");
$tableRow
.append(`<td>${product.name}</td>`)
.append(`<td>${product.price}</td>`);

$productTable.append($tableRow);
});

// Populate new subslides
// All options below are REQUIRED for adjunct subslide generation
targetSlide.subslides.push({
title: `Product page ${pageNumber}`,
html_content: `<section class="subslide content ${pageClass}">
${$productTable}
</section>`,
slide: targetSlide.url,
enabled: true,
sequence: index, // starts from 0
});
});

return sections;
},
};
info

selections.js is run each time a new presentation is created, and each time an existing presentation is edited and saved. Keep this in mind and consider how your code should behave in each instance. eg. On presentation creation, adjunct subslides are generated. On presentation edit, adjunct subslides are updated. See isNewAppointment data option for testing if selections.js is being run to create a new preso, or edit an existing.

warning

JS cannot be included in the html_content markup. Any JS required for the slide, must be added to the parent slide's <script> tag as usual.

Updating adjunct subslides (preso edit)

It is not required to nuke a slide's adjunct subslides if you just want to make an update to the existing array. For example, looping through the existing slide's subslides, making any required changes to the html_content etc. might be preferred for your particular implementation. You can also push/pop any extra new/unrequired subslides from the array instead of nuking all of them.

When there's no preso data to draw

When data gets involved, some slide designs have a tendency to fall over. In order to cater to all preso modes, you will need to develop for the situation where there is so no preso data to draw from when rendering you adjunct subslides.

As our selections code needs to run in order for any dynamically generated subslides (adjunct subslides) to exist, we often end up with a set of blank slides when previewing would-be adjunct subslides in an Empty Preview mode (such as the LivePreso presets page) or PresoManager edit mode.

Unfortunately, we cannot use fixtures to supply data in this instance, and will need to employ some trickier tactics.

Templating your slide elements

Keep your adjunct subslide template assets within reach of the slide at run-time. We recommend using Underscore templates.

Selections:

  1. Generate required number of adjunct subslides in selections as empty shells
  2. Populate each subslide's content at run-time using templates (eg. underscore)

Empty Preview / PresoManager edit mode:

  1. Detect empty-preview, edit-mode or screenshot-thumbnail
  2. Render empty content shells to mimic subslides - if required
  3. Populate slide using the slide template assets

Using Underscore templates

Due to how the LivePreso app loads in <script> tags, you won't be able to use these to declare your Underscore templates. Instead, we recommend using textareas which are hidden with display: none; on your slide, but still accessible by javascript when required.

Example:

<article id="performance">
<!-- Other slide content -->

<textarea class="template performance-template">
<h3><%= data.title %></h3>
<table>
<% _.each(data.stats, function(stat) { %>
<tr>
<td><%= stat.name %></td>
<td><%= stat.value %></td>
</tr>
<% }) %>
</table>
</textarea
>
</article>
$("#performance").on("sliderendered", () => {
const $pageContainer = $("#performance");

const performanceTemplate = _.template(
$pageContainer.find(".performance-template").val(),
null,
{ variable: "data" }
);

$(".js-stats-container", $pageContainer).html(
performanceTemplate({
title: "January review",
stats: [
{
name: "Uptake",
value: "58%",
},
{
name: "Retention",
value: "97%",
},
],
})
);
});
textarea.template {
display: none;
}