Bundles 2.0 Demo Repo

"Imagination will often carry us to worlds that never were. But without it we go nowhere" - Carl Sagan

Bonjour!

Welcome to the Bundles 2.0 Live Demo. The main audience for this demo is existing developers working within Expression looking for a "crash course" on the new stuff Bundles 2.0 provides - and analogues to existing techniques that will be familiar to individuals with experience developing within Expression.

If you're not familiar with Expression - find us over at expression.cloud. Expression is a fully customizable SaaS CMS, Digital Commerce, and Development Platform that empowers developers and marketers to build amazing digital experiences together. Bundles are just one part of that experience so if you've stumbled upon us, take a look - If you've been looking for an alternative to Wordpress, Drupal, Magento, and all the usual suspects, you've found what you're looking for.

Entry Point

This page, like all pages in Expression, is rendered with an Element. An Element is an HTML template for marking up data pulled from Datasources, used to render blocks of content. Elements are based on the Handlebars templating language. Elements can reference other Elements, allowing for modular markup construction

The Element rendering this page (template.hbs) can be viewed in GitHub here: xpr/element/template.hbs

Each Element can optionally have a configuration file. The configuration for this Element can be viewed in GitHub here: xpr/element_config/template.json

This page is using Elements to render links to files in GitHub: {{{XprElement bundlePath="git_link" options.path="xpr/element/git_link.hbs"}}}.

Output:

xpr/element/git_link.hbs .

Bundle Web Directory

Bundles can now include static media. This is great for custom images or assets which should be developer managed, rather than client managed. Static media lives in the bundle's web directory: xpr/web/

Using for CSS and JS

This is how we include the CSS and JS for this bundle: href="{{{WebRoot xpr.bundle}}}/css/devon-custom.css"

Developers may be interested in using this feature to use CSS/JS compilers or js package managers to compile custom css, and place the end result in the /xpr/web/ directory.

Request Context

Let's see how you can dump context variables using the XprJson. We will dump the xpr variable

. This is the same xpr variable you may be used to from working with Elements in traditional Bundles.

{{{XprJson xpr}}}

