Single Page mode

In Single Page mode, Sircl supports Single Page Applications (SPA's). The application still relies on server-side rendering, but renders in a single page. The browser is not required to perform full page loads. Deep linking and back and forth browser navigation remain supported.

Introduction

A SPA or Single Page Application is a website or web application where only the first access requires a full page load. Subsequent page requests only update part(s) of the page to give the user the impression he/she navigates to a new page, while the browser does not need to perform a full page initialization resulting in faster and more fluent transitions, and allowing the page to retain context over requests.

The above description is supported by Sircl in both Multi Page and Single Page modes.

But a true SPA also influences the URL of the browser, assigning each virtual page a distinct URL thereby also providing support for deep linking: the ability where the user can click on an external link (in an email for instance), or enter a URL manually in the browser, to get immediate access to a specific page and state in the application.

And this is exactly the capability that Sircl offers in Single Page mode.

Setting up Single Page mode

Main target

Remember, in Multi Page mode, every hyperlink or form submission that did not explicitely set an (inline) target, is processed by the browser as a regular request and results in a full page load.

In Single Page mode, this is not true anymore. Any link or form that has no explicit target, is by default processed as a page part request!

To shift to Single Page mode, you therefore need to define what the inline target will be when no explicit target is set. And that is done by marking an element with the main-target class.

When operating in Single Page mode, there should always be exactly one element with the main-target class. And it typically stays on that same element.

When using a template engine on the server to generate pages, the main-target class is to be placed on the element directly surrounding the content of individual pages.

Consider the following template or "layout" page as it is called in some languages. It has a .main-target element surrounding the placeholder for page content:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Sircl Single Page mode</title>
  <link href="/libs/sircl-2.0/sircl-bundled.min.css" rel="stylesheet" />
</head>
<body>
  <div class="main-target">
    @RenderBody
  </div>
  <script src="/libs/jquery-3.6/jquery-3.6.0.min.js"></script>
  <script src="/libs/sircl-2.0/sircl-bundled.min.js"></script>
</body>
</html>

As the above code represents a template, @RenderBody is a place holder for page content. Depending on your serverside framework, you would write:

  • @RenderBody if you are using Razor templates in ASP.NET, as the examples here
  • <?=$this->section('content')?> if you are using Plates templates in PHP
  • @html.block('body') using Vash layout pages in Node.js
  • ...

A first page of your Single Page Application could consist of a "menu" with two options coded as hyperlinks:

<a href="/Customers">Customers</a>
<a href="/Suppliers">Suppliers</a>

This would be a full page to be rendered with the above template. So the whole content sent to the browser when accessing this page, is the above template in which @RenderBody is replaced by the page content (the two hyperlinks).

Since the Sircl core library (sircl.js) is loaded (as it is part of the sircl-bundled.min.js), and since the page has an element with the main-target class, Sircl is switching to Single Page mode.

If you would open the console of the developer tools of your browser, you would see the following output when the page is loaded:

Sircl v.2 running.
sircl.singlePageMode = true

The second line is an indication that Sircl switched to Single Page mode.

Full or partial response

To access the customers page, there are now two possibilities:

  1. The user clicks the hyperlink "Customers"
  2. The user enters the full URL in the browser, for instance https://www.yoursite.com/Customers

In the second case, when the user enters the full URL, but also when the user clicks a link from outisde your site (i.e. from another site or from an email), a full page request will be performed by the browser. The server receives a request to render the /Customers page and needs to respond with a full page (the template, in which the content place holder is replaced with the proper content). This is the default behaviour of your template engine on the server.

The first case is different: the user clicks the hyperlink within the page where Sircl is active and running in Single Page mode. As the hyperlink has no target, the main target will be assumed, and Sircl will issue an Ajax request to retrieve just the content of the main target element!

Now, your server also receives a request to render the /Customers page, but this time it is an Ajax request and the server should only respond with the content of the main target, not the full page anymore.

When the server received a request, it will have to determine if it is a (non-Ajax) full page request, or a (Ajax) page part request. 

To this goal, Sircl adds a request header X-Sircl-Request-Type with value Partial on every request for a page part.

The server can check this header to find out what to return.

By putting the following code (ASP.NET Razor syntax) in front of the above template, the server would still always render the template, but when a page part request is issued, the full reponse content would only consist of the requested page part:

@if (this.Context.Request.Headers["X-Sircl-Request-Type"] == "Partial")
{
    @:@RenderBody()
    return;
}

(In ASP.NET you could also use the _ViewStart.cshtml file to distinct full from partial page requests.)

Though a page part request is issued when clicking the Customers hyperlink, which issues a hyperlink to retrieve only that part of the page that fits in the main target, the URL of the browser is updated, and the user can press the back button of the browser to return to the previous page. Your application is now a SPA, as it is updated by Ajax requests, but browser navigation is fully operational.

Fixing cached back navigation

Some browsers cache the server response and try to render that when the user navigates back in the browser, forgetting that this was an Ajax request to be handled by Sircl. As a result, such browsers may render only the content of the page without the surroundings of the template.

To fix this, inject a small piece of Javascript when rendering a page part, that checks whether Sircl is loaded, and if not, performs a full page reload:

@if (this.Context.Request.Headers["X-Sircl-Request-Type"] == "Partial")
{
    <script>if (typeof sircl === "undefined") { location.reload(); }</script>
    @:@RenderBody()
    return;
}

Now, when the browser renders a page part, that Javascript line is executed. If Sircl is loaded, it means the whole template is still there, which means Sircl has injected the page part correctly. If Sircl is not loaded, it means there is no template anymore. The script will then reload the whole page using a regular (non-Ajax) request, which will trigger the server to return the whole content (template included).

Redirections & Back navigation

As explained in the section Partial Loading, redirections require a slight difference in code to work with partial page requests, be it in Single Page or Multi-Page mode. The reason is that standard redirects (using HTTP status codes 3xx) are handled by the browser and are invisible to Sircl. Therefore Sircl is not aware of the redirect and can not correctly update the page URL.

To instruct a redirect that can be handled by Sircl, return a regular status code 200 or 204, and include a Location header with the URL of the new location to retrieve.

Location: /AnotherPagePart

In addition, with Sircl, the server can also instruct the browser to navigate back using the X-Sircl-History response header with either of these values:

  • back : return back and use cached data of the page if available
  • back-uncached : return back but without using any cached data
X-Sircl-History: back

The back navigation instruction is also typically sent with a 200 or 204 status code.

Sub-targets

Sub-targets, as discussed in the section Partial Loading, can be used in combination with the main target as well. This can be usefull when you want to maintain the behaviour associated to updating the main target (which is to update the browsers URL and pushing a line on the browsers history stack), while only changing a part of the content of the main target.

Navigation and history

In Single Page mode, any GET request made to update the main target of the page, will also update the browsers URL with the URL of the main target page part request. Therefore, the URL of the browser always reflects the content of the main target element.

Navigating back and forth in the browser (using the back and forward browser buttons) is determined: the URL in the history is to be used to perform a page part request to render the main target element.

The behaviour of the browser history can be controlled using the history attribute on the trigger of the request (i.e. the hyperlink or the button used to submit the form), or, if a form is submitted, on the FORM element.

Push on history

"push" is the default history behaviour. It means a new line is pushed on the history stack of the browser:

<a href="/Item/A/Details" history="push">Details</a>

If you do not mention a history attribute, "push" is its default value.

Skipping history

You can instruct the browser not to update the URL in the navigation bar and not to push a new line on the history stack by using the "skip" value for the history attribute:

<a href="/TabAddress" history="skip">Address</a>

This can for instance be used to simulate tabs on a page for which you want to expose a single URL.

Replacing history

You can also choose to "replace" the history, which means the URL in the navigation bar of the browser is updated, but no new line is pushed on the history stack of the browser.

In the following example, a paged list is rendered:

<form class="onchange-submit" method="get" history="replace">
  <p><a href="/Item/A">Item A</a></p>
  <p><a href="/Item/B">Item B</a></p>
  <p>...</p>
  <nav>
    <input type="radio" name="page" value="1" checked> Page 1
    <input type="radio" name="page" value="2"> Page 2
  </nav>
</form>

Clicking Item A or Item B triggers the hyperlink and will push a new entry in the browsers history stack.

Selecting Page 2 however, will submit the form (as the onchange-submit class on the form will automatically submit the form when one of its fields changes), and since the form has a history attribute with value "replace", the URL of the browser will be updated (include the "?page=2" query) but no additional history entry will be created. When the user later presses the browsers back button, the browser will not render page 1 but will return to the page preceding this page.

History replacement can also be directed by the server, by setting the X-Sircl-History-Replace response header, in which case the server can determine which URL should be rendered:

X-Sircl-History-Replace: /AnotherUrl

Caching history entries

When navigating away from a page, and then back to it using the browsers back button, often a cached representation of the previous page is rendered.

Sircl Ajax requests are however by default not cached. Therefore, navigating back to a page part loaded by Sircl, will issue a new request to the server to ensure up to date data.

In Single Page mode, you can explicitely request Sircl to cache the current page part of the main target when navigating away. That way, when navigating back, the cached content can be rendered.

The request to cache is made by adding "cache" to the history attribute value on the link, trigger or form that triggers leaving the current page:

<a href="/Item/A" history="push cache">Item A</a>

Back navigation can be performed by pressing the browsers back button, or by clicking a hyperlink (element with a href attribute) or element with an onclick-load attribute, with the special value "history:back":

<a href="history:back">Back</a>

With the "history:back-uncached" value, you can order the browser to navigate back and reissue the previous request even if it was previously cached:

<a href="history:back-uncached">Back</a>

Server directed navigation

In response to requests, the server can send navigation instructions back to the browser using the X-Sircl-History response header.

Server directed navigation instructions are only processed on page part requests issued by Sircl, not by requests issued by the browser. Any content the response would contain, will be ignored.

Navigating back

The server can instruct the browser to perform back navigation by returning the following response header:

X-Sircl-History: back

Or:

X-Sircl-History: back-uncached

In this second case, if the previous page was cached, it's cached view will be ignored and the request will be reprocessed by the server.

Reloading the current page

To reload (refresh) the current page, the server can return following response header:

X-Sircl-History: reload

Note that in Single Page mode, this will not force a full page request but only reload the content of the main target by issuing a page part request.
To reload the full page in Single Page mode, also pass the X-Sircl-Target: _self header.

Updating content outside the main target

When building Single Page Applications with Sircl in Single Page mode, you often have navigation items (links, menus, buttons) residing in the template page, outside the main target.

Through navigation, only the content of the main target is replaced. What if you want to highlight the menu that belongs to the active route, or otherwise want to update content outside the main target ?

Highlighting active route Bootstrap

The ifroute-setactive attribute takes a regular expression that is matched on the current browsers URL. If the regular expression matches the current URL, the decorated element gets the active class, otherwise the active class is removed.

This attribute is typically used to highlight menu items in a navigation bar when they represent access to the current page (current URL). For instance: 

<nav>
  <ul>
    <li ifroute-setactive="\/Customers(\/|$)">
      <a href="/Customers">Customers</a>
    </li>
    <li ifroute-setactive="\/Suppliers(\/|$)">
      <a href="/Suppliers">Suppliers</a>
    </li>
  </ul>
</nav>

In the case of the first instance of the ifroute-setactive attribute, an URL is matched when it contains "/Customers/" or ends on "/Customers".

Note that when matching URLs, this attribute does only consider the pathname of the URL. Protocol, hostname, port, query and hash are ignored. For instance, in the following URL, only the underlined part is considered in the matching:

https://www.example.com:80/Catalog/Products/15?view=detail#specs

The ifroute-setactive attribute is a Bootstrap extension and is only available when the sircl-bootstrap library is loaded.

Combining partial page loads

By combining partial page loads, the page part loaded in the main target, can contain content that will be moved to other locations in the page.

Suppose the page contains a breadcrumb outside the main target. When loading a page part, that part can update the breadcrumb by providing its new content inside an element with the onload-moveto attribute. The page part to be loaded in the main target would then for instance be:

<div onload-moveto="#breadcrumb" document-title="Magazines - CSS Expert" hidden>
  Home / Magazines / CSS Expert
</div>
<h1>CSS Expert</h1>
<p>CSS Expert is a magazine for CSS experts...</p>

When this page part is loaded (in the main target, or any other target), the content of the DIV element is moved to an element with id "breadcrumb", wherever on the page that element is.

Updating the document title

If the loaded page part contains an element with the document-title attribute, as is the case in the above sample "Combining partial page loads", the value of that attribute is used as title for the current document and is typically shown in the browsers title bar.

Alternatively, the server can provide a new document title using the X-Sircl-Document-Title response header as in:

X-Sircl-Document-Title: Magazines - CSS Expert

Combining SPAs

It is possible to combine several Single Page Applications (each with their own distinct master template) within a single web host. Any hyperlink allowing the user to navigate from one application to the other, should have a target="_self" attribute to ensure a full page request is performed and the correct template is loaded.

An alternative approach is to have page parts themselves verify they are loaded in the correct template. Therefore the templates must be identifyable, and each page part must have a line of code to assert the right template is present.

To make templates idenfyable, you can use a script that sets a appId or other variable to a distinct value (i.e. a GUID or a distinct application name):

<script> var appId = "MyApplication51"; </script>

Each part would then verify it is loaded in the correct template:

<script> if (typeof appId === "undefined" || appId != "MyApplication51") { location.reload(); } </script>

This script invokes a full reload of the page if no appId variable is defined, or if it does not contain the expected value.

 

Loading...