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
Sircl App Identifier
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 a default target that will be used as inline target when no explicit target is set. This is done by marking the default inline target with the sircl-appid
attribute.
When operating in Single Page mode, there should always be exactly one element with the sircl-appid
attribute. And it typically stays on that same element.
When using a template engine on the server to generate pages, the sircl-appid
attribute 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 [sircl-appid]
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 sircl-appid="MyApp">
@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@html.block('body')
when using Vash layout pages in Node.js<?=$this->section('content')?>
for Plates templates in PHP{{ $slot }}
or@yield('content')
for Blade Templates in Laravel{% block content %}{% endblock %}
for Django templates- ...
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 sircl-appid
attribute, 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.
Sircl App Identifer value
The value given to the sircl-appid
attribute is yours to choose, but it should reflect the application identity and not conflict with other applications. When using Sircl in Single Page mode, the risk exists that a hyperlink to another Sircl application ends up in the content of the other application being embedded in the template of the first application. It is therefore important to uniquely identify the applications.
When Sircl issues an AJAX request to retrieve the main body part in a Single Page situation, it will check the response for an X-Sircl-AppId
header. If this header is provided, and the value matches the value of the sircl-appid
attribute, then the page will remain in place and only the content of the default target will be replaced. If the application identifers differ, the web request will be re-issued by the browser and result in a full page load.
Note that you can also use the application identifier as a way to indicate a context switch requiring a full page reload. For instance, make an application identifer by concatenating the application name with the current language and a (public) identifier of the currently logged in user, and whenever the user changes language or logs in or logs out, the application identifier will not match and a full page reload will be issued.
Sircl App Mode
When a response to a main body page part request does not return an X-Sircl-AppId
header, the content will be accepted by default and the page remains in place (with updated default target content). You can choose to consider the absence of the X-Sircl-AppId
header as an indication that the response should not be loaded in the default target by setting the sircl-appmode="strict"
attribute on the default target (the element holding the sircl-appid
attribute).
Responding to Single Page requests
Responding to a Single Page part request is not very different than responding to any page part request: the server should only return the HTML code for the requested part.
Identification of the request type (full or partial) is done by checking the X-Sircl-Request-Type
request header.
In addition, the server should respond with an X-Sircl-AppId
response header containing the value it expects to match with the sircl-appid
attribute. It can safely return this response header with any page part response (not only responses relating to the default inline target).
An example
Taking back the above example of a page with two hyperlinks, to access the customers page, there are now two possibilities:
- The user clicks the hyperlink "Customers"
- 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 default target will be assumed, and Sircl will issue an Ajax request to retrieve just the content of the default 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 default 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")
{
Context.Response.Headers["X-Sircl-AppId"] = "MyApp"
@:@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 default 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")
{
Context.Response.Headers["X-Sircl-AppId"] == "MyApp"
<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 availableback-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 default target as well. This can be usefull when you want to maintain the behaviour associated to updating the default 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 default target.
Navigation and history
In Single Page mode, any GET request made to update the default target of the page, will also update the browsers URL with the URL of the default target page part request. Therefore, the URL of the browser always reflects the content of the default 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 default 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 default 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 default 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 default 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 default target.
Through navigation, only the content of the default 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 default 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 default target, can contain content that will be moved to other locations in the page.
Suppose the page contains a breadcrumb outside the default 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 default 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 default 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
Page Scrolling
When Single Page mode is activated, Sircl takes care of proper page scrolling (automatically scrolling to the top of the page when navigating forward and returning to the previous scroll location on backward navigation.
By default, this scolling is instant. You can change this into "smooth" scrolling mode by setting the sircl.scrollMode property:
<script>
sircl.scrollMode = "smooth";
</script>
If you want to disable the the Sircl code for backward navigation scrollling and restore the Browser's default behavior, set the browser's history scrollRestoration mode to "auto" in the pageinit handler:
<script>
$$("pageinit", function () {
history.scrollRestoration = "auto";
});
</script>
Main target
In older version of Sircl, the sircl-appid
attribute did not exist and Single Page mode was obtained by marking the default target with the main-target
class. This still works but should only be used if there is no risk of interfering with other Sircl applications.