Bundles
9 min
prerequisite please follow this documentation https //app archbee com/docs/uaccdxznep51uaogpyt7o/iufxwm4gli7bnlp1 aap1 of how to set up bundles in the hub to navigate in bundles, pxp can be configured with buttons and a table of content (toc) docid\ stqod2njlnlttq5ganb p in apps, post in bundles also can be swiped to set up, various configurations must be done explicitly find below the instructions to configure it in web and app (button navigation) app only (swiper) web and app (button in web, swiper in app) web and app (button navigation) deprecated this button navigation method is no longer recommended use the post swiper instead (see app only (swiper) docid 4zoulnsikv3z4meksj0ax below) — it now works on both app and web this section is kept for reference only this configuration uses a single content body component and does not support swiping between items its ux is mainly intended for usage on websites to implement this approach, you need to configure two views (see view settings docid jl6ufl2jd2us8qkfrbpp for further details) view 1 path prefix/\ contentslug view 2 path prefix/\ bundleslug/\ postslug (note prefix can be replaced with any static path segment ) both views include the content body component the only difference between them lies in the property filters one uses \ contentslug, and the other uses \ bundleslug post swiper (app & web) to leverage swipe functionality, we use a swiper of content body components (also referred to as the post swiper ) since the content body component can render both posts and bundles, this setup allows users to swipe through individual posts within a bundle the post swiper works on both web and app , and the swiper configuration is pretty much the same for both the main difference is the url structure , which is handled by the url resolver (see url resolver setup below) and swiper configuration you may create two separate views — one for web and one for app — if they need to differ; in this documentation we use a single view for both { "content" \[ { "content" { "type" "content body", "contextkey" "context", "id" "$context context id" }, "datasource" { "data" "$context posts", "type" "context" }, "type" "swiper", "effect" "slide", "loop" false, "pathvalue" { "value" "$context context properties slug", "condition" { "value" "$context context properties slug", "operation" "set", "and" \[ { "value" "$context context properties slug", "operation" "set" }, { "operation" "equals not", "value" "$context device type", "comparevalue" "desktop" } ] } } } ], "name" "content" } configuration overview this configuration defines the following behavior datasource uses a context based data source and reads its data from $context posts, which contains the flattened list of posts provided through the url resolver (see url resolver config below) content renders each slide using a content body component contextkey "context" exposes the post data to the component id "$context context id" binds each slide to its corresponding post effect and loop effect "slide" enables the standard slide transition effect loop false disables continuous looping, making the swiper behave as a linear post reader pathvalue generates the seo friendly url path segment from the post slug using $context content properties slug a condition is applied so that the value is only used when the slug is defined (set), and the current device is not desktop applying this guard ensures that pathvalue is evaluated against the active slide context rather than the parent swiper context and prevents empty path segments from being added to the url for more information, see the pathvalue (path scope) docid\ kvprj609p4fmtg4uqayys section of the swiper component documentation hnb url resolver setup configure the url resolver (see dynamic url resolving docid\ gkzy7wd58h fc2qwqurgr ) so that the same view can be opened on both web and app platforms while using the appropriate url structure for each environment this setup consists of two parts datatopathresolver — generates the outgoing url for a piece of content urltoviewresolvers — maps an incoming url back to a view and its associated data to keep path definitions consistent, define a shared constant for path prefixes and reuse it across both resolvers const paths = { web 'readweb', app 'readapp', }; datatopathresolver datatopathresolver maps content to the url used to open it the generated url depends on the current platform app (platform !== 'web') app urls use content ids because they are stable and available offline supported url formats bundle or standalone post /{app}/{id} post within a bundle /{app}/{bundleid}?swiper id={postid} when opening a bundled post, the bundle is loaded and the swiper uses the swiper id query parameter (url scope) to determine the initial slide web (platform === 'web') web urls use slugs to create human readable and seo friendly urls supported url formats bundle or standalone post /{web}/{slug} post within a bundle /{web}/{bundleslug}/{postslug} async function datatopathresolver({ content, dataresolver }) { if (!content) { return; } const { platform } = dataresolver contextservice metadata; const isbundle = content contenttype === 'bundle'; const issinglepost = content contenttype === 'post' && content bundleid == null; const ispostinbundle = content contenttype === 'post' && content bundleid != null; // app logic — use ids (stable, offline friendly) if (platform !== 'web') { if (isbundle || issinglepost) { return { paths \[`/${paths app}/${content id}`] }; } if (ispostinbundle) { // open the post's issue bundle with this post as the initial id return { paths \[`/${paths app}/${content bundleid}?swiper id=${content id}`] }; } return; } // web logic — use slugs (human readable / seo) if (isbundle || issinglepost) { return { paths \[`/${paths web}/${content properties slug}`] }; } if (ispostinbundle) { const bundle = await dataresolver findcontentbyid(content bundleid); // bundleslug/postslug — the post slug is matched by the swiper's pathvalue return { paths \[`/${paths web}/${bundle properties slug}/${content properties slug}`] }; } } urltoviewresolvers urltoviewresolvers maps incoming urls back to the content view and provides the data required to render it both web and app routes resolve to the same view and supply an identical viewcontext containing content — the resolved content item with bundled content included posts — a flattened list of posts used as the swiper data source ($context posts) to avoid duplication, the shared logic is extracted into a helper function // shared fetch a content (post or bundle) and build the view context async function resolvecontentview(contentid, dataresolver, source) { try { const content = await dataresolver findcontentbyid(contentid, { includebundledcontent true }); const posts = content contenttype === 'bundle' ? content contents map((c) => c post) \[content]; return { viewname 'content', viewcontext { content, posts }, }; } catch (error) { console error(`error resolving ${source} content `, error); return { notfound true }; } } const urltoviewresolvers = \[ { // web content urls resolve based on slugs — /readweb/\ bundleslug(/\ postslug) pathpattern `/${paths web}/\ contentslugs+`, async viewresolver({ match, resolveddata, dataresolver }) { const maincontentslug = match params contentslugs\[0]; const maincontent = resolveddata\[maincontentslug]; if (!maincontent || !maincontent contents? length) { return { notfound true }; } return resolvecontentview(maincontent contents\[0] id, dataresolver, 'web'); }, }, { // app content urls resolve based on ids — /readapp/\ contentid pathpattern `/${paths app}/\ contentid`, skipresolveddata true, // required for offline viewresolver({ match, dataresolver }) { return resolvecontentview(match params contentid, dataresolver, 'app'); }, } ]; styling buttons to change the position of the nav buttons, you can override it in css as follows app // app css selectors media (max width 767px) { // default is absolute pxp swiper swiper button { position fixed !important; } // default is 10px swiper button next { right 0px !important; } // default is 10px swiper button prev { left 0px !important; } } web (default css from experience) it can be overwritten via scss // default web css selectors bundle button prev { left 0; right auto; } bundle button next { right 0; left auto; } bundle button prev, bundle button next { position absolute; top 50%; width 27px; height 44px; margin top 22px; z index 10; cursor pointer; background { size 27px 44px; position center; repeat no repeat; } } important remarks a post can be associated with multiple bundles therefore, when opening a post, the expected behavior needs to be clearly defined below are the possible options open the post directly , without reference to any bundle open the post within the bundle that matches the bundleid associated with the post open a separate view or popup listing all possible bundles, allowing the user to choose display a small section within the component (e g , content or search) that lists all related bundles, letting the user select one to open currently, option 2 used in this documentation