Extensibility

Sircl is build as a library, with extensibility in mind. This section describes the various ways you can extend and alter Sircls behavior.

Introduction

Sircl consists of an Ajax page part loader as well as of an extensive library of behaviours known as event-actions, action-events and change-actions.

As a library, it contains a large set of common behaviours to which you may want to add your own behaviours. Sircl v2 is therefore designed with extensibility in mind, to allow you to add behaviours specific to your application in such a way that they operate identical to native Sircl behaviours.

Coding in Javascript

To be extensible, Sircl provides extension points. To these extension points, Javascript functions can be injected. Therefore, extending Sircl envolves writing Javascript code.

If you plan to contribute your additions to the Sircl project, you should adhere to the following coding conventions, in addition to the commonly accepted Javascript coding conventions:

  • Favor double quotes to express string constants in code and in code comments.

  • When writing CSS selectors, write element names in capital letters. Attribute names in lower case. I.e: "A[href]".

  • Variables holding, and funtions returning jQuery selection objects should have names starting with a "$" sign.

  • Variables holding, and funtions returning CSS selector strings should have names ending with a "$" sign.

  • Code must be compatible with IE11 and more recent browsers. Arrow functions are therefore not allowed.

If your code is not meant to be shared with the world, of course, you are free to apply any coding conventions you like.

Core Sircl functions

The core of Sircl contains some helper functions that can be used when extending Sircl. In some occasions, it is also possible to overwrite these functions to alter their default behavior.

sircl.ext.alert

The sircl.ext.alert(subject, message, event) function provides support for a (blocking) informational message box.

The function takes following arguments:

  • subject : element that triggered the alert request;
  • message : a string containing the text to show;
  • event : the event that triggered the confirm request. Can be null.

The default implementation calls the window.alert() method.

Example:

<button type="button" class="onclick-warn">Click me</button>
<script>
$(function() {
  $(document).on("click", ".onclick-warn", function(event) {
    sircl.ext.alert($(this), "This element has been clicked !", event, true);
  });
});
</script>

You can also overwrite the sircl.ext.alert() function so that it would for instance use the SweetAlert2 component.

sircl.ext.confirm

The sircl.ext.confirm(subject, message, event) function provides support for a blocking message box with OK and Cancel buttons.

The function takes following arguments:

  • subject : element that triggered the confirm request;
  • message : a string containing the text to show;
  • event : the event that triggered the confirm request. Can be null.

The default implementation calls the window.confirm() method.

sircl.ext.effectiveValue

The sircl.ext.effectiveValue(element) function returns the effective value of a form control element.

The function takes following arguments:

  • element : the form control element to get the value of.

The returned value depends on the control type:

  • for radios and checkboxes: if checked their value, else an empty string;
  • for multivalue selects, an array of selected values;
  • for all other controls, their actual value.

sircl.ext.enabled

The sircl.ext.enabled(elementOrJqObject, [enabled]) function returns whether an element is enabled, or sets the element state to enabled or disabled.

The function takes following arguments:

  • elementOrJqObject : an element or jQuery object refering to one or more elements;
  • enabled :
    • if not given : the function returns the state of the (first) given element (true if enabled, false if disabled);
    • if true: enables the given element(s);
    • if false: disables the given element(s).

The default implementation uses the disabled property except for elements having a [href] attribute, where a .disabled class is used.

sircl.ext.getId

The sircl.ext.getId(elementOrSelector, createIdIfMissing) function returns the id value of an element. It can create an id if requested.

The function takes following arguments:

  • elementOrSelector : an element, CSS selector string (relative CSS selectors are not supported) or jQuery object;
  • createIdIfMissing : a boolean, whether an id is to be generated if the element does not have one.

sircl.ext.isInternalTarget

The sircl.ext.isInternalTarget(targetValue) function returns true if the target is to be considered internal. Returns false if the target is to be considered external, or if the targetValue parameter is null or an empty string.

The function takes following arguments:

  • targetValue : the string target value.

The inverse test can also be performed using the derived sircl.ext.isExternalTarget(targetValue) function.

