Building a Purple Experience
Components
Table-of-content (TOC)
17 min
in earlier versions of pxp, tocs have been configured via a toolbar the toc field in the toolbar is deprecated now in favour of the configuration below it allow greater flexibility this guide presents a fully configurable and flexible method for implementing a table of contents (toc) in pxp reference app https //staging purplemanager com/#appdetail;id=9e5a0da6 c284 4720 9b59 d942b2c48bf7 https //staging purplemanager com/#appdetail;id=9e5a0da6 c284 4720 9b59 d942b2c48bf7 content body component (app + web) 1\ toc button 1\ 1 overview create a custom toc button using an html component this approach allows full control over styling, icon selection , and functionality 1\ 2 json configuration 1\ 2 1 views json { "tag" "img", "type" "html", "tap" { "type" "setcontextvariables", "variables" \[ { "key" "tocopened", "value" "$functions updatetoc($context tocopened)" } ] }, "attributes" { "src" "resource //dynamic/storefront/assets/images/icons/toc svg", }, "class" "toc icon" } 1\ 2 2 custom server js updatetoc (tocopened) => { return !tocopened; } explanation tag "img" → specifies the element type (an image in this case) type "html" → renders as a raw html component tap sets the tocopened context variable by calling the updatetoc function this toggles the toc list open/closed it also sets a context variable accordingly via custom server js attributes src path to the toc icon image in this example, the icon is stored in dynamic resources class "toc icon" → apply custom css for styling 2\ toc panel this section explains how to configure a fully functional toc panel that displays a list of content items, allows navigation to each, and provides a close action 2\ 1 json configuration 2\ 1 1 toc data source { "type" "content", "contextkey" "toc content", "filter" { "properties" { "key" "slug", "value" "\ bundleslug" } }, "fetchoptions" { "includebundledcontent" true } } explanation purpose this data source should be added to the data field of the view it provides the content list for the toc panel content type in this example, a bundle type is requested includebundledcontent ensures that post data within the bundle is included in the response alternative the same logic can be implemented in the url resolver const content = await dataresolver findcontentbyid(id, { includebundledcontent true }); 2\ 1 2 list configuration 2\ 1 2 1 views json { "content" \[ { "tag" "div", "type" "html", "content" "\[]", "class" "toc backface", "tap" { "type" "setcontextvariables", "variables" \[ { "key" "tocopened", "value" "$functions updatetoc($context tocopened)" } ] }, "condition" { "value" "$context tocopened", "comparevalue" "true" } }, { "content" \[ { "content" \[ { "content" \[ { "tag" "h2", "type" "html", "content" "toc title" }, { "content" \[ { "tag" "button", "type" "html", "class" "icon icon close", "tap" { "type" "setcontextvariables", "variables" \[ { "key" "tocopened", "value" "$functions updatetoc($context tocopened)" } ] } } ], "type" "section", "tag" "div", "class" "toc actions" } ], "type" "section", "class" "toc header" }, { "content" \[ { "content" { "content" \[ { "tag" "img", "type" "html", "content" "", "attributes" { "src" "$context context thumbnails default" }, "class" "toc image" }, { "content" \[ { "tag" "h2", "type" "html", "content" "$context context name", "class" "toc title" }, { "tag" "p", "type" "html", "content" "$context context description", "class" "toc description" } ], "type" "section", "class" "toc content" } ], "type" "section", "tap" { "type" "navigate", "path" "read/$context\['toc content']\[0] properties slug/$context context properties slug/" }, "class" \[ "toc card", { "value" "active", "condition" { "comparevalue" "$context pathparams postslug", "value" "$context context properties slug" } } ] "class" "toc card" }, "datasource" { "data" "$functions getbundlelist($context\['toc content']\[0])", "type" "context" }, "type" "list" } ], "type" "section", "class" "toc content" } ], "type" "section", "class" "toc collapsible" } ], "type" "section", "condition" { "comparevalue" "true", "value" "$context tocopened" } } ], "type" "section", "class" "toc container" } 2 1 2 2 custom server js getbundlelist (bundle) => { return bundle contents map(content => content post) } 2 1 2 3 custom css toc icon { filter invert(1); } toc container toc backface { position fixed; width 100%; height 100%; top 0; left 0; background color rgba(0, 0, 0, 0 6); } toc { padding 0 28px 24px 28px; font family var( contentfont); border radius 4px; display grid; justify content end; } toc collapsible { position fixed; top 56px; z index 1; width 100%; max width 384px; right 0; bottom 50px; height auto; overflow auto; background color white; webkit overflow scrolling touch; } toc collapsible toc header button { display block; } toc toc actions { display flex; justify content end; webkit justify content flex end; align items flex end; webkit align items flex end; } toc toc actions button icon { margin right 0 2em; } toc toc header { text transform uppercase; border bottom 2px solid black; padding bottom 12px; padding top 35px; display flex; background color white; z index 1; justify content space between; position webkit sticky; position sticky; top 0; height 70px; } toc toc header h2 { letter spacing 0 3px; font size 16px; line height 22px; font weight 500; } toc toc header button { display none; color black; font size 16px; } toc toc content { display grid; } toc card { display flex; align items center; background #fff; border radius 12px; box shadow 0 4px 10px rgba(0, 0, 0, 0 05); padding 16px; margin bottom 12px; transition transform 0 2s ease, box shadow 0 2s ease; } toc card\ hover { transform translatey( 2px); box shadow 0 6px 16px rgba(0, 0, 0, 0 08); } toc card active { font weight 700; background color #f0f4ff; / light highlight / box shadow 0 6px 16px rgba(0, 0, 0, 0 12); border 1px solid #3366ff; / optional accent / } toc image { width 64px; height 64px; border radius 50%; object fit cover; margin right 16px; } toc content { flex 1; } toc title { font size 1 2rem; margin 0; font weight 600; color #333; } toc description { font size 0 9rem; margin top 4px; color #666; } 2 2 component breakdown toc backface displays a gray overlay behind the toc panel when it’s open clicking it closes the toc panel using setcontextvariables visibility is controlled by the condition "$context tocopened" === true toc header displays the toc title contains the toc actions section with a close button to hide the toc panel toc content (main list) displays the list of toc items uses the toc content data source from 1 1 uses the helper function getbundlelist to normalize data into a list of posts works with any source type (bundle, dossier, collection) as long as the result is a post list toc item (toc card) contains thumbnail (toc image) post title (toc title) description (toc description) clicking a toc item navigates to the post using read/$context\['toc content']\[0] properties slug/$context context properties slug/ post slug $context context properties slug bundle slug $context\['toc content']\[0] properties slug active css class is applied to a toc card when its content slug matches the post slug in the current url notes you can customize layout, styles, and actions to fit project needs post swiper (app) 1\ toc button same as the content body component 2\ toc panel 2\ 1 json configuration 2\ 1 1 toc data source not needed 2\ 1 2 list configuration 2\ 1 2 1 views json similar to the content body component with some updates 1\ tap action it now uses a different navigate url to reflect the path of the current view additionally, a query param is passed the query param comes from the swiper component used in the view its value represents the current post being displayed "tap" { "type" "navigate", "path" "readbundle/$context content properties slug", "params" { "swiper id" "$context context id" } } 2\ active class for toc card the condition for applying the active class has been updated it now uses id instead of slug the swiper id is used as the reference for the currently displayed post "class" \[ "toc card", { "value" "active", "condition" { "comparevalue" "$context\['swiper id']", "value" "$context context id" } } ] 3\ toc panel data source thanks to the url resolver, the bundle is available under content in the context, making it accessible for rendering "datasource" { "data" "$functions getbundlelist($context content)", "type" "context" } final output { "content" \[ { "content" \[ { "content" \[ { "tag" "h2", "type" "html", "content" "toc title" }, { "content" \[ { "tag" "button", "type" "html", "class" "icon icon close", "tap" { "type" "setcontextvariables", "variables" \[ { "key" "tocopened", "value" "$functions updatetoc($context tocopened)" } ] } } ], "type" "section", "tag" "div", "class" "toc actions" } ], "type" "section", "class" "toc header" }, { "content" \[ { "content" { "content" \[ { "tag" "img", "type" "html", "content" "", "attributes" { "src" "$context context thumbnails default" }, "class" "toc image" }, { "content" \[ { "tag" "h2", "type" "html", "content" "$context context name", "class" "toc title" }, { "tag" "p", "type" "html", "content" "$context context description", "class" "toc description" } ], "type" "section", "class" "toc content" } ], "type" "section", "tap" { "type" "navigate", "path" "readbundle/$context content properties slug", "params" { "swiper id" "$context context id" } }, "class" \[ "toc card", { "value" "active", "condition" { "comparevalue" "$context\['swiper id']", "value" "$context context id" } } ] }, "datasource" { "data" "$functions getbundlelist($context content)", "type" "context" }, "type" "list", "attributes" { "data" { "sample" "$context\['swiper id']" } } } ], "type" "section", "class" "toc content" } ], "type" "section", "class" "toc collapsible" } ], "type" "section", "condition" { "comparevalue" "true", "value" "$context tocopened" } }