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;
},
};
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.
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:
- Generate required number of adjunct subslides in selections as empty shells
- Populate each subslide's content at run-time using templates (eg. underscore)
Empty Preview / PresoManager edit mode:
- Detect
empty-preview
,edit-mode
orscreenshot-thumbnail
- Render empty content shells to mimic subslides - if required
- 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 textarea
s 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;
}