Output:
{"settings":[],"request":{"debug":false,"uri":"\/","pageLandingType":"section","cookies":[],"domain":{"_links":{"self":{"href":"\/api\/sections\/7","title":"Section","name":"\/"},"sectionTypes":{"href":"\/api\/sectionTypes\/","title":"SectionType"},"find":{"href":"\/api\/sections\/{id}","templated":true}},"Id":7,"SortOrder":1,"Path":null,"Status":1,"Type":"domain","UsesPlaylists":false,"Variables":"","Popup":null,"UsesPermissions":false,"UsesSSL":true,"Target":"default","Config":null,"ModId":null,"Aliasing":null,"LockParent":null,"CreatedBy":null,"DefaultPageTitle":"","AdminTemplate":null,"Package":null,"ComponentDir":null,"ComponentFile":null,"RestrictedLink":"","DocType":"strict","LastModified":"2020-05-05 08:50:35","CreatedOn":null,"UrlRemainderPattern":"","ComputedDisplayDate":null,"RendererBundlePath":":bundles-demo\/element\/template","Name":"bundles-demo.xpr.cloud","Description":"","MetaTagKeywords":"","MetaTagDescription":"","Title":"bundles-demo.xpr.cloud","Slug":"bundles-demo.xpr.cloud","Invisible":0,"_locale":"en_CA","CanonicalUrl":"https:\/\/bundles-demo.xpr.cloud\/","IsActive":true,"_UsesPermissions":"unrestricted","_embedded":{"Links":[{"_links":{"self":{"href":"\/api\/sections\/sectionlink\/","title":"Section","name":"\/"}},"CanonicalUrl":"https:\/\/bundles-demo.xpr.cloud\/","_locale":"en_CA"}],"Children":[{"_links":{"self":{"href":"\/api\/sections\/5024","title":"Section","name":"\/"}},"Id":5024}],"DomainLanguages":[{"_links":{"self":{"href":"\/api\/domainlanguages\/27","title":"DomainLanguage","name":"\/"}},"Id":27}]}},"store":{"_links":{"self":{"href":"\/api\/store\/stores\/1","title":"Store","name":"\/"},"find":{"href":"\/api\/store\/stores\/{id}","templated":true}},"Id":1,"Name":"Default Store","StreetAddress":null,"Apartment":null,"PostalCode":null,"City":null,"PhoneNumber":null,"CurrencyId":1,"DefaultLocale":"","InventoryManagement":"physical","BackOrdersEnabled":false,"AdditionalOrderEmails":null,"UserGroupId":1,"IsDefault":null,"DefaultCustomerType":0,"LoginOnRegistration":false,"UseEmailAsLogin":false,"TimezoneId":0,"InitialOrderStatus":"Pending","ActiveCategoryIdVarName":null,"ActiveProductIdVarName":null,"DisplayOutOfStock":false,"InvoiceTemplateId":0,"SubscriptionEmailTemplateId":0,"DomainId":7,"_embedded":{"Country":{"_links":{"self":{"href":"\/api\/store\/countries\/1","title":"Country","name":"\/"}},"Id":1},"Region":{"_links":{"self":{"href":"\/api\/store\/countryRegions\/1","title":"CountryRegion","name":"\/"}},"Id":1},"Domain":{"_links":{"self":{"href":"\/api\/sections\/7","title":"Section","name":"\/"}},"Id":7}}},"users":[],"section":{"_links":{"self":{"href":"\/api\/sections\/7","title":"Section","name":"\/"},"sectionTypes":{"href":"\/api\/sectionTypes\/","title":"SectionType"},"find":{"href":"\/api\/sections\/{id}","templated":true}},"Id":7,"SortOrder":1,"Path":null,"Status":1,"Type":"domain","UsesPlaylists":false,"Variables":"","Popup":null,"UsesPermissions":false,"UsesSSL":true,"Target":"default","Config":null,"ModId":null,"Aliasing":null,"LockParent":null,"CreatedBy":null,"DefaultPageTitle":"","AdminTemplate":null,"Package":null,"ComponentDir":null,"ComponentFile":null,"RestrictedLink":"","DocType":"strict","LastModified":"2020-05-05 08:50:35","CreatedOn":null,"UrlRemainderPattern":"","ComputedDisplayDate":null,"RendererBundlePath":":bundles-demo\/element\/template","Name":"bundles-demo.xpr.cloud","Description":"","MetaTagKeywords":"","MetaTagDescription":"","Title":"bundles-demo.xpr.cloud","Slug":"bundles-demo.xpr.cloud","Invisible":0,"_locale":"en_CA","CanonicalUrl":"https:\/\/bundles-demo.xpr.cloud\/","IsActive":true,"_UsesPermissions":"unrestricted","_embedded":{"Links":[{"_links":{"self":{"href":"\/api\/sections\/sectionlink\/","title":"Section","name":"\/"}},"CanonicalUrl":"https:\/\/bundles-demo.xpr.cloud\/","_locale":"en_CA"}],"Children":[{"_links":{"self":{"href":"\/api\/sections\/5024","title":"Section","name":"\/"}},"Id":5024}],"DomainLanguages":[{"_links":{"self":{"href":"\/api\/domainlanguages\/27","title":"DomainLanguage","name":"\/"}},"Id":27}]}},"article":{"_links":{"self":{"href":"\/api\/articles\/5","title":"Article","name":"\/"}},"Id":5,"Title":"Content Management Article #1","PageTitle":"","Description":"","Html":"<p><b>Hola!&nbsp;<\/b>I am content within a content management article. If you're seeing this content, It was pulled dynamically and rendered by an Element!<\/p>\n","CreatedOn":"2020-04-29 20:24:38","Active":1,"PendingArticleId":0,"MetaTagKeywords":"","MetaTagDescription":"","CreatedBy":7,"Homepage":false,"SortOrder":0,"Invisible":false,"Searchable":false,"StartDate":null,"EndDate":null,"DisplayDate":null,"LanguageParentId":5,"ArticleLink":false,"ArticleLinkUrl":"","ArticleLinkRedirect":false,"RenderLinkedArticleUrl":false,"LastModified":"2020-05-04 10:19:27","LastModifiedBy":7,"Slug":"content-management-article-1","CanonicalUrl":"https:\/\/bundles-demo.xpr.cloud\/content-management-article-1","RendererBundlePath":"","_embedded":{"Section":{"_links":{"self":{"href":"\/api\/sections\/7","title":"Section","name":"\/"}},"Id":7},"Language":{"_links":{"self":{"href":"\/api\/languages\/1","title":"Language","name":"\/"}},"Id":1},"Domain":{"_links":{"self":{"href":"\/api\/sections\/7","title":"Section","name":"\/"}},"Id":7}}},"urlParams":{"section_id":"7","section_copy_id":"0"},"user":null,"language":{"_links":{"self":{"href":"\/api\/languages\/1","title":"Language","name":"\/"},"find":{"href":"\/api\/languages\/{id}","templated":true}},"Id":1,"Name":"English","Default":true,"Active":true,"IsoCode":"en","Abbreviation":"EN","IsoCode2":"CA","Locale":"en_CA"},"client":{"ip_address":"18.204.227.117","http_accept":"text\/html,application\/xhtml+xml,application\/xml;q=0.9,*\/*;q=0.8","http_accept_charset":null,"http_accept_encoding":"gzip","http_accept_language":"en-US,en;q=0.5","http_referer":null,"http_user_agent":"CCBot\/2.0 (https:\/\/commoncrawl.org\/faq\/)"},"body":"","headers":{"X-Varnish":"403934904","Accept-Encoding":"gzip","X-Forwarded-For":"18.204.227.117","Host":"bundles-demo.xpr.cloud","Accept-Language":"en-US,en;q=0.5","Accept":"text\/html,application\/xhtml+xml,application\/xml;q=0.9,*\/*;q=0.8","User-Agent":"CCBot\/2.0 (https:\/\/commoncrawl.org\/faq\/)","X-Forwarded-Proto":"https","X-Real-Ip":"18.204.227.117"}},"site":{"home":{"section":{"_links":{"self":{"href":"\/api\/sections\/7","title":"Section","name":"\/"},"sectionTypes":{"href":"\/api\/sectionTypes\/","title":"SectionType"},"find":{"href":"\/api\/sections\/{id}","templated":true}},"Id":7,"SortOrder":1,"Path":null,"Status":1,"Type":"domain","UsesPlaylists":false,"Variables":"","Popup":null,"UsesPermissions":false,"UsesSSL":true,"Target":"default","Config":null,"ModId":null,"Aliasing":null,"LockParent":null,"CreatedBy":null,"DefaultPageTitle":"","AdminTemplate":null,"Package":null,"ComponentDir":null,"ComponentFile":null,"RestrictedLink":"","DocType":"strict","LastModified":"2020-05-05 08:50:35","CreatedOn":null,"UrlRemainderPattern":"","ComputedDisplayDate":null,"RendererBundlePath":":bundles-demo\/element\/template","Name":"bundles-demo.xpr.cloud","Description":"","MetaTagKeywords":"","MetaTagDescription":"","Title":"bundles-demo.xpr.cloud","Slug":"bundles-demo.xpr.cloud","Invisible":0,"_locale":"en_CA","CanonicalUrl":"https:\/\/bundles-demo.xpr.cloud\/","IsActive":true,"_UsesPermissions":"unrestricted","_embedded":{"Links":[{"_links":{"self":{"href":"\/api\/sections\/sectionlink\/","title":"Section","name":"\/"}},"CanonicalUrl":"https:\/\/bundles-demo.xpr.cloud\/","_locale":"en_CA"}],"Children":[{"_links":{"self":{"href":"\/api\/sections\/5024","title":"Section","name":"\/"}},"Id":5024}],"DomainLanguages":[{"_links":{"self":{"href":"\/api\/domainlanguages\/27","title":"DomainLanguage","name":"\/"}},"Id":27}]}}}},"server":{"date":"2020-05-28"},"bundle":"bundles-demo"}

