Native Element Content Routing, with Rule-Based Guided Navigation for the Next-Gen Experience Web

Jason Wicker
view.DO
Published in
10 min readNov 13, 2020

view.DO is building the first experience-based view engine to power the next evolution of the Web

Hi. I’m Jason. I’m the CTO and Co-Founder of view.DO. We created a Digital Experience Platform and UI Framework for building, hosting, and delivering millions of personalized experience sites our clients use to engage their customers in a deeply personal, yet wildly scalable format.

This is my first post, in a series of posts I’ll write to document our journey in creating our next-gen toolset.

First, the difference between web sites and web experiences

Websites use a “here-is-everything” approach to deliver content with user-selected navigation. Navigation is usually organized, but it relies on users to explore on their own, with a wide variety of navigation options, sprinkled everywhere. Links are in the content, the header, menus, and the footer.

To drive behavior, marketers must rely on the design. They use flashy calls to actions, banners, hero content, or highlighted click-targets, hoping the user isn’t distracted by the noise of choice.

Conversely, web experiences use a presentation approach to deliver content. They drive behavior in a bite-sized, step-by-step format. Navigation is simple and usually presented as a choice or a simple ‘Next’ button. Next button navigation is often dynamic and could be rule-driven based on collected data.

In short, experiences drive a user towards specific goals with less noise of choice. The navigation burden is shifted from the user to the publisher, but it doesn’t have to be hard.

Humans are curious and compelled to see what’s next but are easily overwhelmed by too many options.

That said, we here at view.DO are working on our next-gen tools for rapidly creating web experiences for ourselves and our clients. We have a published set of tools that have helped us create personalized experiences in days, not months, but those tools have grown old and newer technologies offer better options for even faster rendering and easier production.

We decided to abandon heavy frameworks and opted instead to use native Web Components built with StencilJs for ultra-light, lazy loadable components that render lightning-fast.

A Declarative Content Router for the Web 5.0

Our first task was creating an in-page content router, specifically designed for the experiential web. We know there are many Web Component Routers out there, but none we found could be used without some level of scripting, build process, or template engine.

Web 5.0 will focus on the individual, perhaps allowing a website to convey a different experience for each different person. It could perceive the emotions of an individual and respond appropriately, and it could detect subtleties that enable more powerful interactions. — source

We also want to express conditional guided-navigation along with user-selectable navigation. As you can imagine, there are ZERO existing routers that support guided-navigation. (We aren’t surprised, there doesn’t seem to be many tools for the experience-web and it’s why we had to create them.)

We also want to express our navigation rules with variable data and we wanted to do it ALL without requiring a line of code from our production team.

This approach could give us templates that work without a build process.
Raw HTML should be enough. Here we go.

High-Level Requirements:

  • In-Page Routing w/support for: Hash or Path Routes
  • Support Animated Transitions
  • Support Guided Navigation (Next)
  • Support Data-Driven & Conditioned Rules for Next

Guided Navigation

Experience content is presentational in nature. Content is concise with minimal noise and tends to have an implicit ending; a selection was made, the form was completed, the video ended or the Next button was clicked.

Static content has no end and leaves the user to decide what to do.

Experiences also have static content, but it is typically found at the end or as an informational page offshoot. In most cases, revisiting a completed experience shows this static page, with links to revisit the presentation, rewatch a video, or update data that was collected.

As such, experience navigation can be categorized into two distinct types: static and guided. Static is simple. The link doesn’t change, we don’t need to solve for that. Guided navigation takes the user to the next route of content in a stateful sequence with regard to certain conditions and data-rules.

Guided navigation gets complex. Expressing rules for where to go next can quickly get unruly, especially considering that a user can refresh the page, drop out of an experience, and return at any time.

A Simple Guided Navigation Strategy

Let’s take what we know about experiences and make some assumptions to simplify a guided-navigation strategy for determining the hard part: what should come next.

