This paragraph is rendered inline by another snippet, via story.renderSnippet().

<% $(function () { // A snippet-owned timer. Without a teardown hook this keeps // firing forever after you navigate away, against DOM nodes // that no longer exist. const timer = setInterval(function () { s.ticks++; $("#ticks").text(s.ticks); }, 200); // Hand the cleanup to the engine: it runs once, when this // snippet is left, and is then discarded. story.onLeave(function () { clearInterval(timer); }); }); %>

Lifecycle

<%- story.renderSnippet("Banner") %>

A timer owned by this snippet has ticked 0 times.

Leave, and the next snippet checks whether it actually stopped: Walk away

That link's text is wrapped in <strong> on purpose. A handler reading event.target instead of event.currentTarget would see the inner element and the link would do nothing.

Story code has run <%- s.storyCodeRuns %> time(s) — it must be 1, even though this snippet renders another one inline.

<% $(function () { const before = s.ticks; $("#before").text(before); // If onLeave did its job the counter is frozen; sample again // after a second, by which point a live 200ms timer would have // advanced it by about five. setTimeout(function () { const after = s.ticks; $("#after").text(after); const stopped = after === before; $("#verdict") .text( stopped ? "PASS - the timer stopped when the snippet was left" : "FAIL - the timer is still running (" + (after - before) + " ticks leaked)" ) .addClass(stopped ? "pass" : "fail"); $("#log").html( s.eventLog.map(function (line) { return "<li><code>" + line + "</code></li>"; }).join("") ); }, 1000); }); %>

Check

Tick count on arrival: ?
Tick count one second later: ?

measuring…

Events seen so far

    Story code has run <%- s.storyCodeRuns %> time(s).

    Go back

    /*** * Story code runs exactly once, at the very beginning of the story. * * Counting the runs here is not busywork: if a snippet renders another * snippet inline (this example's "Start" does), a naive engine would run the * story code a second time for the nested render. The counter below makes * that visible. */ s.storyCodeRuns = (s.storyCodeRuns || 0) + 1; s.ticks = 0; s.eventLog = []; /*** * The engine fires two events on `window` around every snippet change: * * iff:snippet:leaving - before the outgoing snippet's DOM is replaced * iff:snippet:shown - after the incoming snippet has been rendered * * Both hand you the snippet object in question. Registering here (rather than * in global code) means the handlers are bound once for the whole story. */ $(window).on("iff:snippet:leaving", function (_event, snippet) { s.eventLog.push("leaving: " + (snippet ? snippet.name : "(nothing)")); }); $(window).on("iff:snippet:shown", function (_event, snippet) { s.eventLog.push("shown: " + snippet.name); });