Category Archives: jQuery

Throttling Delegated Events in jQuery

12 May 2017

If you’ve read any of my posts you might have seen the pattern I like to use for using event delegation. That pattern, in its simplest form, looks like this:

$(document).ready({
   $('body').on('click','div',changeSomething);

   function changeSomething(e){
      // do something
   }
});

For me this is super useful – I can dynamically add elements to the DOM and not need to worry about attaching event listeners to specific UI elements. Well, as you can see I have the click event mapped to div elements. This works fine for something as simple as this:

<div>hello world</div>
<div>the sky is blue</div>

If I click on one of the above divs my changeSomething event will fire. If that’s what I wanted to happen then this is working perfectly. But what happens when we introduce more complicated layout with nested div‘s?

<div>
  <div>hello world</div>
  <div>the sky is blue</div>;
</div>

Each time I click on within either string of text **2** events are fired and my function will be fired twice. Consider this layout:

<div>
  <div>
    <p>hello world</p>
    <div class="button">I'm a button</div>
  </div>
  <div>
    <p>the sky is blue</p>
    <div class="button">I'm another button</div>
  </div>
</div>

If I clicked the “button” divs you will see that **3** events are fired for each click. Why would this be?

First, lets understand how event delegation works.

Event delegation relies on event bubbling. Event bubbling is the behavior that describes how events “bubble up” through the DOM. That is, they start at the DOM element where it was initiated and then travel up to that element’s parent, and then up to the parent’s parent, etc., until the event is stopped somehow (perhaps via the event.stopPropagation() method) or it reaches the top level DOM element.

Event bubbling is key to delegating events – notice that I bound the click event to the body – this means that my code is waiting for “click’s” to “bubble” their way up to the body. If they didn’t bubble through the DOM then the event listener I created would never “hear” the event.

While this explains why it is possible to delegate events it doesn’t explain why when using jQuery to create event listeners that we “hear” multiple events each time we click on a div one single time. This bit from the jQuery documentation provides the answer:


jQuery bubbles the event from the event target up to the element where the handler is attached (i.e., innermost to outermost element) and runs the handler for any elements along that path matching the selector.

Note that the docs says “…and runs the handler for any elements along that path matching the selector. Therein lies the answer. By definition jQuery will fire our callback handler for each element that matches the selector that I specified when I created the event listener. Because event bubbling means that the event will travel up though the DOM the event is therefore “hitting” other DOM elements that match the given selector. Each time this happens the callback handler is fired and the callback receives the very same event object.

Now that we understand what is happening we can look at our HTML and know that this structure:

<div> <!-- the event bubbles to here and the callback is fired a third time -->
  <div> <!-- the event bubbles to here and the callback is fired a second time -->
    <p>hello world</p>
    <div class="button">I'm a button</div> <!-- click here to fire the click event and the first callback -->
  </div>
</div>

…will cause our callback handler to fire 3 times from a single event instead of firing only once (see the in-code comments above).

How do we setup the listener to prevent this from happening? Well…. we could create a unique selector like .btn but I find that restrictive because it creates unnecessary JS hooks into our HTML (imagine all the selectors you would have, i.e., .btn .add .delete .menu .expand… what a mess!!!). In the end jQuery behaves the way that it behaves but we can easily write a small bit of code to prevent a single event from firing our callbacks multiple times.

Recall that each callback is passed the event object which will be the same for them all. As a result if you log those events to the console you will in fact see that they all have the same timestamp since, quite naturally, they are in fact identical. With this knowledge we can build a “throttling” function. Throttling is the term used to allow the first function call, but prevent subsequent calls.

Review the following (its very much simplified for this example):

   $(document).ready({

      var _fifo = []; // used by the throttler

      // throttle
      function _isOriginal(e){
        for (var i=0;i<_fifo.length;i++){
          if (_fifo[i] === e.timeStamp){
            return false;
          }
        }

        _fifo.push(e.timeStamp);
        if (_fifo.length > 20){
          _fifo.splice(0,1);
        }
        return true;
      }

      // create event listener
      $('body').on('click','div',changeSomething);

      // the callback for the delegated "click" event
      function changeSomething(e){
        // check the event to see if we have already seen it!
        if (!_isOriginal(e)){
            return; // if we ***have*** already seen the event then ignore it!
        }
        // do stuff in here
      }
   });

Pay attention to the _isOriginal function. It does the following:

  • Uses the _fifo array to store the 20 most recent timestamps (“20” is arbitrary, pretty sure 1 or 2 will work as well)
  • Compares the timestamp of the current event against those stored within the _fifo array
  • If there is a match it returns “false” (ignore the event)
  • If there is no match it returns “true” (new event!)
  • Timestamps are cycled in and out of the _fifo array at all times so that we will always have a timestamp from the most recent event and thus will always be able to compare and reject copies. The last/oldest timestamp is pruned (via the splice method ) from the _fifo array once its length hits 20 while the newest is added to the front (read about FIFO stacks)