Let’s assume all experiences end with static content at a base-route and navigation within this content is static.

Path: /home
Content: static html
Navigation: static

Let’s also assume presentation content must live as sub-routes under their declaring static route. Let’s say these special sub-routes are declared ‘must-visit’ with some kind of declaration and the Next button always returns to the base-route.

Presentation content could use both static and guided navigation.

Path: /home/step-1
Content: presentational
Navigation: guided

Path: /home/step-2
Content: presentational
Navigation: guided

Path: /home/step-3
Content: presentational
Navigation: guided

Path: /home
Content: static html
Navigation: static

With these assumptions, our navigation system can make a global rule: before we present our base-route content, the user must first visit each of the sub-routes defined as ‘must-visit’ in the order they are declared.

Add State & Visit Tracking

With the above rules, each visitor to the static base-route would progress through the sub-routes before reaching the static content. This is a step in the right direction, but the state doesn’t survive refreshes or revisits. Let’s fix that.

Let’s now say our base-route holds state. If we held a list of must-visit sub-routes, our strategy becomes:

Base Route Rules:
1: Create or retrieve an ordered list of ‘must-visit’ sub-routes from the state.
2: If the list has any items:
True: Pop the first item from the list and navigate to the route
False: Remain on the base route and display the content
3: Store the state

Each sub-route displays its content and returns to the sub-route when the presentation ends or the user clicks ‘Next’. The base-route re-evaluates the same rule-set until all sub-routes are visited.

With this simple construct, we could return a user to the last place they were in the sequence without much heavy lifting. With a persistent state store, we could restore abandoned and completed experiences, back to their state when revisiting.

A little better, but now let’s solve for sub-routes that should only be visited under certain conditions and sub-routes that should always be visited on re-visits.

Add Support for Optional Visit Tracking

The simple strategy above declares all sub-routes as must-visit, but there are instances where the requirement can differ.

Let’s add a new property to our declared sub-routes that describes the visit requirements.

Expand the ‘visit’ requirement to: once, always, and optional.

once: This is the tracking we discussed earlier where the base-route ensures the sub-route is visited at least once before showing its contents. The visit is tracked in a persistent store, so revisits don’t require a revisit.

always: This tells the navigation system to revisit this sub-route every time a user revisits the base-route in this experience. Visit-tracking is in session only.

optional: This tells the navigation system to ignore this sub-route and not include it in the must-visit list. This sub-route exists for static navigation.

Let’s take a look at our route definition and include this new property.

This changes our navigation rules to be:

Base Route Rules:
1: If a must-visit list exists in the store:
True: Retrieve the list and insert any ‘always’ routes.
False: Creating the list excluding ‘optional’ items.
2: If the list has any items:
True: Pop the first item from the list and navigate to the route
False: Remain on the base route and display the content
3: Persist the state

Path: /home/welcome
Visit: always

Path: /home/choose-favorite // choice links to red or blue
Visit: once

Path: /home/choice-red
Visit: optional

Path: /home/choice-blue
Visit: optional

Path: /home

In this example, the sub-route “choose-favorite” displays a list of choices, each with their own direct links to their corresponding optional sub-routes.
If Next() was clicked, the user would just return home.

Now we have sub-routes that won’t automatically be visited if they are optional. They will only be visited by explicit, static navigation.

Add Support for Data-Driven Conditions

The above strategies account for most experience navigation rules, but we are still missing the ability to have navigation conditioned on existing data, collected in previous steps or part of the starting data set.

Let’s first assume the data context is established and there is a persistent data store providing data to the view engine. We also must assume an expression-evaluator exists (it does) that can evaluate expressions to resolve a boolean result.

We are going to add a new ‘visit’ type, named ‘when’. This ‘visit’ type informs the navigation system to derive the actual ‘visit’ type from the expression contained in the ‘when’ property expression. This expression evaluates to true or false and can contain complex predicates that combine data from different sources.

True: the ‘visit’ type is ‘once
False: the ‘visit’ type is ‘optional

