custom.js & $functions
13 min
overview purple experience provides two javascript extension files file path when it runs what it can set custom server js storefront/assets/scripts/custom server js server (ssr) + client window $functions only custom js storefront/assets/scripts/custom js browser only window\ storefronthooks , window\ isissuelocked , window\ calculatestorefrontusertags $functions must be defined in custom server js , not custom js this is because $functions are used during config resolution, which happens on the server during ssr custom server js runs a strict sandbox — any attempt to access or set window properties other than $functions is blocked custom js is browser only and is the correct place for lifecycle hooks and lock state overrides when to use this use custom server js when you need to expose helper functions that views json can call during config resolution ( $functions ) use custom js when you need to track page views in an external analytics system ( onnavigationend ) run custom logic after content loads ( oncontentloaded ) intercept or customise purchase flows ( beforecontentpurchase , beforesubscriptionpurchase ) initialise ad networks ( onadinit ) customise lock state display for issues ( isissuelocked ) compute and set user tags based on purchase/subscription data ( calculatestorefrontusertags ) basic example custom server js — helper functions for config resolution window $functions = { getissueyear (issue) => (issue ? new date(issue publicationdate) getfullyear() tostring() ''), getoptionalsourceissueid (issue) => (issue && issue sourceissue ? issue sourceissue id ''), }; custom js — lifecycle hooks (browser only) window\ storefronthooks = { onnavigationend ({ event, viewcontext }) => { analytics track('page view', { path event urlafterredirects, platform viewcontext platform, }); }, }; in views json { "type" "html", "tag" "span", "text" "$functions getissueyear($context issue)" } configuration window\ storefronthooks — lifecycle callbacks register callbacks on window\ storefronthooks to react to framework lifecycle events all hooks are optional — define only the ones you need for the full hook reference (parameters, return values, and examples for each hook), see storefront hooks documentation docid\ ae kb6wwnkngu9dhr43hv available hooks at a glance hook when it fires onpurpleserviceinit purple service is ready onnavigationend after angular navigationend onnavigationstable app stable after navigation oncontentloaded catalog content fully loaded beforecontentpurchase before a content purchase beforesubscriptionpurchase before a subscription purchase entitlementchanged login / logout / subscription change subscriptionschanged user subscriptions updated onadinit ad network initialisation beforerenderad before an ad slot renders isadrefreshpaused ad refresh pause check isaddisabled per slot disable check getintersectionmargin lazy load trigger distance window $functions — callable from views json register functions on window $functions they are called synchronously during config resolution whenever $functions myfn( ) appears in views json window $functions = { // return a formatted year string from an issue object getissueyear (issue) => (issue ? new date(issue publicationdate) getfullyear() tostring() ''), // return empty string if issue has no sourceissue getoptionalsourceissueid (issue) => (issue && issue sourceissue ? issue sourceissue id ''), // functions can call other $functions or receive $context parts formattitle (title, suffix) => (title ? `${title} — ${suffix}` suffix), }; calling from views json { "type" "html", "tag" "span", "text" "$functions formattitle($context issue name, $context publication name)" } functions receive the already resolved argument values — $context substitution happens before the function is called functions can be nested $functions outer($functions inner($context attr)) return undefined or empty string to produce no output functions must be synchronous — async functions are not supported in config resolution window\ isissuelocked(issue, purpleservice) — custom lock logic override this async function to customise when an issue is shown as locked the default always returns false (no additional lock conditions) this is called only after the mandatory check ( purchasable true and purchased false ) returns false window\ isissuelocked = async function (issue, purpleservice) { // lock issues that have a custom property set if (issue properties && issue properties\['requires premium'] === 'true') { const ispremium = await purpleservice hassubscription('premium plan id'); return !ispremium; } return false; }; parameter description issue the catalogissue object being evaluated purpleservice purple service instance — provides access to entitlement and purchase apis window\ calculatestorefrontusertags(subs, issues, pubprods) — custom user tags when defined, this function is called after the user's purchase data is loaded it computes a set of boolean tags that are then accessible as $context usertags \<tag> important the function must always return all possible tags (even the ones that are false ), because the tracking service uses the full set to remove tags that are no longer active window\ calculatestorefrontusertags = (subs, issues, pubprods) => { const hasactivesub = subs some((s) => s purchased); const hasboughtissue = issues length > 0; return { issubscriber hasactivesub, haspurchasedissue hasboughtissue, ispremiumuser hasactivesub && hasboughtissue, }; }; access in views json { "type" "section", "condition" { "value" "$context usertags issubscriber", "operation" "equals", "comparevalue" "true" }, "content" \[{ "type" "html", "tag" "p", "text" "subscriber content" }] } parameter type description subs appsubscription\[] all subscription plans (purchased and not purchased) issues catalogissue\[] all purchased issues pubprods publicationproduct\[] all purchased publication products testing notes / edge cases $functions must be synchronous if a function returns a promise, the promise object is used as the value — it is not awaited this will produce broken output keep $functions purely synchronous calculatestorefrontusertags must return all tags if a tag key is missing from the return value, the previous value for that tag remains in context indefinitely always return every tag with an explicit true or false isissuelocked is called per issue, per render keep it lightweight avoid heavy api calls inside it; cache results if needed hook error handling experience wraps hooks in try catch if a hook throws, an error is logged but the app continues never rely on hooks throwing to cancel flows — return the appropriate result object instead see storefront hooks documentation for hook specific notes related topics context system docid\ c0z2lrmscicnrsjymkvub datasources docid\ fumbuabweon oto5wfevf todo insert link to dynamic url resolving