Experience Views

Experience Views allow you to build custom web interfaces on top of your connected device data and present those interfaces to your Experience Users. Views are rendered as a reply to an Experience Endpoint, and the data backing your views comes from a Losant Experience Workflow.

Through the combination of endpoints, workflows, views and users, it is possible to build completely custom, web-based, user-specific solutions entirely within the Losant platform.

Viewing Experience Views

Experience Views List

Views are listed within the "Views" tab of your application's "Experience" subsection. Here you will see a heading for each type of view and a list of views of that type beneath it. Click the name of any view to edit it.

Adding an Experience View

From the Views list page, click the "Add" button next to the list heading for the type of view you would like to create. A view's type is not editable after creation; if you would like to change a view's type, you will have to create a new view of the type and copy the contents from the old view to the new one.

Create Experience View

Views are built as a combination of HTML and Handlebars templates. There are three types of Experience Views; each serves a distinct purpose, and each is configured in a different way.

Layouts

Layouts are "wrappers" in which pages are rendered. Most application experiences will only use a couple of layouts, as most customizations to a layout that would be applied on a per-page basis can be accomplished using {{section}} helpers.

Layout Configuration

Layout Configuration

A layout takes the following configuration options:

  • Name: The name of the layout. This is required and it must be unique across your application.
  • Description: An optional description of the layout. This is never displayed to end users.
  • Content: The combination of markup and Handlebars helpers used to render the layout. This can include {{section}} helpers and components, and it must include one {{page}} helper.

The {{page}} Helper

Your layout must include a single {{page}} helper somewhere within it. Wherever the helper is included is where the Experience Page using the layout will be rendered.

{{section}} Helpers

Your layout may optionally include multiple {{section}} helpers, which are blocks within the layout whose contents may be filled in by the page referencing the layout, or by components used within the page.

A section is defined as such, where "mySectionName" is a unique name within the layout:

{{section "mySectionName"}}

To include default section content to render in the event that neither the child page nor any component fills the section, define the section like so:

{{#section "mySectionName"}}Default Content{{/section}}.

The section's content is then defined in subsequent pages and their components using a {{#fillSection}} helper.

Using Layouts

Layouts are chosen as an optional property of an Experience Page, though a page's layout can be overridden within the Endpoint Reply node on a per-endpoint-request basis.

Layouts are optional within your application experience. However, if you choose not to define a layout for any page, that page should include all the necessary markup to properly render a web page (your doctype declaration, etc.).

Example Layout

A layout includes common elements that are used in all web pages you are rendering within your experience; for example, a typical layout should include the following:

  • Your <!doctype html> declaration
  • The HTML <head> as well as any common <script> and <link> (stylesheet) tags
  • Your opening and closing <body> tags
  • Common UI elements that do not change per page, such as your header and footer

Here is an example of everything a layout should include:

<!doctype html>
  <html lang="">
  <head>
    <meta charset="utf-8">
    <title>{{#section "pageTitle"}}Welcome{{/section}} | My Experience</title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Stylesheets should be included in the head, and they must be served from a third-party CDN -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
    <!-- we can add per-page styles using a {{#fillSection}} helper within a page -->
    {{section "pageStyles"}}
  </head>
  <body>
    <div class="wrapper">
      {{ component "mainNav" }}
      <!-- the page's content renders here -->
      {{ page }}
      {{ component "footer" }}
    </div>
    <!-- some scripts that are used on all pages can be included here -->
    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <!-- we can include scripts specific to the page using a section -->
    {{section "pageScripts"}}
  </body>
</html>

Pages

Pages are the fundamental Experience View type; without a page, it is impossible to render a custom user interface on top of your application data. You can have a single page for every endpoint in your application, or you can define pages that are used as templates for rendering specific types of data (e.g. a list of resources).

Page Configuration

Page Base Configuration

A page takes the following configuration options:

  • Name: The name of the page. This is required and it must be unique across your application.
  • Description: An optional description of the page. This is never displayed to end users.
  • Layout: The layout in which this page should be rendered. If you do not provide a layout, the page will be rendered without one (in which case you should choose "Custom" for your page type and, in the page content, provide a doctype and other tags necessary to render HTML in a browser.) If you choose a layout, your page will render at the location of the {{page}} helper within the layout.
  • Page Type: Choose either "Custom" or "Dashboard". More info on each page type is available below.

Custom Pages

Page Custom Configuration

Custom pages are configured much the same way layouts and components are: by providing a mix of HTML and Handlebars to reference the data passed down to the page. You may include any view components within your page content, in addition to providing {{#fillSection}} helpers for setting the contents of {{section}} helpers defined within a layout.

Dashboard Pages

The "Dashboard" page type makes it easy to render any dashboard that is also owned by your application's owner. This can be useful for adding some custom branding around your dashboard and also for controlling access to the dashboard (as well as default context settings) using the Experience Users feature.

Page Dashboard Configuration

A dashboard page takes the following additional configuration options:

  • Dashboard: The dashboard to render. This can be a specific dashboard or a Handlebars helper referencing a dashboard ID in the context provided to your page.
  • Time: The time (in Unix time) to render each block at. This can either be a specific time or a reference to a context property. Providing no time will default the dashboard to the current time. (Read more about viewing past dashboard states.)
  • Theme: Whether to render the dashboard in the "Light" or "Dark" theme.
  • Context: The context variables to pass down to the dashboard. If you chose a specific dashboard, you will see all the context variables defined for the dashboard and will have the option of setting a value for each. If you referenced an ID from the page's context, you must defined each key and value you wish to pass down. In both cases, the value can be a static value or a reference to a page context property.

Using Pages

In order to use a page, you must do the following:

  1. Define an Experience Endpoint for your users to visit.
  2. Build a Experience Workflow to handle the endpoint request and issue a response. (You can do this automatically from the endpoint's edit page.)
  3. Choose "Experience Page" as the "Reply Type" in the Endpoint Reply node and select your page as the one to render for the user.

In the Endpoint Reply node, you may optionally construct a pageData object to supply to the page. Note that there are a number of context properties available automatically, in addition to the pageData you may provide through the workflow.

Example Page

Given the example layout defined above, a typical custom page body may look like this:

{{#fillSection "pageTitle"}}
  This Is My Page Title
{{/fillSection}}

<!-- this content will populate the {{page}} helper within the layout -->
<div class="content">
  <h1>Hello there, {{experience.user.firstName}}!</h1>
  <p>You have access to the following devices:</p>
  <ul>
    {{#each pageData.devices}}
      {{component "deviceLink"}}
    {{/each}}
  </ul>
</div>
<!-- end page content. fillSections are removed from the document flow; their order and placement within the page does not matter. -->

{{#fillSection "pageStyles"}}
  <style type="text/css">
    .wrapper { width: 100%; } /* this page should be full-width */
  </style>
{{/fillSection}}

Components

Components are snippets of HTML and Handlebars that can be used within your layouts, pages or other components. Often components are invoked multiple times per page – such as when iterating over a list of items – but they can also be used simply as a means of cleaning up your code (for example, creating a component housing your experience's navigation code and referencing it one time in your layout).

Component Configuration

Component Configuration

Components take the following properties:

  • Name: The name of the component. This is required and it must be unique across your application.
  • Description: An optional description of the component. This is never displayed to end users.
  • Content: The combination of markup and Handlebars helpers used to render the component. This can include {{#fillSection}} helpers as well as other components.

{{#fillSection}} Helpers

The contents of any {{section}} helper defined within a layout can be filled by including a {{#fillSection}} block helper within a page (or a component referenced within a page). The helper can be placed anywhere within that view, and its contents are removed from the rendering flow of the view and pushed to the layout. You can see examples of {{#fillSection}} helpers being used in the example page and example layout provided above.

Given a {{section "mySectionName"}} helper defined within a layout, its contents would be populated by including the following in your page or component:

{{#fillSection "mySectionName"}}
  This content will be placed at the spot of "mySectionName" in the layout!.
{{/fillSection}}

When working with {{#fillSection}} helpers, keep the following rules in mind:

  • {{#fillSection}} helpers may be placed within pages, or within components that are called within those pages. If a component is referenced directly from a layout, its {{#fillSection}} declarations (and any {{#fillSection}} declarations from components referenced by that component) will be ignored.
  • When multiple {{#fillSection}} helpers attempt to set the content of the same {{section}} (as in, when both a page and a component reference the same section name in a {{#fillSection}} declaration), the last declaration the renderer sees is the one that will populate the {{section}}. For this reason, it is a good idea to put a {{#fillSection}} helper at the bottom of your page's content to ensure it is not overridden by any child component (unless, of course, that is the desired behavior).

Using Components

Components can be included within layouts, pages and other components. To render a component with the name "componentName", place the following helper where you would like the component to render:

{{component "myComponentName"}}

Passing Custom Context

By default, a component has access to the entire page context object when it is invoked as {{component "myComponentName"}}. However, it is possible to specify the context passed down to a component by adding a third argument:

{{component "myComponentName" pageData.myComponentContext}}

When passing context to a component, the passed context will become primary data available to the component. Its root value can be referenced with a period ({{.}}). If you wish to use the full context supplied to the page in this scenario, you must reference it under the "@root" property.

Example Components

In our example page, we referenced a "deviceLink" component that was inside an {{#each}} loop. In such a use case, the current iteration of the loop is the default context passed to the page; however, as mentioned above, one may still reference the top-level context using a @root property.

With that in mind, the "deviceLink" component may look like the following (with an "active" class included on the link to the current URL):

<li class="{{#eq @root.request.params.deviceId id}}active{{/eq}}"><a href="/devices/{{id}}">{{name}}</a></li>

A component can also be a one-time use snippet that is called directly from a layout. In these cases, their use is more for code cleanup and readability than anything else. Given our example layout, our "footer" component may look like the following:

<hr />
<div class="footer">
  &copy; 2018. All rights reserved.
</div>

Referencing Context

Data passed down to your views can be referenced using standard Handlebars paths. It is through this method that different user interfaces can be provided depending on the user requesting the page, the endpoint being viewed and the data returned by your backing workflow.

Data That is Always Provided

The following data is available in your root context from any layout, page or component:

{
  time, // the time of the request
  application, // object containing the application name and ID
  experience: {
    user, // object containing info on the user who made the request (if available)
    endpoint, // object containing info on the endpoint config
    page, // object containing info on the page config
    layout, // object containing info on the layout config,
    version // the version of the experience for the request
  },
  flow, // object containing the backing workflow's name, ID and version
  globals, // key/value mapping of any global variables from the application / workflow
  request: {
    path, // the actual request path
    method, // request method (e.g. get, post, patch, delete)
    headers, // object with header names as keys and their values mapped to them
    params, // object with path params as keys and their values mapped to them
    query, // object with query params as keys and their values mapped to them
    body, // request body (if applicable, e.g. in a post or patch request)
    cookies // object with cookie names as keys and their values mapped to them
  },
  pageData // object containing the custom data passed down from the workflow
}

The pageData Property

When replying to your endpoint request with an experience page, you have the option of passing additional information down to the view; that data is generated by the workflow replying to the request. All of that data is available under the pageData property within the view's context.

Render Logs

For debugging purposes, Losant provides a live log of render attempts for your experience views. This log creates an entry for every attempted page render (including failures), along with additional data about the render attempt, such as the chosen layout and any components referenced within.

Render Logs

The full log is available on the Experience Views main page. If you'd like to watch the log for a single component, visit that component's edit page and the log's contents will be limited to just that component.

Deleting Experience Views

A view can be deleted by clicking the "Delete" icon next to any view on the list page, or by clicking the "Delete" button in the footer of a view's edit page.

Take Care When Deleting Views

If you delete a page, any endpoint replies referencing that page will fail to load until you update its Endpoint Reply node to reference an existing page. The same is true for layouts referenced by a page or overridden within an Endpoint Reply node.

Deleted or renamed components will not cause the endpoint to fail; rather, the view will simply render no content in the spot where the offending component was referenced.