Our path expressions could then remain declarative and still provide the comprehensive data-driven navigation, most experiences need.

This method gives allows us to use a terse declaration for complex navigation rules. If you were wondering, we are also building an expression evaluator to evaluate values from any configurable data store — and NO, we aren’t using eval() ;)

Example: Sub Route Required when data equals a value

Path: /home/choice-red
When: color = red

Example: Sub Route Required when data is set

Path: /home/answer-exists
When: answer != null

Example: Sub Route Required with multiple conditions

Path: /home/complex-example
When: (answer != null && age > 21) || verified = false

Building on these assumptions, conventions, and two properties the view engine can construct a robust data-driven rule-based system for guided navigation without a complex rule-engine. The expression evaluation is the most complex aspect of this engine, but this is established at the sub-route — and all routes have no context of navigating anywhere based on rules.

Now let’s move out of theory any into our new HTML Elements.

The Views and View-Dos

In our theorized examples above, we discussed routes and specialized sub-routes. With our view-engine elements, these routes are expressed using <x-view> elements and their specialized sub-routes and navigation rules are expressed with child <x-view-do> elements.

The default ‘visit’ requirement is ‘once’, so it can be omitted for normal behavior. Also, if the ‘when’ property exists, the ‘visit’ requirement is always derived from that expression, so ‘visit’ can be omitted in these cases too. This leaves very few occasions that we need to include the ‘visit’ property.

Views can also contain other Views (routes have sub-routes, and they can have sub-routes, etc), each with their own View-Dos, or specialized sub-routes. This construct can be expanded to create a very deep rule-based navigation system, complete with presentations in a guided navigation system, built-in.

These tags will be explained in detail within their own component documentation, but here is an example structure in HTML:

Contrived Example:
Note: real content HTML is omitted for brevity.

<x-ui>
<x-view path="/"
page-title="Home">
<x-view-do path="/welcome"
visit="always"
type="html">
</x-view-do>
<x-view-do path="/get-name"
page-title="What's your name?"
when="name == null"
type="input">
...
</x-view-do>
<x-view-do path="/hi-name" |
page-title="Hello!"
type="html">
<h2>Hello <x-display value='name'/></h2>
<p><a href="/video-intro">Check out our video</a></p>
</x-view-do>
<x-view-do path="/video-intro"
visit="optional"
type="video">
</x-view-do>
<x-view path="/about"
page-title="About Us">
<x-view-do path="/meet-mike"
page-title="About Mike"
type="html">
</x-view-do>
<x-view-do path="/meet-paul"
page-title="About Paul"
type="html">
</x-view-do>
<x-view-do path="/meet-jason"
page-title="About Jason"
type="html">
</x-view-do>
<x-do path="/meet-max"
page-title="About Max"
type="html">
</x-view-do>
<p><a href="/about">About us</a></p>
...
</x-view>
</x-view>
</x-ui>

This structure informs the navigation system:

When landing at route ‘/’, first visit the ‘/welcome’ sub-route.

Next, visit the ‘/get-name’ sub-route, where we collect a name.

Next, visit the ‘/hi-name’ sub-route that holds a personalized greeting message. This page offers a link to our intro video.

Finally, go to the ‘/’base- route with our static home content.

Here there is a link to a sub-route ‘/about’ that first walks the user through a presentation of each person leading the company that ends with static content about the company.

With this approach, our production team could express rule-based guided navigation schemes in no-time, allowing our personalized experience sites to do what they do best, engage users in an interactive, personal way without breaking the bank.

Watch this space for an announcement once these components go live. I believe as the web evolves to be more personalized, more interactive, and less noisy, the tools we create will help publishers propel their content into the next-gen Web.

Now back to coding.

--

--

Jason Wicker
view.DO
Editor for

I’m Jason Wicker, CTO and Co-Founder of view.DO — creators of the first web-experience marketing platform for personalized engagements at scale.