[ $> uname -a ]
At Capillary, one of our main focus is providing a “omni-channel” engagement to our customers. Imagine yourself walking in Puma showroom, signing up with our loyalty program at the billing counter, and returning home to find an exclusive loyalty dashboard. Next time, should you decide not to go all the way to the store, your points are available even on your online purchase! Good days eh mate?

The “Web Channel” is one such channel which bag a few biggies like eCommerce, Social and “Microsite” engagement solutions. A Microsite  is essentially an embeddable widget, within a client’s website. This widget, in simple terms, consist of different services provided by Capillary.For instance, think about a “comments widget”, similar to Disqus and Facebook Comments Plugin, which everyone wants. Of course, each client also wants it customized to match their site’s look and feel. Some of them also want an “extra” feature;  to incentivize their users by giving them a coupon after they comment. Such demanding requirements validates the market space well,but it also demands an equally versatile technology to enable support for plug n play requirements.

The business requirements sum up like:

  1. Brand specific customizations: These include UI/UX and minor feature additions to “predefined” widgets.
  2. Quick deployment cycle: The app should be created, updated and deployed via a deployment interface. In case of wrong config changes, it should be able to rollback to a previous stable version.
  3. Easy to create templates: Optimize time by reducing boiler plate codes. The templates also need to have a rich UI and themes.

“Guys, you will need to build a solid infrastructure around this. Everything must be super easy for our deployment guys. Long turnaround times are simply not acceptable!”, said our manager and the challenge was hence accepted!

[ $> touch “sharingan.framework” ]
Keeping the above in mind, it was clear that this framework needed to be extremely flexible to be able to “plug ‘n play” different components in the app. For brevity sake, lets assume the same “Comments Widget” as an example: For clients who want to incentivize their users for commenting with a coupon, it should be as simple as “plugging in” the “Issue a coupon for user X” component. Since we also had to reduce development time for our frontend designers and developers, there needed to be some way to reduce boilerplate codes. This would help them focus on the “flow” and the business logic rather than wasting time on writing AJAX calls and Validations. Oh! Remember we also needed to have a per-client UI component? This literally translates to having separate CSS and JS assets for every brand which will define their site specific UI/UX.

All the above gave birth to the “Sharingan Framework” – a modern frontend framework for creating plug ’n play widgets. Yeah we are indeed addicted to Naruto 🙂

[ $> cat “sharingan.framework” ]
It soon boiled down a couple of design decisions:

  1. A Single Page App with stateless components for scale. This enables us to very easily “mashup” different components such as “issue a coupon for user X” upon “submitting a feedback form”.
  2. It needs to be backend agnostic and work RESTfully over endpoints that needs to be explicitly defined.
  3. Declarative markup to avoid boilerplate codes and rapidly create templates.
  4. Model driven views.
  5. Config driven control flows; which decides what to load next depending on the expressions defined. The entire business logic of the app is serialized in this (JSON) file and can be easily modified, as compared to defining the logic in the backend code. This along with the statelessness behavior, forms one of the core philosophies of this framework.

The frontend architecture looked something like this:

sharingan_architecture

Sharingan Architecture

Lets dig deeper into the 3 main components, that defines the above:

  • State: A State is an encapsulation of a group of models and a single view. These models (data) are two-way bound to the DOM. This two way data binding allows us to create “directives” similar to AngularJS, which makes common tasks such as validations and AJAX calls much easier. This kind of declarative markup in the template minimizes repeated boilerplate codes.
  • Action: DOM events in the template are observed and declaratively “mapped” to an action. An action accepts input from a data source, processes it and then pipes the response to either a State or another Action itself. This allows us to not only linearly chain actions (much like UNIX pipes), but also enables adding conditional logic like an IFTTT (IF This Then That) flow.
  • Control Flow Configuration File: This file defines the entire behaviour of an app. It mainly consists of State and Action definitions, the control structures and global configurations.

‘Nuff said! Lets take a simple example and see how the above ties up together. Consider a template with a simple login form consisting of a username and password field. This form needs to POST to /foo/auth endpoint upon form submit. If the JSON encoded response from the backend is {success: true} , then we will need to show an authSuccess  page. Otherwise, show an authFailed  page.

The login template (partly taken from bootsnip) looks like something this:

Notice three key things above:

  • sgn-on-submit  attribute in the form element. This instructs the observer to execute loginAction  when a submit  event occurs for the form. Note that the attribute is in the form sgn-on-*  , where “*” can be replaced for with any DOM event. Eg: sgn-on-click="foo"  will listen on clicks and execute fooAction .
  • sgn-ajax-key  attribute in the input fields of the form. This captures the value input by the user and assigns it to $ajax  model with the specified key. This model is used to execute an AJAX call as specified by the action configuration. Eg: Assume the user inputs “me@souvikroy.com” in the email field and “foobar” in the password field. The ajax model will be hence populated as:
  • data-validate/data-error-msg  attribute in the input fields, lets you declaratively add validations to the DOM object.  When the validation fails, the error messages are rendered in another DOM object with id in the form of {inputDomID}_error .

Next, lets look into our control flow configuration, which is a simple JSON file consisting the following frontend configurations:

You must have noticed the state and action definitions above. Every state essentially consists of a template resource location and an URL to identify the state. This URL is present as a hash fragment, like in every modern single page webapp. For instance, www.bar.com/foo#!/login will map "url":"/login"  and will render the loginState  with login.html  template. In case if the hash fragment is empty or doesn’t match with any state, then the startState  value from the global configuration is loaded. The URLs also follow a “ExpressJS path” convention which offers a great deal of flexibility. In future, we will also be supporting HTML5 History PushState API. 

Similarly, action definitions consists of data needed to execute itself and jump to another state or action. For brevity sake, lets assume only actions of "type":"ajax"  right now. A bit earlier we wrote a template which executes loginAction  upon form submit. The action handler within our framework sees that loginAction  is an "type":"ajax"  action which should make a "method":"POST"  call to "url”:"/foo/auth" . Here comes the cool part.. When a successful HTTP response (200) is received from the backend, the success  block is executed. It tells our handler to expect  a response of "type":"JSON" . The decoded JSON response is then stored against  our $response  model, and “match”ed against every expression . Once the expression evals to true, the next state or action specified is “execute”d. As mentioned earlier,  this also enables us to linearly “pipe” the actions along with its data, providing tremendous flexibility for modifications.

For example, consider that the backend response from /foo/auth is {success:true} . The expression "$response.success == true"  will eval to true. Our internal action handler will check what to do next and figures out that authSuccessState  has to be loaded next ( "execute": "authSuccessState" ).

In most of the general world use cases, we will also need to pass data from one action to the next state. The data  block deals with it and can pass data to a shared resource, such as a $url  model, which holds the query parameters of the state’s URL. For instance, assume the backend response to be something as {success:true, id:12345} . We need to pass the "id":"12345"  to the next state’s URL, so that it can perform a “GET” API call with it and render the response data back in the templates.  Note that every model’s attribute is also accessible within the templates, in the form of  {model:attribute} . For example:  {$url:id} refers to an  $url  model with an attribute id  passed from the previous action; and  $response  model with an attribute foo  in the current state:  {$response:foo}

Sometime back we briefly mentioned about how two-way data binding with model-driven-views help us reduce boilerplate codes. Interestingly, that also has a direct role to play for the internal implementation of the above. Since our models are already bound to our current state’s view, any changes in them (via actions) are automatically reflected back in the view. We use an amazing lightweight library called RivetsJS for this and it sure did make our life simpler.

We’ve merely scratched the surface until now. Stay tuned for the next part, a lot more geeky,  where we’ll talk more about Rivets and many other nifty features of Sharingan. We’ll also be implementing a real world example comments widget as mentioned in the beginning. If you have any questions, please leave a comment below or drop an email at: souvik.roy@capillarytech.com.

PS: If working on cutting edge web technologies interests you, we have quite a few positions open.