sircl.ext.$mainTarget

The sircl.ext.$mainTarget() function returns a jQuery object to the main target.

The function takes no arguments.

sircl.ext.$select

The sircl.ext.$select($context, selector$) function returns a jQuery object matching the given selector within the given context. Relative CSS selectors are supported.

The function takes following arguments:

  • $context : the context in which to resolve the selector, usually a jQuery object to the element on which the selector appears;
  • selector$ : the string selector to evaluate.

sircl.ext.subtituteFields

The sircl.ext.subtituteFields(url, $source, mustHaveSubstituteFieldsClass) function performs field substitution in the given URL.

The function takes following arguments:

  • url : the URL to perform substitution in, and to return;
  • $source : the source of the substitution, usually the element holding the URL;
  • mustHaveSubstituteFieldsClass : whether to require / check if the $source element has the .substitute-fields class.

The returned value is the given URL with fields substituted.

sircl.ext.visible

The sircl.ext.visible(elementOrSelector) function returns true if the given element is visible, false otherwise.

The sircl.ext.visible(elementOrSelector, visible [,allowAnimation]) function shows or hides the given element depending on the visible argument.

The function takes following arguments:

  • elementOrSelector : an element, CSS selector string (relative CSS selectors are not supported) or jQuery object to get or set visibility for;
  • (optional) visible : boolean, if true makes the element visible, else hidden.
  • (optional) allowAnimation : boolean, if true, and element to hide or show has animate class, animate the transition.

The default implementation to show or hide an element is to use the hidden attribute.

Extending Sircl

Extending Sircl concists of adding handlers. Sircl defines various types of handlers which you can create and register so that they will be invoked by Sircl when appropriate. But regular event handers are also important.

Event handlers

Many behaviors, especially action-events and event-actions, are implemented with regular Javascript or jQuery event handlers.

Event handlers are typically installed on loading of the page. Because Sircl can load and replace parts of the page later on, either handlers must be reinstalled after loading page parts, or, and this is the best practice, events are added to the document object and rely on bubbling to get called when raised on elements of the page.

Example

In the following example we define an x-onclick-setcontent attribute that, when it's element is clicked, will replace it's elements content with the value of the attribute:

$(function() {
  $(document).on("click", "[x-onclick-setcontent]", function() {
    var newContent = $(this).attr("x-onclick-setcontent");
    $(this).text(newContent);
  });
});

We advice you to prefix your own extended event-action attributes and classes with the "x-" or another prefix to avoid possible conflicts with future versions of Sircl that could also provide support for this event-action.

The $() function is an alias to the jQuery() function, which is a function that, when it receives a callback as argument, will execute that function when the page is loaded (when the DOM content is loaded). It's argument function executes only once, when the page is loaded.

That function adds an event handler for "click" events on the document object, but with the instruction to fire only for click events originating from elements having an x-onclick-setcontent attribute (matching the "[x-onclick-setcontent]" selector).

When fired, "this" refers to the element on which the click event originated. The value of the x-onclick-setcontent attribute is retrieved and written as text (new content) of the element.

The following is an example of its use:

<p x-onclick-setcontent="The secret word is 'chocolate'.">
  Click here to unveil the secret word
</p>

The event handler is added on the document object, not on each match of the "[x-onclick-setcontent]" selector. It is therefore not needed to add or remove event handlers when page parts are loaded or replaced, even if those contain elements with the x-onclick-setcontent attribute.

Some event handlers are added to the document.body object. This is the case for event handlers that may decide not to propagate the event, such as the onclick-confirm or onchange-propagate attribute handlers.

Content Ready Handlers

Besides event handlers, Sircl adds additional handler types. One of them, and one of the most used ones, are the content ready handlers. They are invoked whenever content in a page changes as a result of calling the jQuery html(...) function.

There are different slots where content ready handlers can be registered for, depending on the phase of the following content pipeline:

As first, content ready handlers for the "before" phase are executed (except when the page initially loads), then the HTML is effectively substituted. Finally, the "content", "enrich" and "process" phase handlers are executed in that order.

When the page is initially loaded, the "content", "enrich" and "process" phase handlers are executed for the whole document.

When a page part is loaded, the "before" phase handlers are executed for the part of the document that will be replaced, and the "content", "enrich" and "process" phase handlers are executed on the new content.

"before" phase handlers can be used to perform clean-up before removing HTML. For instance, the Bootstrap extensions use this type of handler to remove tooltips and popovers.

"content" phase handlers are handlers that manipulate the new content, by for instance moving content around, performing string replace operations, 

"enrich" phase handlers can further enrich the HTML by adding classes and attributes.

"process" phase handlers before the final processing of the content.

If the new HTML content is null, the "content", "enrich" and "process" phase handlers are not executed. 

Content ready handlers are regular JavaScript functions. They take no parameter and are not expected to return a value. But they run in the context of the element receiving new content, which means "this" refers to the element receiving new content.

To register a content ready handler, call the sircl.addContentReadyHandler(phase, handler) function.

A shortcut notation to register content ready handlers is through the $$(phase, handler) function.

With this shortcut notation, when the phase is "process", the phase parameter can be left away.

Example

In Single Page mode, hyperlinks without explicit targets set, are assumed to use the main target. So in Single Page mode, the following hyperlink would cause the Google homepage to load inside the main target:

<a href="https://www.google.com/">Google</a>

Now suppose you want still to use Single Page mode, but you want to define regions where hyperlinks without explicit target attribute are not assumed to fit in the main target, but replace the whole page.

You could define yourself a default-target attribute and rule that any hyperlink inside an element with that attribute, takes as target the value of that attribute. For instance:

<div default-target="_top">
  <a href="https://www.google.com/">Google</a>
</div>

In this case, the Google link should be considered having "_top" as target and should therefore not be handled as an inline target anymore.

But for Sircl to honor this, we must add a target attribute with value "_top" on each hyperlink. We could do so using an "enrich" phase content ready handler (as the handler will add attributes to elements of the content):

$$("enrich", function() {
  $(this).find("*[default-target] A[href]:not([target])").each(function() {
    $(this).attr("target", $(this).closest("[default-target]").attr("default-target"));
  });
});

Error handling

When an exception is thrown in a content ready handler a request is made to handle the error. This request is made by envoking the error handlers. The error code is one of the following:

  • "S121" if a "before" phase handler failed
  • "S122" if a "content" phase handler failed
  • "S123" if an "enrich" phase handler failed
  • "S124" if a "process" phase handler failed

The data passed to the error handler is an object with following properties:

  • "exception": the exception that occured
  • "fx": the handler function that failed

After calling the error handlers, the remaining content ready handlers are processed.

If multiple content ready handlers fail, the error handlers can be called multiple times in a single run through the content pipeline.

Request processing handlers

Whenever Sircl issues an Ajax request to retrieve a page part (through hyperlink or form submit), a request processing pipeline is used.

In this request processing pipeline, custom handlers can be added for different phases: "beforeSend", "afterSend", "beforeRender" and "afterRender":

"beforeSend" handlers are invoked before the Ajax request is initiated and are typically used to activate spinners and other load progess visuals.

Next, the Ajax request is issed.

If the request fails - returns a HTTP status code higher than 299 - then "onError" handers are injected in the pipeline After running the onError handlers, processing goes on with the afterSend handlers.

"afterSend" handlers are invoked when the Ajax call returns, whether it succeeded or failed. It is the place to remove spinners and other load progress visuals.

If at this point the request is not successful, the pipeline processing is aborted and no more handlers are executed.

"beforeRender" handlers are invoked as next, in all cases except:

  • when the call failed;
  • when the call had the X-Sircl-History response header set to "back", "back-uncached" or "refresh";
  • when the call returned status 204 (No Content).

Then the page part returned form the server is rendered. This triggers the content ready handlers.

"afterRender" handlers are the last to be invoked.

Chaining

Request processing handlers are Javascript functions taking a request object as argument. In addition, they are responsible to call the next handler in the chain. This is done by calling this.next(req);. The following is a minimal handler that doesn't do anything:

sircl.addRequestHandler("beforeSend", function(req) {
  this.next(req);
});

Request processing handlers must call the next() method, but they do not need to call that method before returning. They can also call the method asynchronously. For instance, the following request processing handler adds a delay of 300 ms before initiating the Ajax call:

sircl.addRequestHandler("beforeSend", function(req) {
  window.setTimeout(function(processor, req) { processor.next(req); }, 300, this, req);
});

Error handling

When an exception is thrown in a processing handler a request is made to handle the error. This request is made by envoking the error handlers. The error code is one of the following:

  • "S131"

The data passed to the error handler is an object with following properties:

  • "exception": the exception that occured
  • "fx": the handler function that failed
  • "request": the request parameter passed to the handler functions

After calling the error handlers, the next() method is implicitely called to ensure the chain of calls is not broken.

Request parameter

When an Ajax request is initiated, a request object is created which is then passed to all request processing handlers.

The request object contains following properties:

  • "$trigger": a jQuery object representing the trigger of the request (the clicked link or pressed submit button), if any;
  • "$initialTarget": a jQuery object representing the (initial) target element to contain the loaded page part;
  • "$finalTarget": a jQuery object representing the final target element to contain the loaded page part;
  • "$form": in case of a form submit, a jQuery object representing the submitted form;
  • "event": in case of a form submit, the submit event;
  • "action": the called URL;
  • "method": the method used in the call (usually "get" or "post");
  • "accept": the value of the Accept request header; if null or not set, defaults to "text/html";
  • "enctype": if set, the encoding type; defaults to "application/x-www-form-urlencoded" for form submit requests;
  • "charset": if set, the charset for form submit requests;
  • "isForeground": true for user-initiated requests;
  • "targetHasChanged": whether the target was changed during the call;
  • "xhr": the XMLHttpRequest object that will be/was used for the Ajax request.

Once the Ajax request has been performed, following properties are added:

  • "succeeded": true if the Ajax call succeeded;
  • "aborted": true if the Ajax call was aborted (in which case succeeded will be false);
  • "status": the status returned by the call (the XMLHttpRequest);
  • "statusText": the status text returned by the call;
  • "responseText": the content returned by the call (the page part HTML);
  • "documentTitle": the value of the "X-Sircl-Document-Title" response header;
  • "documentLanguage": the value of the "X-Sircl-Document-Language" response header;
  • "alertMsg": the value of the "X-Sircl-Alert-Message" response header;
  • "renderMode": the value of the "X-Sircl-Render-Mode" response header;
  • "history": the value of the "X-Sircl-History" response header;
  • "historyReplace": the value of the "X-Sircl-History-Replace" response header.
  • "allResponseHeaders": list of all response headers of all requests including redirecting requests.

Handlers can add additional properties to pass on data to next handlers. They should thereby ensure that the property name is unique and is not expected to conflict with other extensions and additions. Starting the property name with a double underscore guarantees they will be no conflict with names used in Sircl or official Sircl extensions.

Example

The following example shows an errorHandler that will "recover" from the error:

sircl.addRequestHandler("onError", function(req) {
  // Recover from error, pretend succeeded to allow rendering to happen:
  req.succeeded = true;
  req.status = 200;
  req.responseText = "<p>Sorry, this part could not be loaded.";
  if (req.method == "get") {
    req.responseText += " <a href=\"" + req.action + "\" target=\"<P\">Retry</a>";
  }
  req.responseText += "</p>";
  // Chain to next handler:
  this.next(req);
});

It sets succeeded back to true so that the request appears succesful to the rest of the chain. And a new response text is injected.

Change action handlers

Change actions do not use the regular request processing handlers. Instead, they use their own request pipeline with "beforeSend", "afterSend", "onError", "beforeRender" and "afterRender" handlers.

Change action handlers are Javascript functions having a (simplified) request parameter. On the contrary of request processing handler, change action handlers are not chained: they do not need to call a next function.

Example of registering a change action handler:

sircl.addChangeActionHandler("afterSend", function(req) {
  if (!req.succeeded) alert("Change action failed.");
});

History handlers

After history handlers, the only type of history handlers currently supported, are called when the browser URL has changed.

An after history handler is registered by means of the sircl.addAfterHistoryHandler(callback) function. It's argument, callback, is the handler function. This handler function takes no arguments and returns no value.

Error handling

When an exception is thrown in an after history handler a request is made to handle the error. This request is made by envoking the error handlers. The error code is one of the following:

  • "S125"

The data passed to the error handler is an object with following properties:

  • "exception": the exception that occured
  • "fx": the handler function that failed

After calling the error handlers, the remaining after history handlers are processed.

Error handlers

An error handler is a function that takes a code, message and data argument and is registered through the sircl.addErrorHandler(callback) function.

Error handlers are called whenever an error could be intercepted.

Sircl installs a default error handler function that logs to the browsers console.

You can add additional error handlers as follows:

<script>
  sircl.addErrorHandler(function (code, message, data) {
    alert(message);
  });
</script>

More about error handling is discussed in the next section about Error Handling.

Debugging

While developing extensions to Sircl, or testing the way Sircl works, installing the "sircl-debugging.js" extension can be helpful.

This is what it does:

  • To all elements having a href, onclick-load, action or formaction attribute, that don't have a tooltip yet, add a tooltip showing the URL invoked when triggering the element;

  • Delays Ajax calls to load page parts with 400 ms to better see load progess indicators;

  • Write to console.log the request processing phases to help detect errors and chaining issues;

  • When a page part is loaded, give it a blue border for 500 ms.

Example

A textarea meter

The following example demonstrates how to extend Sircl with additional behavior, or how to write behavior the Sircl way.

Suppose we have TEXTAREA elements and we want to visualize to the user how many more characters he is allowed to type. Textarea elements have a maxlength attribute that determine the maximum length of the text inside it.

We can add a METER (or PROGRESS) element to provide the user that visual feedback about how many more characters can be typed. But the textarea must be connected to the meter or progress element.

We can model this connection as an event action. Because it will mainly react on the "input" event on the textarea. So that is also where we will place the atrribute: on the textarea.

But would we have an "oninput" or "ifinput" event action ? The meter has to be updated not only on events, it als has to be initialized when loaded, so we should use "ifinput".

For the action name, we could choose "updatemeter". This gives our full event action attribute name: ifinput-updatemeter. To avoid possible conflict with future versions of Sircl that may provide such an event-action out of the box, it is adviced to prefix your own event-actions with "x-" or anything distinctive. So the complete name of the attribute becomes x-ifinput-updatemeter.

It's value would be a CSS selector towards the meter to use, as we need to connect the meter to the textarea.

The HTML code would then be:

<div>
  <legend>Comments:</legend>
  <textarea x-ifinput-updatemeter="#comments-meter" maxlength="200"></textarea><br/>
  <meter id="comments-meter"></meter>
</div>

For it to work, we need the following code:

$(function() {
  $(document).on("input", "TEXTAREA[x-ifinput-updatemeter]", function() {
    var len = $(this).val().length;
    var $meter= sircl.ext.$select($(this), $(this).attr("x-ifinput-updatemeter"));
    $meter.prop("value", len);
  });
});

$$(function() {
  $("TEXTAREA[x-ifinput-updatemeter]").each(function() {
    var $meter= sircl.ext.$select($(this), $(this).attr("x-ifinput-updatemeter"));
    var max = parseInt($(this).attr("maxlength"));
    $meter.prop("max", max);
    $meter.prop("high", max * .8);
    $meter.prop("value", $(this).val().length);
  });
});

You can also test it on Codepen.

On line 4 we get a jQuery object to the meter. You could write this as:

var $meter = $($(this).attr("x-ifinput-updatemeter"));

but in order to support relative CSS selectors, we use the sircl.ext.$select() function.

We then update the value attribute of the meter.

This is all done through an input event handler installed when loading the script.

Whenever a page or page part loads, we will search for all TEXTAREA elements having an x-ifinput-updatemeter attribute and initialize their max, high and value attributes.

The high attribute is initialized to 80% of the maxlength.

 

Loading...