About Element

Demonstrates using {{{XprElement}}} to pull in an Element

The code for this Element (about.hbs) can be viewed in GitHub here: xpr/element/about.hbs

{{{XprElement bundlePath="about"}}}
Output:

... Hello! I am a Sub Element!

This repository intends to practically demonstrate how to accomplish the main mechanics of development in Expression using the Bundles 2.0 framework. This Element is a simple snippet of static HTML.

Attached Data

For these examples, we will be pulling data from bundles-demo.xpr.cloud's Content Management API's

  • The Element rendering this page (template.hbs) can be viewed in GitHub here: xpr/element/template.hbs
  • The configuration for this Element can be viewed in GitHub here: xpr/element_config/template.json
  • An Element's configuration includes the Datasources attached, and any options to configure those Datasources. This template Element has a Datasource attached which uses the datasource/articles_by_section.json Datasource Definitionto populate the variable {{PageArticles}} for this Element.

    {{#each PageArticles}} <li> {{Title}} </li> {{/each}}
    • Content Management Article #1
    • Content Management Article #2

    Context Passing

    Elements can have data from the root (also known as parent) context passed into them to be rendered. Here we are going to loop over the same data as in the Attached Data context, but instead of rendering each article inline, pass the article to a sub Element for rendering. xpr/element/details/article_details.hbs

    {{#each PageArticles}} {{{XprElement bundlePath="details/article_details" context.Article=this}}} {{/each}}

    Output:

    I am a Sub Element. I accept "Article" objects and render them. Here is a rendering of the article that was passed into me:

    Title:Content Management Article #1

    Html:

    Hola! I am content within a content management article. If you're seeing this content, It was pulled dynamically and rendered by an Element!

    I am a Sub Element. I accept "Article" objects and render them. Here is a rendering of the article that was passed into me:

    Title:Content Management Article #2

    Html:

    This is a second article from Content Management

    Element Options

    "Sub" Elements can also have their own Datasources attached. While it is usually better to "fetch up front and pass down", sometimes you want an Element which controls and fetches its own data. Here we demonstrate an Element which will fetch an article based on Id, and we will use the PageArticles variable from the previous example to supply the list of Ids.

    Note that we are "wasting" some effort here by refetching from the API, this is just an illustration

    These are the files in use for this demonstration, and an overview of the flow:

    {{#each PageArticles}} {{{XprElement bundlePath="details/article_details_fetching" options.ArticleId=this.Id}}} {{/each}}

    I am a Sub Element. I fetch "Article" objects based on an option passed in, and then render them.

    Title:Content Management Article #1

    Html:

    Hola! I am content within a content management article. If you're seeing this content, It was pulled dynamically and rendered by an Element!

    I am a Sub Element. I fetch "Article" objects based on an option passed in, and then render them.

    Title:Content Management Article #2

    Html:

    This is a second article from Content Management

    SSJS - Bind Directly To Element

    Element Inclusion

    {{{XprElement bundlePath="details/ssjs" options.message='Hello World!'}}}

    Element Configuration

    xpr/element_config/details/ssjs.json { "datasources": [ { "__comment" : "Note that there is no 'path' specified for this Datasource, in Bundles 2 there is no need to create 'null' adapters when only requiring a script binding", "name" : "script_output", "scripts" : [ { "_comment" : "Notice the preceeding / in the path which resolves to the script in /server_js/simple_echo. If the path was set to 'simple_echo' with no preceeding /, it would look for a 'neighbor file' which would be in /server_js/details/simple_echo", "path" : "/simple_echo", "options" : { "message" : "{{options.message}}" } } ] } ] }

    Script

    xpr/server_js/simple_echo.js //this is a "simple echo" JS script to demonstrate binding scripts to Elements and Datasources. exports.process = function(context, options) { return options["message"]; }

    Result

    I am a sub element. I have an attached SSJS script which I can render the output of. The output looks like this:

        "Hello World!"
    

    SSJS - Datasources & Modules

    Coming Soon...

    Bundle Routes

    Bundle Routes are specified in the "routes.json" file at the root of the Bundle's xpr folder. xpr/routes.json A route binds an endpoint (URL) and it's HTTP request verb to a script. and provides a formatter Element to render the result of the script.

    This demo shows an ajax GET route, running the same script but supplying different formatters: one for HTML and one for JSON.

    Result:

    Result: