The Hub is a fully customizable and data-driven Delight XR element that connects multiple VR contents through a navigation hub that is explorable in VR.


The interface presented in the hub is composed of widgets like content grids and menus. These widgets (like the content itself) are created through markup as child elements of the <dl8-hub>. A powerful action system based on filter and sorter actions ensures a flexible approach to what is shown in content views and in which order.

Getting started

The simplest reasonable form of the <dl8-hub> includes a few contents wrapped in a <dl8-hub-content> element and just one grid widget (<dl8-hub-grid>). All widgets and views (i.e. things that are actual interface elements) need to be grouped into a layout component to tell the system how to arrange the widgets around the user. In the current version this layout element is the <dl8-hub-vizor>.

With this basic building blocks at hand a good markup starting point for a simple hub would look like this:

    <dl8-video title="Video 1" poster="p1.jpg" format="STEREO_360_TB">
      <source src="video1.mp4" type="video/mp4"/>
    <!-- ... -->   
    <dl8-img title="Image 6" poster="p2.jpg" src="img.jpg" format="MONO_360">

The result might look something like this:

Hub Markup in Depth

In this section we will go over the different elements that make up the hub in detail and explain their markup and element API.


This is the root element. Since it’s also an embed element like other Delight VR contents (videos, images, etc.) it has the same common API (width, height, poster, title, etc.). There are two additional parameters to control the room that surrounds the hub which behave exactly like the equally named parameters of the <dl8-cinema> element and setup the 360° image for the room:

room-src=”<URI>” (optional)

room-format=”<string>” (optional)

There are several child elements that can be added to the <dl8-hub>. There are two different kinds: Elements that provide data and semantic (<dl8-hub-content>, <dl8-hub-filter>, <dl8-hub-sorter> and elements that provide the visual representation (<dl8-hub-vizor>)


This element groups up all the content that the hub should display. It is important to note that there is a separation of appearance and data at play here. Contents defined as child elements of this grouping element have no visual representation but only provide the data for the visual elements (aka widgets). The child elements can either be regular Delight VR contents (dl8-video, dl8-img, dl8-tour, etc.) or <dl8-hub-group> elements. Additionally you can add <dl8-external-content> elements which represent links to other sites and external content. This allows you to setup things like banner ads, affiliate links and other traffic redirection mechanisms. 

To account for use cases with massive amounts of data and server-side filtering and pagination logic the <dl8-hub-content> mode attribute can be set to dynamic. This in combination with two additional events on the element allow for dynamic fetching of contents for the widgets. The attributes are as follows:


on-request-content=”<function(startIdx, endIdx, filters, sorter, contentCallback)>”
This function will be called whenever the hub detects that a content is missing upon user interaction and needs to be fetched from the server. Through the given arguments you can formulate paginate queries to the server that then can respond with a subset of contents. On server response you can create an array of contents that adhere to the Delight VR json spec (see below) and pass them as an argument to the contentCallback function.

on-request-content-count=”<function(filters, contentCountCallback)>”
This function will be called when the hub needs to know the item count for a specific subset of the contents. This is used to build up pagination and other related things. When the server responds with the count just pass that as an argument to the contentCountCallback function.

The Delight VR JSON/Object Spec

To support dynamically adding contents when the dl8-hub-content mode is dynamic you need to pass an array of contents to the contentCallback function in the on-request-content handler. The data passed to this callback must adhere to the Delight VR Object specification. Here is an example of how a <dl8-video> element with two sources would look like:

  tagName: "dl8-video",
  title: "Example Video",
  poster: "example-poster.jpg",
  format: "STEREO_360_TB",
  _children: [{
    tagName: "source",
    src: "example-low.mp4",
    type: "video/mp4",
    quality: "low"
  }, {
    tagName: "source",
    src: "example-high.mp4",
    type: "video/mp4",
    quality: "high"

Generally a content is always described by the tagName, all subsequent attributes and optional children tags adhering to the same spec recursively in the _children property. Below you will find a description of the format:

<data> = {
  tagName: <a-valid-dl8-content-tag>,
  opt <attrNameInCamelCase>: <string>, // NOTE: camel case will be
  // converted to attribute
  // notation:
  // forExample -> for-example
  opt _children: [ // NOTE: recursive nesting is 
    // possible

Filter and sorter API

When the on-request-content and on-request-content-count handlers are called, your JavaScript function will be given a filters and a sorter argument. These arguments are arrays of filterIds or sorterIds that you can use to identify your filter and sorter objects to formulate a backend query.


The <dl8-hub-group> is a meta element that can group contents by defining actions (filtering, sorting) that are triggered when interacting with it. Like a menu item that can appear amidst contents in a content widget.

<dl8-hub-group title="Music" poster="poster.jpg">
  <dl8-hub-action-filter for="main" filter="musicFilter">


The <dl8-hub-filter> is a grouping element for all available filters. Filters are means to filter content according to content types, existing content attributes or even freely definable content attributes (x-attrs). They can be referenced by <dl8-hub-action-filter> actions. Each filter has a filter-id attribute to reference it and a value attribute to tell the system what should be filtered. Attribute filter additionally have a attr attribute to declare over which content attribute to filter. Given that you are using the dynamic content mode you can use the <dl8-hub-filter-dynamic> to reference remote implementations. Possible child elements of <dl8-hub-filter> are:

filter-id=”<string>” attr=”<string>” value=”<string>”
filter-id=”<string>” attr=”<string>” value=”<string>”
filter-id=”<string>” attr=”<string>” value=”<array>”
filter-id=”<string>” attr=”<string>” value=”<array>”
filter-id=”<string>” value=”<string>”
filter-id=”<string>” value=”<array>”

This for example can be used in combination with a <dl8-hub-group> element to define a grid tile that, upon interacting, filters the grid view to only show videos:


 <dl8-video ...></dl8-video>
 <dl8-img ...></dl8-img>
 <dl8-hub-group title="Videos" poster="poster.jpg">
 <dl8-hub-action-filter for="main" filter="videoFilter">

 <dl8-hub-filter-element-is filter-id="videoFilter" value="dl8-video">

 <dl8-hub-grid view-id="main"></dl8-hub-grid>



This is a grouping element for all sorters. Sorter are means to sort content over freely defined content attributes. Sorter and Filter have lot in common. Both can be referenced by actions via the sorter-id and define the attribute over which to sort with the attr attribute. Sorting can either be done lexicographical or numerical. This is defined by the type attribute. Given you are working in a dynamic query mode (see above for details) you can use the dl8-hub-sorter-dynamic to reference your remote server implementation of a sort. Currently the following sorter are available:

sorter-id=”<string>” attr=”<string>” type=”<lexicographical|numerical>”


The vizor groups all children widget elements and lays them out horizontally on a virtual cylinder around the user. It does so by centering all widgets along the resting position (negative z-axis) and stretching widgets out vertically so they fill up the height of the vizor. It currently is the only available layout system for the hub. You can set the following attributes:

height=”<number>” (optional)

radius=”<number”> (optional)

spacing=”<number>” (optional)

show-brand-logo (boolean, optional)

show-home-button (boolean, optional)

no-shadow  (boolean, optional)


The grid is one of two currently available widgets. It is the main view for displaying content. It displays cover items with thumbnails and labels in a regular grid view with freely definable column and row count and can be paginated when not all items fit one page. The pagination can either be vertically or horizontally. The view can be filtered and sorted by default through setting filter and sorter attributes respectively.

As a bonus the grid can have a sorter dropdown by providing a sorter-list so the user can choose the ordering of the displayed contents. Possible attributes are:

title=”<string”> (optional)

view-id=”<string>” (optional)

width=”<number>” (optional)

rows=”<number>” (optional)

columns=”<number>” (optional)

tile-padding-x=”<number>” (optional)

tile-padding-y=”<number>” (optional)

scroll-mode=”<vertical|horizontal>” (optional)

show-no-cover-text (boolean, optional)

filter=”<filter-id>” (optional)

sorter=”<sorter-id”> (optional)

sorter-list=”<array>” (optional)


The menu widget displays <dl8-menu-item> child elements in a vertical scrolling list. Menu items just like hub groups can trigger multiple actions (filter actions, sorter actions, etc.). The menu has the following attributes:

title=”<string”> (optional)

view-id=”<string”> (optional)

width=”<number”> (optional)

rows=”<string”> (optional)

default-menu-item=”<menu-item-id>” (optional)


A menu item is a text button that upon interaction triggers the declared child action elements. It has the following attributes:


menu-item-id=”<string”> (optional)

The Action System

As mentioned in the markup section, triggerable elements (<dl8-hub-group>, <dl8-hub-menu-item>) can declare action elements as children which are executed in order of appearance when a user interacts with these. There are different types of actions. Currently there are 3 types: filter actions, sorter actions and set-title actions. The action system is build in a way that novel actions can easily be introduced so that greater flexibility and interactivity of the hub can be achieved in the future.

The currently supported actions are:


This action triggers a filter on a specified view




This action triggers a sort operation on a specified view




This action triggers a title change on a specified view




Three column layout

This is a full blown hub with a menu, a full size grid and a small side grid for special items

 <dl8-hub title="Three Columns" room-src="room.jpg" room-format="STEREO_360_TB">

  <!-- Contents -->
    <dl8-model poster="model-poster.jpg" title="Model" src="model.json">
      <dl8-model-ibl src="ibl/forest.json"></dl8-model-ibl>
      <dl8-model-skybox src="forest.png"></dl8-model-skybox>
    <dl8-video title="Video 1" poster="video-poster1.jpg" format="MONO_360" x-dl8-attr-category="Music" x-dl8-attr-rating="1.0" x-dl8-attr-ts="123456778">
      <source src="video1.mp4" type="video/mp4" />
    <dl8-video title="Video 2" poster="video-poster2.jpg" format="STEREO_360_TB" x-dl8-attr-category="Animation" x-dl8-attr-rating="4.0" x-dl8-attr-ts="123456782">
      <source src="video2.mp4" type="video/mp4" />
    <dl8-video title="Video 3" poster="video-poster3.jpg" format="STEREO_360_TB" x-dl8-attr-category="Animation" x-dl8-attr-rating="4.0" x-dl8-attr-ts="123456782">
      <source src="video3.mp4" type="video/mp4" />
    <dl8-img title="Photo 1" poster="photo-poster1.jpg" src="photo1.vr.jpg" format="CARDBOARD_PHOTO" x-dl8-attr-category="Photo" x-dl8-attr-tags="[ 'sunny', 'cloudy' ]" x-dl8-attr-rating="8.0" x-dl8-attr-ts="123456781">
    <dl8-img title="Photo 2" poster="photo-poster2.jpg" src="photo2.jpg" format="MONO_360" x-dl8-attr-category="Photo" x-dl8-attr-tags="[ 'sunny', 'cloudy' ]" x-dl8-attr-rating="8.0" x-dl8-attr-ts="1234567811">
    <dl8-hub-group title="Music" poster="music-cover.jpg">
      <dl8-hub-action-filter for="main" filter="categoryMusicFilter"></dl8-hub-action-filter>
      <dl8-hub-action-set-title for="main" value="Category: Music"></dl8-hub-action-set-title>
    <dl8-hub-group title="Photo" poster="photo-cover.jpg">
      <dl8-hub-action-filter for="main" filter="categoryPhotoFilter"></dl8-hub-action-filter>
      <dl8-hub-action-set-title for="main" value="Category: Photo"></dl8-hub-action-set-title>
    <dl8-hub-group title="Animation" poster="animation-cover.jpg">
      <dl8-hub-action-filter for="main" filter="categoryAnimationFilter"></dl8-hub-action-filter>
      <dl8-hub-action-set-title for="main" value="Category: Animation"></dl8-hub-action-set-title>
    <dl8-hub-group title="All Groups" poster="all-cover.jpg">
      <dl8-hub-action-filter for="main" filter="groupFilter"></dl8-hub-action-filter>
      <dl8-hub-action-set-title for="main" value="Category: All Groups"></dl8-hub-action-set-title>

  <!-- Visual Representation -->
  <dl8-hub-vizor brand-src="brand-logo.jpg" height="0.7" spacing="0.05">
    <dl8-hub-menu width="0.3" title="Home" default-menu-item="all" rows="10">
      <dl8-hub-menu-item menu-item-id="all" title="All">
        <dl8-hub-action-filter for="main" filter="allContentFilter"></dl8-hub-action-filter>
        <dl8-hub-action-set-title for="main" value="All"></dl8-hub-action-set-title>
      <dl8-hub-menu-item title="Videos">
        <dl8-hub-action-filter for="main" filter="videosFilter"></dl8-hub-action-filter>
        <dl8-hub-action-set-title for="main" value="Videos"></dl8-hub-action-set-title>
      <dl8-hub-menu-item title="Images">
        <dl8-hub-action-filter for="main" filter="imagesFilter"></dl8-hub-action-filter>
        <dl8-hub-action-set-title for="main" value="Images"></dl8-hub-action-set-title>
      <dl8-hub-menu-item title="Music">
        <dl8-hub-action-filter for="main" filter="categoryMusicFilter"></dl8-hub-action-filter>
        <dl8-hub-action-set-title for="main" value="Music"></dl8-hub-action-set-title>
      <dl8-hub-menu-item title="Best Images">
        <dl8-hub-action-filter for="mostRecentList" filter="imagesFilter"></dl8-hub-action-filter>
        <dl8-hub-action-set-title for="mostRecentList" value="Best Images"></dl8-hub-action-set-title>
      <dl8-hub-menu-item title="Best Videos">
        <dl8-hub-action-filter for="mostRecentList" filter="videosFilter"></dl8-hub-action-filter>
        <dl8-hub-action-sort for="mostRecentList" sorter="timestampSorter"></dl8-hub-action-sort>
        <dl8-hub-action-set-title for="mostRecentList" value="Best Videos"></dl8-hub-action-set-title>
      <dl8-hub-menu-item title="Popular Videos">
        <dl8-hub-action-filter for="mostRecentList" filter="videosFilter"></dl8-hub-action-filter>
        <dl8-hub-action-sort for="mostRecentList" sorter="ratingSorter"></dl8-hub-action-sort>
        <dl8-hub-action-sort for="mostRecentList" sorter="timestampSorter"></dl8-hub-action-sort>
        <dl8-hub-action-set-title for="mostRecentList" value="Popular Videos"></dl8-hub-action-set-title>
    <dl8-hub-grid view-id="main" width="1.0" scroll-mode="horizontal" columns="3" rows="3" title="Main" filter="allContentFilter" sorter="alphabeticalSorter"></dl8-hub-grid>
    <dl8-hub-grid view-id="mostRecentList" width="0.3" scroll-mode="vertical" columns="1" rows="3" title="Most Recent" filter="allContentFilter" sorter="timestampSorter" show-no-cover-text></dl8-hub-grid>

  <!-- Filters -->
    <dl8-hub-filter-attr-is filter-id="categoryMusicFilter" attr="x-dl8-attr-category" value="Music"></dl8-hub-filter-attr-is>
    <dl8-hub-filter-attr-is filter-id="categoryAnimationFilter" attr="x-dl8-attr-category" value="Animation"></dl8-hub-filter-attr-is>
    <dl8-hub-filter-attr-is filter-id="categoryPhotoFilter" attr="x-dl8-attr-category" value="Photo"></dl8-hub-filter-attr-is>
    <dl8-hub-filter-attr-contains-one-of filter-id="tagFilterCloudyOr" attr="x-dl8-attr-tags" value="[ 'cloudy', 'sunny' ]"></dl8-hub-filter-attr-contains-one-of>
    <dl8-hub-filter-attr-contains-all-of filter-id="tagFilterCloudyAnd" attr="x-dl8-attr-tags" value="[ 'cloudy', 'sunny' ]"></dl8-hub-filter-attr-contains-all-of>
    <dl8-hub-filter-attr-contains filter-id="tagFilterSunny" attr="x-dl8-attr-tags" value="sunny"></dl8-hub-filter-attr-contains>
    <dl8-hub-filter-element-is filter-id="videosFilter" value="dl8-video"></dl8-hub-filter-element-is>
    <dl8-hub-filter-element-is filter-id="imagesFilter" value="dl8-img"></dl8-hub-filter-element-is>
    <dl8-hub-filter-element-is-one-of filter-id="groupFilter" value="[ 'dl8-hub-group' ]"></dl8-hub-filter-element-is-one-of>
    <dl8-hub-filter-element-is-one-of filter-id="allContentFilter" value="[ 'dl8-model', 'dl8-img', 'dl8-video', 'dl8-hub-group' ]"></dl8-hub-filter-element-is-one-of>
    <dl8-hub-filter-element-is filter-id="menuFilter" value="dl8-hub-menu-filter"></dl8-hub-filter-element-is>

  <!-- Sorter -->
    <dl8-hub-sorter-attr sorter-id="alphabeticalSorter" attr="title" type="lexicographical" order="ascending"></dl8-hub-sorter-attr>
    <dl8-hub-sorter-attr sorter-id="ratingSorter" attr="x-dl8-attr-rating" type="numerical" order="descending"></dl8-hub-sorter-attr>
    <dl8-hub-sorter-attr sorter-id="timestampSorter" attr="x-dl8-attr-ts" type="numerical" order="descending"></dl8-hub-sorter-attr>
    <dl8-hub-sorter-attr sorter-id="timestampSorter2" attr="x-dl8-attr-ts" type="numerical" order="ascending"></dl8-hub-sorter-attr>