There you go, a simple and effective “callback throttler”. As always, if you understand how things work you may just find that you don’t need to go in search of a plugin to solve your problem. A little “under-the-hood” knowledge goes a long way 🙂

Trigger Basic Auth Dialog with jQuery / AJAX

05 May 2017

We’re all familiar with basic authentication (or should be) – my experience with it has been via cPanel or doing it by hand on *nix servers. Once setup it just works. The other day I was integrating an app into a service where the dev environment had basic authentication on it. My AJAX calls were blocked (naturally) but did not trigger a username/password dialog to appear. I had assumed that the browser would just “do its thing”.

What is happening behind the scenes is that my AJAX call causes the server to return a 401 HTML error page. This is an unexpected response that jQuery can’t handle and that will cause the following error in the console:

$ Uncaught SyntaxError: Unexpected token < in JSON at position 0 at JSON.parse ()

jQuery will also return a status code “0” which, by the way, is a generic error from jQuery. As the response from the server was an HTML document (401 error page) jQuery couldn’t parse it (it was expecting the response to be JSON) thus the above error. Lacking any proper status code to use jQuery then just uses “0”.

Lastly if I inspect the response header I can see that WWW-Authenticate: Basic appears. That’s a clue as to what we should do to get things working which is to add a Authorization:Basic header to our AJAX request to begin with.

We can add the necessary header by specifying the beforeSend callback when we configure the AJAX request. Inside of the callback we can create the Authorization header that will be added to our request. See the following example:

  $.ajax(
    {
      method: 'POST',
      contentType: 'application/json',
      url: 'service URL here'
      data:'data here',
      beforeSend: function (xhr) {
        xhr.setRequestHeader ('Authorization', 'Basic xxxx');
      },
    })
    .done(function( data, textStatus, jqXHR ) {
      // do something here...
    })
    .fail(function( jqXHR, textStatus, errMsg ){
      // do something here...
    });

Here you can see that we are setting the “Authorization” header via xhr.setRequestHeader. The spec says that Basic should be followed by a base-64 representation of the user name and password to authenticate with but in this case we are using an arbitrary string. Since its obviously wrong the server will respond by asking for the correct credentials; the authentication dialog will appear.

As you can see there is some interaction going back and forth between the client and the server. If you’re attentive to the network tab in the Chrome console you can see the handshaking as it happens.

Templating with jQuery

11 Jan 2016

There are a few templating libraries available which you could use to create templates for your apps. While its easy enough to learn how to use them you could also just as easily create your own external template files for your jQuery-based projects using the jQuery skills you already have. Discussed here is a simple way of including HTML templates into your jQuery work.

Introduction

Templates provide a way of separating layout from code. As such, they have various methods of indicating where data is to be inserted into the template. In our case we’ll use class and ID selectors in the HTML itself and thus be able to use jQuery’s existing methods to manipulate the template.

The data to be inserted into our templates can take the form of strings, numbers, objects or some sort of array. Arrays mean that you likely have a repeating region. The available templating libraries (Underscore, Handlebars, etc) or frameworks that have built-in templating (Sencha Touch, Angular, etc) provide methods that abstract the process of looping through arrays to display lists or tables. These libraries and frameworks also provide helper functions for things such as string formatting and conditional logic to add intelligence to the template.

While built-in helper methods are a handy feature this article will assume that you’ll build your own by leveraging jQuery’s features. For our purposes I’ll assume that you only need the most basic type of template and that will be one that has identified in some way what portions of the template itself will be used/substituted or otherwise populated with your desired data.

Setup a Template

Our template will be an HTML file – a properly formed file complete with all of the required HTML elements, ie., html, head, title & body tags. Basically, you will layout your page so that it represents one specific type of view within your application. Save your templates in a folder called “templates” in some logical area of your app/site.

While you are laying out your page you are naturally creating CSS class and ID selectors for you page elements. While you are doing this you are also likely using greeked-in text or “for-position-only” images so that you can realize your layout. Also, you should build the template with an eye towards the areas that need to be populated with text, images or whatever the case might be. With this last point in mind you should pay attention to the class/ID selectors that you give to your DOM elements as that will be what we use to manipulate the template.

Since we mentioned CSS, your style sheet should be an external style sheet that your app/site loads up. This way when the template is used all of the necessary styling will already be present. If you are the sort that doesn’t want stylesheets loaded that aren’t yet needed then figure out a way to inject them into your page – we are using jQuery so this should be an easy task – understand that today’s discussion is only about templates not dependency management 🙂 (though honestly, the link to the external CSS could be added to the template HTML, then you create a query selector to grab it when the template is loaded and then….. but I digress…)

Sample Template

Here’s a sample template from a project that I recently worked on.



    

    

Pretty basic. Note that the actual template is the one with the #interactive ID. The rest of the page is just filler for the layout guys on my team.

Within the #interactive DIV I used ID selectors for the elements that would need to be populated with text. Also, while I didn’t keep any dummy text in the template you could easily insert greek text so that you can see what the layout looks like without having to first run it through your app. That’s the nice part about using this kind of templating workflow, that your teammates can focus on layout while you focus on developing the application without worrying about stepping on each other’s work.

Worth noting here is that like in any project you have a header and a footer. The same with this template. Recall that we mentioned that it was nice to be able to look at a template outside of your application to see it in its entirety without impacting development. That would assume that the header and footer be present even though it isn’t needed. You could of course elect not to include these two page features for many reasons, not the least of which would be to make a smaller template file that loads that much faster. This example for better or worse includes the header (denoted by the topBlueBar and logo ID’s) and the footer (denoted by the controlsWrapper).

It is of course true that you can add some automation via Gulp or similar to strip the header and footer from the templates in order to give the layout guys the benefit of viewing their work in-context and the more picky developers the desire to strip out what would be considered wasteful…. however you want to deal with it, its entirely up to you. Again, both header and footer are presented here and will be dealt with in the next section.

Before we proceed look at that wrapping div element on line 5 – this is **necessary** as without it the template will not work in the way that we might expect. We’ll explain why in the next section.

Load the Template

This is the most difficult part (not that its at all difficult) – loading a template. You simply need to make an AJAX call to load up the file. Here’s an example:

var _templates = {}; // save your templates so that you can re-use them
$.get('templates/content_tpl.html', function(template){
    // in this example, we're calling the template "sample_template" and assigning the template as a DOM object as its value
    _templates.sample_template = $('
').append($($.parseHTML(template)).find('#interactive').html()); });

Hopefully the above is clear but lets quickly break things down.

First, $.get() loads data from the server using a GET request. We are passing the local path to the desired template HTML file.

Next, the file that is returned to us is text which isn’t as useful to us as it could be so we convert it into an array of DOM nodes. This is crucial so that we can use jQuery’s DOM-manipulation methods on the template instead of having to resort to string or regular expression methods.

Now that the page has been converted to jQuery DOM nodes we look for the template area itself which is denoted by the DIV with the #interactive ID.

Finally we assign the resulting DOM nodes to a property of the _templates element so that we can use it in the future.

Interestingly we now can get back to that wrapping DIV mentioned in the previous “Sample Template” section. Without it in place our #interactive query selector won’t work because what will happen is that jQuery will search the first DIV in the page, which of course doesn’t contain the div#interactive element. By wrapping everything in a DIV jQuery is then able to traverse all the nodes and find the desired element.

In case you didn’t realize what just happened – we extracted only the template HTML from the template. Everything else is being discarded. So the header and footer HTML that was part of the template HTML file itself is not what we will be working with moving forward.

Using the Template

Recall that we converted the string result of the AJAX get to DOM nodes, that we pulled out only the nodes that we needed, and that we saved the result to _templates.sample_template.

Using the template is simple: do what you always do in a page where you want to change the values or inner html of page elements:

// get a deep copy of the template
var tpl =  JSON.parse(JSON.stringify(_templates.sample_template));

// populate the template
tpl.find('#title').html('This is my first template');
tpl.find('#subTitle').html('This is a subtitle');

// now insert the template html into your app
// where our app already has a #templateArea DIV within it so
// this is a simple insert 
$('#templateArea').html(tpl.html());

Notice that we can easily manipulate our template using the usual jQuery DOM manipulation methods. Also, it would be a simple matter to pull out a chunk of DOM nodes to iterate through a list and then reinsert them back into the tpl.

From here you can start to flesh out your home-grown templating system. Adding methods for formatting dates, dollar amounts, or handling repeating areas like lists or tabular data.

Creating UI Using HTML5 Data Attributes

28 Mar 2015

As you may know part of the HTML5 spec includes the use of “data” attributes within HTML elements. For example:

<div data-animal="frog"></div>

You can see the naming convention – the first part must be “data-” and the part after can be anything. This feature provides a mechanism for storing information within your elements for whatever your use-case happens to be.

jQuery provides and easy way of accessing the HTML5 “data” attribute via the data() method as shown here:


<p data-color="red">What color am I hiding?</p>

console.log($(p).data('color')); // outputs "red" to the console

One way to use this is as part of a UI methodology that looks for specific “data” tags in response to user interaction. Thus your application will know that when the user clicks on an element that the element in question is a button (for example) and that the button should trigger specific functionality.

My personal convention for “buttons” and the like is an “action” data tag – data-action=”doSomething”. When I don bother using a framework my applications are always look for this type of attribute within elements. This quickly becomes important if you delegate events to an element higher in the DOM, for example, the body tag.

The sample code below shows this kind of setup.

<html><head><title></title>
<script>
   $(document).ready({
      $('body').on('click','div',changeSomething);

      function changeSomething(e){
         // do something
      }
   });
*lt;/script>
</head>
<body>
   <div id="wrapper">
      <div>
         <div data-action="changeColor">Random Colored Text</div>
      </div>
   </div>
</body>
</html>

All well and good – if the user clicks on the text “Randomly Colored Text” then the changeColor function is fired.

Look at this arrangement:

<html><head><title></title>
<script>
   $(document).ready({
      $('body').on('click','div',changeSomething);

      function changeSomething(e){
         // do something
      }
   });
</script>
</head>
<body>
   <div id="wrapper">
      <div>
         <div data-action="changeColor">Randomly Colored Text</div>
         <div>Hello</div>
         <div data-action="changeSize">Randomly Sized Text</div>
      </div>
   </div>
</body>
</html>

Clicking on either of the DIVs will trigger the “changeColor” function – and in fact, clicking on ***any*** div on the page will call that function – and that includes the wrapping DIVs. We need a way to discern which DIV element is actually something that we care about.

<html><head><title></title>
<script>
   $(document).ready({
      $('body').on('click','div',changeSomething);

      function changeSomething(e){
         if (!$(e.target).data('action')){
            return;
         }
         // do something 
      }
   });
</script>
</head>
<body>
   <div id="wrapper">
      <div>
         <div data-action="changeColor">Randomly Colored Text</div>
         <div>Hello</div>
         <div data-action="changeSize">Randomly Sized Text</div>
      </div>
   </div>
</body>
</html>

On line 7 we’re now checking to see if the data attribute “action” exists in the div that triggered the event. If it doesn’t exist we stop the flow of execution via the return statement. That’s a good start – lets take it one step further as we still need to know how to “changeColor” (line 18) or “changeSize” (line 20).

<html><head><title></title>
<script>
   $(document).ready({
      $('body').on('click','div',changeSomething);

      function changeSomething(e){
         if (!$(e.target).data('action')){
            return;
         }
         if ($(e.target).data('action') === 'changeColor'){
            console.log('change the fonts color here');
         }
         if ($(e.target).data('action') === 'changeSize'){
            console.log('change the fonts color here');
         }
      }
   });
</script>
</head>
<body>
   <div id="wrapper">
      <div>
         <div data-action="changeColor">Randomly Colored Text</div>
         <div>Hello</div>
         <div data-action="changeSize">Randomly Sized Text</div>
      </div>
   </div>
</body>
</html>

Now we have it – not only are we looking to see if any DIV element that has been clicked is something that we should pay attention to but we are also looking for specific data attributes from which we need to do something (lines 10 and 13). In this case we’re attempting to change the color of the text or change the size of the text.

If you elect to you could always just look for the specific data attributes and not do a check for the “action” data attribute – whatever you like to do – my personal approach is a larger topic making use of both “checks” – see jQuery, PubSubs and Event Delegation.

Something that will mess things up for you is the common practice of nesting multiple elements within a wrapping “button” DIV.

<html><head><title></title>
<script>
   $(document).ready({
      $('body').on('click','div',changeSomething);

      function changeSomething(e){
         if (!$(e.target).data('action')){
            return;
         }
         if ($(e.target).data('action') === 'changeColor'){
            console.log('change the fonts color here');
         }
         if ($(e.target).data('action') === 'changeSize'){
            console.log('change the fonts color here');
         }
      }
   });
</script>
</head>
<body>
   <div id="wrapper">
      <div>
         <div data-action="changeColor">
            <div class="homeIcon"></div>
            <div class="buttonText">
               Randomly Colored Text
            </div>
         </div>
         <div>Hello</div>
         <div data-action="changeSize">Randomly Sized Text</div>
      </div>
   </div>
</body>
</html>

The data-action attribute is still on the button wrapper as seen on line 23. The difference is that we now have two DIV elements within that wrapper – they will be the target of any click events and thus our code will skip over that button and not do anything. You may be tempted to add data-action attributes to everything inside of your button wrapper but that gets messy. Instead lets add some more code to our initial checking of whether or not the data-action attribute exists. Lets add something that iterates through the parents of the div looking to see if one of them contains the data attribute that we are looking for.

if (!$(e.target).data('action')){
    // if e.target does not have a data action, search through a max of 2 parents to find it
    var obj = e.target;
    var max = 0;
    while (!$(obj).data('action') && max != 2){
        obj = $(obj).parent();
        ++max;
    }
    if (!$(obj).data('action')){
        return;
    } else {
        e.target = obj;   
    }
}

Here’s whats happening here – the code looks first for a data-action attribute. If it is not found it then proceeds to look at the parent elements to see if they have them. If not found in one parent it then looks at that parent’s parent, and so on. We have a maximum ceiling here of 2 levels of parent searching. Plenty for this example and for most applications.

Once the parent with a data-action attribute is found the object that represents that parent is used to replace the click event’s “target” – as an FYI all event objects store the element that was clicked, holding them in the event object’s “target” property. By replacing the target – e.target – with the parent object that ***does*** contain the required data attribute we allow our code to continue what it was doing previously.

The full example:

<html><head><title></title>
<script>
   $(document).ready({
      $('body').on('click','div',changeSomething);

      function changeSomething(e){
         if (!$(e.target).data('action')){
             // if e.target does not have a data action, search through a max of 2 parents to find it
             var obj = e.target;
             var max = 0;
             while (!$(obj).data('action') && max != 2){
                 obj = $(obj).parent();
                 ++max;
             }
             if (!$(obj).data('action')){
                 return;
             } else {
                 e.target = obj;   
             }
         }
         if ($(e.target).data('action') === 'changeColor'){
            console.log('change the fonts color here');
         }
         if ($(e.target).data('action') === 'changeSize'){
            console.log('change the fonts color here');
         }
      }
   });
</script>
</head>
<body>
   <div id="wrapper">
      <div>
         <div data-action="changeColor">
            <div class="homeIcon"></div>
            <div class="buttonText">
               Randomly Colored Text
            </div>
         </div>
         <div>Hello</div>
         <div data-action="changeSize">Randomly Sized Text</div>
      </div>
   </div>
</body>
</html>

Hashbang View Routing with History Support

25 Jul 2014

In the world of web-apps we commonly create our applications as “single-page websites”. That is, the page never reloads – we opt instead to manipulate the DOM based on user input to give the look of a seamless native application. Frequently we may need to have the ability to go “back” to the previous view that the user was on. This might be because the UI contains a “Back” button or maybe we’re on Android and want to enable the software back button.

It might seem trivial to just hard-code a back-button but its common to have templated views capable of displaying many different kinds of information and simply going “back” ignores the state of the view. You may think that all you need to do is to declare a variable somewhere that holds a string or object that tells you what your previous view contained and then branch off of that. True, that would work – how about going back multiple steps? Again, you can hack something together I’m sure….

At some point in the past someone was thinking about the same thing and hit upon the idea that the browser’s built-in history object can be used to track an application’s view history. As our app is a single-page application we aren’t loading completely new pages so the history object will just “sit” on the current page/url. Its possible however to update the history object without loading a new page by using “anchor references” which become part of the page’s history but do not cause a page reload.

For example, we can change this:

  • index.html

To this:

  • index.html#hello

An anchor is meant as a quick way to jump down to a specific part of a long page, thus its name. In our case, its counterpart does not exist on the page so merely changing the url of our application to include an anchor does nothing other than to update the page’s history object. When applied to our purposes the anchor will have a special meaning and so moving forward we’ll call it by its technical name – a hash fragment or just hash for short.

Knowing that the page’s history object includes hashes might not seem very interesting. Consider though that there is an event called window.onhashchange that is fired every time the hash changes. Consider next that we have a method of updating the hash in the form of window.location.hash. Further that we can use window.history.back() to traverse the history object which triggers the window.onhashchange and we soon realize that we have the essential ingredients to create a hash-based view router: 1) a method of keeping track of views, 2) a method of going back in our view history, 3) a method of knowing when the hash changes, and 4) a method of changing the hash.

You may have noted that the title of the article contains the word “Hashbang” – this is what a hashbang looks like: #!

The purpose of the “bang” (the exclamation point) is to function as a flag to Google’s bots that the URL they have just parsed should be considered as a link, not an anchor to something else lower on the page. If we use hashbangs in our single-page applications on the web Google will know that its an *application* and not just a single page and will index our links and treat them as individual pages.

For clarity’s sake, here are some hashbang examples:

  • index.html#!home
  • index.html#!products
  • index.html#!productDetail

For web-apps the hashbang isn’t as useful but its part of how we do things none-the-less. You’ll see HTML5 frameworks all over use them as they don’t know if your intent is to build a web-app for a mobile device or to build a single-page app that will live on the internet. As a result its baked in as a hashbang instead of just a hash and so as I said its just how things are done. Contrary to what my mother says I’ve decided that just because everyone else does it that it does in fact mean that I should do it too.

However, if you’ve read what html5doctor.com has to say on the topic you would walk away with a completely different perception:

You may have already seen articles fussing over the adoption of the “hashbang” (#!) pattern on sites like Twitter. This technique updates the address bar with a fragment identifier that can then be used by JavaScript to determine which page and state should be displayed.

This works as a method of creating a bookmarkable, shareable URL for a page’s state in the absence of a standard API.

…It’s ugly. It’s a hack and it looks like one…

…The hashbang was never intended to be a long-term solution, so don’t rely on it….
~ html5doctor.com

“so don’t rely on it”…. the exclamation point’s presence is by the whim of the developer or baked in by a framework. Its presence or lack thereof is actually immaterial to our purposes – if you elect not to use it you still have a fragment identifier that can serve as a “bookmarkable, shareable URL for a page’s state”. A hashbang’s only use as I understand it is to help Google index our one-page websites instead of giving Google a “locked door” that it’s bots can’t get past.

According to Google:

…AJAX URLs containing hash fragments with #! are eligible to be crawled and indexed by the search engine.
~ Google, via “Making AJAX Applications Crawlable

It is true that this Google-ism isn’t part of the official HTML5 spec – nor should it be as it has nothing to do with HTML5. As the hashbang is used more often than not by various frameworks, Twitter, Bing, and other web-properties its repeated use for this particular purpose amounts to a de facto standard for ***indexing AJAX-based single-page applications*** though admittedly not one that is part of any “official” standard.

The inclusion of the “bang” on the topic of Hash-based View Routing is more convention than anything else. And the convention is that “hash” and “bang” go together – use it or not, its up to you. If you use it within your app I’m pretty sure you can “rely on it”.

Sample App

The code presented here is functional and is based on a mythical Audi hybrid app. Click the image to view the app which contains 4 views used to illustrate the topic of this article.

Methodology

This is how I handled tracking view state – it makes sense to me and definitely fits the jQuery way of doing things with data locked into DOM elements via data attributes. Other frameworks like backbone.js or angular.js provide an abstraction for managing view history. I wanted to get past the abstraction that a framework provides and see about doing it myself.

Triggering a view change

Here’s the run-down on triggering a view change:

  1. A button is touched – the associated event object and its data attributes are broadcast to all subscribers of the touchend event
  2. The triggerViewChange method looks at the “action” data attribute – if it equals “updateHash” then the the event object is pushed to the _events array for future use and the page’s hash is set via window.location.hash (_events is described in more detail later in this article)
  3. Setting the hash itself causes the hashchange to fire
  4. notifyHashChange is listening for hashchange events and now receives the event and notifies the methods that have subscribed to it. Review the first code sample provided later on this page – the init() method – there are four functions subscribed to the hashchange event and they all create views. Those functions are:
    • audi.controller.showDefaultView()
    • audi.controller.showModelsView()
    • audi.controller.showModelsDrillDownView()
    • audi.controller.showCarDetailView()
  5. Each of the functions subscribed to the hashchange inspects the window’s hash – if it corresponds to a specific key then the function knows it is the view that must be rendered to the screen
    • Recall that the event object that triggered all of this was added to the end of the _events array – when a view changes it inspects that last item of this array for the desired event object and inspects the HTML5 data attributes it needs in order to know how to configure the content.

That’s it, a new view has been triggered and is now displayed, we’ve updated the _events array and the windows’s location object has a record of where we went.

Going back in the view history

You can simulate clicking a button by using Chrome’s console and entering “window.history.back()” provided that you created a history to begin with by touching the different navigation buttons.

Here’s the rundown on what happens when window.history.back() is invoked by hand via the console or by an in-app “back” button:

  1. showPreviousView() is triggered – this function will:
    • Remove the last array item from _events
    • window.history.back() is invoked
  2. The hashchange event is fired and everything wired to it does its thing:
    • The hash is now different – the four functions subscribed to this event are notified and each inspect the hash
    • The function whose key matches the hash fires and the appropriate view is rendered
    • As before the view in question looks at the last item in the _events array. Since we removed one from the end of the array (step 2 above) the last array item is now in-synch with our view. Everything the view needs to know about this prior view is now available to it and so it configures its content appropriately

Methodology in Practice

What I’m going to show here uses my own personal convention for building apps. To get a refresher for how I approach things you should first read “The Pub/Sub Pattern and Event Delegation in jQuery“.

Lets skip all of the setup – assuming you read the aforementioned article we will start with a bare-bones app – 4 views/pages – here’s all the code:

A short intro to the code is below.

Init the app (audi.js)

To initialize the app I have the following in the index.html

<script>
	$(function(){
		//document.addEventListener('deviceready', audi.init, false);
		audi.init();
	});
</script>

As you can see this is the hook to get things started. Once the document’s assets have been loaded jQuery’s ready event fires which in turn fires the app’s init() function.

init() sets up our controller by associating functions with the proper touch events and then by setting up all the listeners required by the application.

;(function(ns,$){
	/* init()
	 * This inits the application.
	 * @type {Function}
	 * @param {}
	 * @return {} Returns nothing
	 */
	ns.init = function(){
	audi.controller.subscribe('touchend',audi.controller.doButtonTouched);
	audi.controller.subscribe('touchend',audi.controller.doButton2Touched);
	audi.controller.subscribe('touchend',audi.controller.triggerViewChange);
        audi.controller.subscribe('touchend',audi.controller.showPreviousView);

        audi.controller.subscribe('hashchange',audi.controller.showDefaultView);
        audi.controller.subscribe('hashchange',audi.controller.showModelsView);
        audi.controller.subscribe('hashchange',audi.controller.showModelsDrillDownView);
        audi.controller.subscribe('hashchange',audi.controller.showCarDetailView);

	$('body').on('touchend','div',audi.controller.notify);
        $(window).on('hashchange',audi.controller.notifyHashChange);
        $(window).on('popstate',audi.util.showHideBackButton);

	window.location.hash = ' '; // make sure the hashchange event fires if the page is reloaded, useful in desktop browsers
        window.location.hash = 'defaultView';

	}
})(this.audi = this.audi || {},jQuery);

Of special note to us are lines 11 & 12 which assign our triggerViewChange and showPreviousView as subscribers to the touchend event. The buttons that exist in this sample application can do one of two things, either 1) change the view (move forward) or 2) go to the previous view (move backwards).

Another thing to note are lines 20 & 21.

Line 20 contains the setup for the hashchange listener. Every time the hash is updated the method “listening” to this event is fired. The 2 lines previously mentioned are responsible for changing the hash – again, they were triggerViewChange and showPreviousView.

Line 21 sets up a listener to the popstate event – I’m using this merely to control when my in-app back button should appear. You can view the source inside of audi.util.js to learn more.

The Controller

Below is a fragment of the controller code showing only the points of interest to our topic.

/** 
 * audi.controller 
 * @type {Object}
 * @return {} returns nothing
 * @name audi.controller
 * @namespace holds the event pub/sub system and button methods
 */
;(function(ns,$){

    var _uiUpdatePause = 5;
    var _subscriptions = {};
    var _events = []; 

	/* subscribe()
	 * This handles the event subscriptions.
	 * @type {Function}
	 * @param {string} eType - the event type
	 * @param {object} cb - the function reference
	 * @return {} Returns nothing
	 * @see audi.init()
	 */
	ns.subscribe = function(eType,cb){
		if (!_subscriptions.hasOwnProperty(eType)){
			_subscriptions[eType] = [];
		}
		_subscriptions[eType].push(cb);
	};

	/* notify()
	 * This notifies the event subscribers (hashchange has a separate publisher).
	 * @type {Function}
	 * @param {object} e - the event object
	 * @return {} Returns nothing
	 * @see audi.init()
	 */
	ns.notify = function(e){
		if (!$(e.target).data('action')){
			return;
		}
                if (audi.util.getIsScrolling()){
                       return;  
                }
		e.preventDefault();
		e.stopPropagation();

		var cbs = _subscriptions[e.type];
		for (var i=0;i<cbs.length;i++){
			cbs[i](e);
		}
	};

	/* notifyHashChange()
	 * This notifies the hashchange event subscribers.
	 * @type {Function}
	 * @param {object} e - the event object
	 * @return {} Returns nothing
	 * @see audi.init()
	 */
        ns.notifyHashChange = function(e){
            var cbs = _subscriptions[e.type];
		for (var i=0;i<cbs.length;i++){
			cbs[i](_events.slice(-1)[0]);
		}
        }

	/* triggerViewChange()
	 * Changes the location hash which will trigger the hashchange 
         * event which itself triggers the appropriate view update.
	 * @type {Function}
	 * @param {object} e - the event object
	 * @return {} Returns nothing
	 * @see audi.init()
	 */
	ns.triggerViewChange = function(e){
		if ($(e.target).data('action') != 'updateHash'){
			return;
		}
		var hash = $(e.target).data('hash');
		_events.push(e);
		setTimeout(function(ev){
			window.location.hash = hash;
		},_uiUpdatePause);
    	}
	/* showPreviousView()
	 * This is called by a hashchange event - it uses the window object's
         * history.back method to go to the previous view.
	 * @type {Function}
	 * @param {object} e - the event object
	 * @return {} Returns nothing
	 * @see audi.init()
	 */
	ns.showPreviousView = function(e){
		if ($(e.target).data('action') != 'showPreviousView'){
			return;
		}
        	var hash = window.location.hash;
        	if (hash == '#defaultView' || hash == 'modelDrillDownView'){
			return;   
		}
        	_events.pop(); // remove the current recorded event so that the previous one will be used.
		window.history.back();
	}

	/* showDefaultView()
	 * Handles the click event for the logo, this renders the default view.
	 * @type {Function}
	 * @param {object} e - the event object
	 * @return {} Returns nothing
	 * @see audi.init()
	 */
	ns.showDefaultView = function(e){
		if (window.location.hash !=  '#defaultView'){
			return;
		}
		audi.view.destroy_iScroll();
        	audi.view.renderDefaultView(_events.slice(-1)[0]);
	}

	/* showModelsView()
	 * Handles the click event for the models button.
	 * @type {Function}
	 * @param {object} e - the event object
	 * @return {} Returns nothing
	 * @see audi.init()
	 */
	ns.showModelsView = function(e){
		if (window.location.hash !=  '#modelsView'){
			return;
		}
		audi.view.destroy_iScroll();
        	$('#mainView').removeClass('splashBG');
        	audi.view.renderShowModels(_events.slice(-1)[0]);
	}

	/* showModelsDrillDownView()
	 * Handles the click event for the models button.
	 * @type {Function}
	 * @param {object} e - the event object
	 * @return {} Returns nothing
	 * @see audi.init()
	 */
	ns.showModelsDrillDownView = function(e){
		if (window.location.hash !=  '#modelDrillDownView'){
			return;
		}
		audi.view.destroy_iScroll();
        	audi.view.renderDrillDown(_events.slice(-1)[0]);
	}

	/* showCarDetailView()
	 * Handles the click event for the models button.
	 * @type {Function}
	 * @param {object} e - the event object
	 * @return {} Returns nothing
	 * @see audi.init()
	 */
	ns.showCarDetailView = function(e){
		if (window.location.hash !=  '#showCarDetailView'){
			return;
		}
		audi.view.destroy_iScroll();
		audi.view.renderCarDetail(_events.slice(-1)[0]);
	}
...

What is the “_events” variable used for?

You’ll see on line 12 above that I’ve declared a variable called _events and its purpose is to collect touch events in an array as they occur. Not all touch events, only ones that are “actionable” as determined by HTML 5 data attributes within some DOM elements (data-action or your own convention). Specific elements are in fact buttons and trigger view changes – and thus hashchange events. These buttons contain information on the view itself and so I hold the touchend events from our button divs (and thus their data attributes) in this array so that it can be passed to the relevant views when the hashchange event fires. Thus, if I go back in my browser history – effectively going backwards in my view history – I have the event that was used to create that previous view and by extension all of the information necessary to populate that prior view.

This _events array is managed by triggerViewChange() which pushes new event objects to the array and showPreviousView() which removes the last array item when the hashchange event fires.

The current event object is accessed like so: _events.slice(-1)[0]. Thus we are able to manage views via hash changes while feeding the views relevant data per the dom element that triggered the hashchange to begin with.

Of course we could choose instead to keep all the DOM-locked data attributes as part of our hash, something like this:

  • index.html#foo=bar&dog=brown&bacon=awesome

I didn’t use that approach because there’s no need as it adds complexity where its not needed. Did you notice how little effort was required to go back in our app’s history? By saving the touch event object I already have all the data represented in the above example. Everything is much simpler and requires less code without the need to parse hash fragments.

Of course, if you have a single-page web page (not a hybrid app) and need bookmarking support or deep linking then you should consider a hash fragment that contains name/value pairs representing whatever state your view requires.

Summary

Once you wrap your brain around the ideas here you realize its not so difficult. The “ah-ha” moment should have happened after you’ve read the Methodology section. If not and you’ve read this far go back and re-read it now that you have the code in your brain as a reference.

Download the entire working example. Note that it uses touch events so you have to enable them in your modern browser of choice.

Also, you can give this a whirl in PhoneGap too if you like, just package up the contents and do a build. You may want to edit the index.html so that the app builds its initial view on “device ready”.