Category Archives: JavaScript

HTML5 Canvas and Particle Systems

13 Mar 2015

I’ve been doing a lot of canvas stuff lately which reminded me of some things that I’ve always wanted to try. In particular I’ve always meant to find time to try writing a particle system using HTML5 Canvas.

Its pretty easy to do – the idea is that we render a shape or a number of shapes to a canvas using window.requestAnimationFrame to recalculate the positions of the particles it each iteration. Before each render to the canvas we wipe the canvas clean then render the shapes to their new locations. That’s all there is to it.

I wrote two experiments at creating a particle system – in one I had the particles take care of themselves separate from any thing else – they essentially moved themselves around the canvas. The second attempt has a system that updates all of the new particle coordinates before rendering them all to the canvas. There are some subtle differences and effects that can be achieved. In almost all cases the second “style” of updating the canvas is preferred.

Before we go any further I’m assuming that you are using a modern web browser – I’ve not bothered with supporting lesser browsers.

Experiment One

The methodology in this one is that each particle takes care of itself. That is, it calculates its own position within the canvas and writes itself to the canvas regardless of whatever else might be happening. The caveat here is that we always need to remove the previously rendered shape before plotting the new one else we end up drawing lines on the screen.

You might think that this would be easy to do as we always know the coordinate of the previous shape and can simply erase it. Shapes, however, are anti-aliased. The outer-most anti-aliased edge of the shape (a “ghost”) is always left behind when we attempt to erase only the portion of the canvas where the previously plotted shape was. You can enlarge the bounding area of the shape to be sure to remove all of it but then you see “empty borders” around shapes as they cross each other.

The point is that even though this looks cool its impractical for most purposes.

The first example doesn’t bother to erase the previously plotted shape. As a result we have a series lines – but lines that have opacity and compositing so that we end up with something cool.

The example on the right does attempt to erase the previously plotted shape but as I mentioned above you can still see the “ghost” of that previous shape which leaves a sort of trail behind it as it moves about the screen.

Experiment Two

This one approaches Canvas animation the way its usually done. First calculate the new position of all shapes, wipe the entire canvas clean, and then write all the shapes to the canvas, repeat.




I wont go through any exhaustive description of how to do things – the described workflow above and the source code below should be all that you need to give it a try for yourself.

;(function(ns){
	
	var _parts = [];
	var _cvs = null;
	var _ctx = null;
	var _bgColor = null;
	
	ns.setupParts = function(cvsID,bgColor){
		_cvs = document.getElementById(cvsID);
		_ctx = _cvs.getContext('2d');
		_bgColor = bgColor;
	}
	
	ns.addParts = function(o){
		_parts.push(o);
	}
	
	ns.updateCanvasWithParts = function(){
		_ctx.clearRect(0,0,_cvs.width,_cvs.height);
		if (_bgColor){
			_ctx.fillStyle = _bgColor;
			_ctx.fillRect(0,0,_cvs.width,_cvs.height);
		}
		for (var i=0;i<_parts.length;i++){
			_ctx.fillStyle = _parts[i].color;
			_ctx.globalCompositeOperation = _parts[i].comp;
			_ctx.globalAlpha = _parts[i].alpha;
			_ctx.fillRect(_parts[i].x, _parts[i].y,_parts[i].height,_parts[i].width);
			_parts[i].update();
		}
		requestAnimationFrame(ns.updateCanvasWithParts);
	}
	
	ns.particle = function(config){
		var that = this;
		this.vx = config.omni ? (Math.random() < 0.5 ? config.vx * -1: config.vx) : config.vx;
		this.vy = config.omni ? (Math.random() < 0.5 ? config.vy * -1: config.vy) : config.vy;
		this.x = config.x;
		this.y = config.y;
		this.originX = config.x;
		this.originY = config.y;
		this.starfield = config.starfield;
		this.color = config.color;
		this.bgColor = config.bgColor;
		this.alpha = config.alpha;
		this.comp = config.comp;
		this.size = config.size;
		this.height = config.uniform ? config.size : Math.round(Math.random() * config.size);
		this.width = config.uniform ? config.size : Math.round(Math.random() * config.size);
		this.update = function(){
			if (!that.starfield){
				if (that.x > _cvs.height - that.height){
					that.vx = that.vx * -1;
				} else if (that.x < 0){
					that.vx = Math.abs(that.vx);
				}
				if (that.y > _cvs.width - that.width){
					that.vy = that.vy * -1;
				} else if (that.y < 0){
					that.vy = Math.abs(that.vy);
				}
			} else {
				if (that.x > _cvs.height + that.size || that.y > _cvs.width + that.size ||
					that.x < -that.size || that.y < -that.size){
					that.x = that.originX;
					that.y = that.originY;
				}
			}				
			that.x = that.x + that.vx;
			that.y = that.y + that.vy;
		}
	}

})(this.particles2 = this.particles2 || {});

document.addEventListener('DOMContentLoaded', function(e){	
	particles2.setupParts('cvs1','#000');
	for (var i=0;i<500;i++){
		var color = Math.floor(Math.random()*16777215).toString(16);
		var p = new particles2.particle({
			color: '#' + color,
			comp: null,
			alpha:1,
			x:(Math.random() * 400),
			y:(Math.random() * 400),
			vx:(Math.random() * 2),
			vy:(Math.random() * 2),
			size:(Math.random() * 6),
			uniform: true,
			omni:false,
			starfield:false
		});
		particles2.addParts(p);
	}
	particles2.updateCanvasWithParts();
});

Managing Multiple iScrolls

28 Jan 2015

i sometimes find myself working on a project where the views require more than a single iScroll. When that happens i always need a method of keeping track of them and destroying them properly as I switch between my views. What I describe here is a simple way to accomplish this.

The first thing to do is to create an array to hold all of my iScroll objects:

;(function(ns){

  // holds all iScroll objects
  var _iScroll = [];

})(this.app = this.app || {});

Next you’ll always need a way to know when the user is interacting with an iScroll so for that purpose I created a boolean variable for my controller to check whenever needed.

;(function(ns){

  // holds all iScroll objects
  var _iScroll = [];

  // is the iscroll scrolling?
  var _isScrolling = false;
  
})(this.app = this.app || {});

And then an iScroll configuration object:

;(function(ns){

  // holds all iScroll objects
  var _iScroll = [];

  // is the iscroll scrolling?
  var _isScrolling = false;

  // iScroll configuration
  var _iScrollSettings = {
    mouseWheel: true,
    scrollbars: true,
    fadeScrollbars:true,
    shrinkScrollbars:'scale'
  }

})(this.app = this.app || {});

With that done I then need a function that will create my iScroll objects and push them into the _iScroll array.

;(function(ns){

  // holds all iScroll objects
  var _iScroll = [];

  // iScroll settings
  var _iScrollSettings = {
    mouseWheel: true,
    scrollbars: true,
    fadeScrollbars:true,
    shrinkScrollbars:'scale'
  }

  /* init_iScroll()
   * Creates any number of iscrolls.
   * @type {Function}
   * @param {String} str - the id of the wrapping element whose contents need to scroll
   * @return {} Returns nothing
   */
  ns.init_iScroll = function(str){
    var len = _iScroll.length;
    _iScroll.push(new IScroll(str, _iScrollSettings));
    _iScroll[len].on('scrollStart',function(){
      _isScrolling = true;
    });
    _iScroll[len].on('scrollEnd',function(e){
      setTimeout(function(){
        _isScrolling = false;
      },500);  
    });
  }

})(this.app = this.app || {});

As you can see within init_iScroll() I feed the CSS ID of the element whose contents must be scrolled to the function. The new iScroll is created and a reference is added to the _iScroll array. Next I configure the scrollstart and scrollend events. Looking at these you can see that when they fire they change the value of the _isScrolling flag. The result is that my application knows when scrolling happens and thus can differentiate between a swipe intended to scroll an iScroll “pane” or a touchend event meant for some other UI element. I of course need to create a getter function to expose _isScrolling to other parts of my app.

Finally, when I create new views I have to completely replace the old with the new which itself may contain its own set of iScrolls. So the first thing I need to do is to be able to correctly remove the existing view’s iScrolls and thus release the memory they consume – failing to do so results in a memory leak so this is an important task. The following function destroys all iScroll objects:

  /* destroy_iScroll()
   * Destroys all iscrolls contained within the _iScroll array;.
   * @type {Function}
   * @param {}
   * @return {} Returns nothing
   */
  ns.destroy_iScroll = function(){
    while (_iScroll.length != 0){
      _iScroll[0].destroy();
      _iScroll[0] = null;
      _iScroll.splice(0,1);
    }
  }

Another aspect of managing iScrolls is that you may want to destroy only specific iScrolls within your views and not just arbitrarily delete all of them. With that use-case in mind we need a means of targeting specific iScrolls – this next function will look for an iScroll object based on the ID of its wrapping element and destroy it:

    ns.findAndDestroyIscroll = function(wrapperID){
        // wrapperID cannot have a leading #
        // see if the iscroll exists, if so, then destroy it and remove from the array of iscrolls
        console.log('before',_iScroll);
        for (var i=0;i<_iScroll.length;i++){
            if (_iScroll[i].wrapper.id === wrapperID){
                _iScroll[i].destroy();
                _iScroll[i] = null;
                _iScroll.splice(i,1);
            }
        }
        console.log('after',_iScroll);
    };

And with that you have everything you could want to manage any situation; a method of keeping track of iScoll objects, creating them, destroying all of them or just destroying specific iScrolls. Below is the complete set of code.

;(function(ns){

  // holds all iScroll objects
  var _iScroll = [];

  // iScroll settings
  var _iScrollSettings = {
    mouseWheel: true,
    scrollbars: true,
    fadeScrollbars:true,
    shrinkScrollbars:'scale'
  }

  /* init_iScroll()
   * Creates any number of iscrolls.
   * @type {Function}
   * @param {String} str - the id of the wrapping element whose contents need to scroll
   * @return {} Returns nothing
   */
  ns.init_iScroll = function(str){
    var len = _iScroll.length;
    _iScroll.push(new IScroll(str, _iScrollSettings));
    _iScroll[len].on('scrollStart',function(){
      _isScrolling = true;
    });
    _iScroll[len].on('scrollEnd',function(e){
      setTimeout(function(){
        _isScrolling = false;
      },500);  
    });
  }

  /* destroy_iScroll()
   * Destroys all iscrolls contained within the _iScroll array;.
   * @type {Function}
   * @param {}
   * @return {} Returns nothing
   */
  ns.destroy_iScroll = function(){
    while (_iScroll.length != 0){
      _iScroll[0].destroy();
      _iScroll[0] = null;
      _iScroll.splice(0,1);
    }
  }

  /* findAndDestroyIscroll ()
   * Destroys specific iScrolls based on the ID of the wrapping element.
   * @type {Function}
   * @param {String} wrapperID - the id of the wrapping element NOT preceded by a hash
   * @return {} Returns nothing
   */
  ns.findAndDestroyIscroll = function(wrapperID){
    // see if the iscroll exists, if so, then destroy it and remove from the array of iscrolls
    console.log('before',_iScroll);
      for (var i=0;i<_iScroll.length;i++){
        if (_iScroll[i].wrapper.id === wrapperID){
          _iScroll[i].destroy();
          _iScroll[i] = null;
          _iScroll.splice(i,1);
        }
      }
      console.log('after',_iScroll);
    };
})(this.app = this.app || {});

I hope you find this useful, happy surfing…..

Using Bing Maps

29 Jul 2014

When you think of online maps most people think of Google Maps, and for good reason. It quickly eclipsed the previous MapQuest standard (is MapQuest still around?) to become the most popular online mapping tool. Bing is a relative newcomer to the mapping scene and is IMO worthy of a look. Its straightforward to implement, mature and it looks just as good as Google Maps. I honestly can’t find a reason (yet) to prefer one over the other.

So, lets get started using Bing Maps… I’ll demonstrate getting your current location, getting location information (address, city, etc) and will wrap up with a practical application that shows the nearest McDonalds to the user’s geolocation.

The very first step is to sign up for a Bing Maps Account so that you can generate a Bing Maps API Key without which your maps won’t work.

Sign Up for a Bing Maps Account

Signing up for Bing Maps account is simple – follow this link and click on “Create A Bing Maps Account” and then find the text link called “Sign up now”.

Obtain a BING Maps API key

Once you have created your account go ahead and log in to the Bing Maps Account center and find the link called “Create or view keys”.

bing_account_center_1

On the next page you’ll have the opportunity to create a new key for use with your Bing Map, the “create key” form looks like this:

bing_account_center_2

You’ll need to add this information to uniquely identify your API key within the Bing Maps portal – you can actually log into the portal at any time to see the analytics associated with your API Key – so if you like to see that sort of data be sure to create a unique key per Bing Map instance.

You can skip the Application URL – the required fields are denoted by an asterisk, which are:

  • Application name – enter a descriptive name for your map key
  • Key Type – you will most likely want to select “Basic” here – this type of key does not have an expiration date
  • Application type – select the proper type

Once you submit the form the page will refresh and you will see your newly created API Key beneath the form:

bing_account_center_3

Plotting your location on a Bing Map

Lets start with finding you’re location and plotting it on a map the example below is what we will be creating (if you see an empty box its because your browser needs permission to allow this page to get your geolocation):

The first thing we need to do to accomplish the above is to get the user’s location. We can do this by accessing the browser’s navigator.geolocation object. Navigator.geolocation is currently supported by the following browsers:

  • IE9+
  • FF 3.5+
  • Safari 5.0+
  • Chrome 7.0+
  • Opera 10.6+
  • iOS Safari 3.2+
  • Andriod default browser as of OS version 2.1+

Note too that when you request the user’s location that the web browser will prompt the user asking for permission to do so – you may have noticed it happen when you loaded this web page.

To get a user’s current location:

...
    navigator.geolocation.getCurrentPosition(function(positionObj){
        console.log(positionObj.coords.latitude + ' - ' + positionObj.coords.longitude);
    }
...

The above will print out your latitude/longitude to the browser’s console. However, it assumes that the navigator.geolocation object exists so we should first do some feature detection, like so:

...

if (navigator.geolocation){
    var positionObj = navigator.geolocation.getCurrentPosition(function(positionObj){
        console.log(positionObj.coords.latitude + ' - ' + positionObj.coords.longitude);
    }
} else {
    // do something useful here
}
...

Cool, now we have the user’s geocoordinates.

Creating the Bing Map

First you need to add the Bing Maps mapcontrol object to your page via a script tag whose source points to the following:

  • ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0
  • And an example of linking it to your page:

    <script src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0">
    

    Next we need to set ourselves up to load the Bing Map when the page loads. Lets setup a new object namespaced to “bingMap” and add in a self-invoking function that adds an onload event listener which waits until the DOM is loaded before calling our private _init method. _init() is where everything will start to happen once the load event fires. We’ll put our navigator.geolocation code in the _init. That all said, this is what we then have:

    ...
    <script src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script>
    <script>
      var bingMap = (function(){
        function _init(){
          if (navigator.geolocation){
            var positionObj = navigator.geolocation.getCurrentPosition(function(positionObj){
            console.log(positionObj.coords.latitude + ' - ' + positionObj.coords.longitude);
          } else {
            // do something useful here
          }
        }        
    
        (function(){
          if (window.addEventListener){ // all other browsers
            window.addEventListener('load',_init,false);
          } else if (window.attachEvent){ // OLD IE
            window.attachEvent('load',_init,false);
          }
        })();
      })();
    
    ...
    

    Next we need to add a DIV container to our page. Lets add one and give it some basic styling:

    ...
    <div id="mymap" style="position:relative;width:100%; height:300px;border:2px solid #555;"></div>
    ...
    

    Ok, time to add out Bing Map to the page. The first thing we need to do is to create a Bing Location Object.

    ...
    <script src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script>
    <script>
      var bingMap = (function(){
        function _init(){
          if (navigator.geolocation){
            var positionObj = navigator.geolocation.getCurrentPosition(function(positionObj){
            console.log(positionObj.coords.latitude + ' - ' + positionObj.coords.longitude);
    
            var loc = new Microsoft.Maps.Location(positionObj.coords.latitude,positionObj.coords.longitude);
    
          } else {
            // do something useful here
          }
        }        
    
        (function(){
          if (window.addEventListener){ // all other browsers
            window.addEventListener('load',_init,false);
          } else if (window.attachEvent){ // OLD IE
            window.attachEvent('load',_init,false);
          }
        })();
      })();
    </script>
    ...
    

    Next we need to create the Bing Map object – the method for creating the map takes a configuration object where you specify your Bing Maps key, the type of map, the center point of the map (your user’s location) and the zoom level.

    ...
    <script src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0">
    <script>
      var bingMap = (function(){
        function _init(){
          if (navigator.geolocation){
            var positionObj = navigator.geolocation.getCurrentPosition(function(positionObj){
            console.log(positionObj.coords.latitude + ' - ' + positionObj.coords.longitude);
    
            var loc = new Microsoft.Maps.Location(positionObj.coords.latitude,positionObj.coords.longitude);
    
    	var map = new Microsoft.Maps.Map(document.getElementById('geo'),
    	  {credentials:YOUR_API_KEY_GOES_HERE,     // Your API Key
    	  mapTypeId:Microsoft.Maps.MapTypeId.road, // The type of map
    	  center:loc,                              // the point around which the map is centered
    	  zoom:12                                  // the zoom level
    	}
          );
    
          } else {
            // do something useful here
          }
        }        
    
        (function(){
          if (window.addEventListener){ // all other browsers
            window.addEventListener('load',_init,false);
          } else if (window.attachEvent){ // OLD IE
            window.attachEvent('load',_init,false);
          }
        })();
      })();
    </script>
    ...
    

    The two configuration object attributes – “mapTypeId” and “center” are not as intuitive as the others so I’ll describe them in some detail.

    “mapTypeId” can take any of the following values:

    Name Description

    aerial

    The aerial map type is being used.

    auto

    The map is set to choose the best imagery for the current view.

    birdseye

    The bird’s eye map type is being used.

    collinsBart

    Collin’s Bart (mkt=en-gb) map type is being used.

    mercator

    The Mercator map type is being used. This setting removes the base map tiles.

    ordnanceSurvey

    Ordnance Survey (mkt=en-gb) map type is being used.

    road

    The road map type is being used.

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”.

Simple Form Validation

16 Jul 2014

A reader posted a comment to my post titled “Mixing jQuery and reCaptcha in Forms” asking about form validation. Instead of adding a large comment to the post in response to her question I thought it best to dedicate a post instead. This is a simple approach – there are many ways to attack the problem.

BTW, I’m use generic JavaScript below – if you are familiar with jQuery then feel free to substitute it where appropriate.

The original question:

This is a great tutorial. It was exactly what i was looking for. The only thing is how to i add additional text fields to validate but only have one alert box or alternatively stop validating once its hit the first error.” ~ Carly

Here’s an approach that I often use variations of. In all cases I start with the form tag – we need to tell the form to fire a function when the user tries to submit it and that this function should return true or false. IF our function returns “true” then the form submits, if it returns “false” then the form does not submit.

<form onsubmit="return validate_my_form()">

Next, we need to write the “validate_my_form()” function.

I first declare the function and then add a simple variable that you can call anything you want, but I like to say something like “isValid”.

function validate_my_form(){
    var isValid = true;
}

The isValid variable will serve as a flag telling us if the form is valid or not. By default it is a boolean set to “True”.

Next I add in an “if” block for each text field in my form – again, this is a very simple example – checking to see if the form has a value – and yet again, you should probably be more stringent, but anyway – if there is no value then I immediately change “isValid” to false. For example:

function validate_my_form(){
    var isValid = true;

    // validate text field 1
    if  (document.getElementById('text_1').value.length == 0){
        // if the field is empty, then there is an error
        isValid = false;
    }
}

Hopefully that’s easy to follow – next – we check to see if “isValid” is valid – if true then we submit the form, if false then we throw an alert:

function validate_my_form(){
    var isValid = true;

    // validate text field 1
    if  (document.getElementById('text_1').value.length == 0){
        // if the field is empty, then there is an error
        isValid = false;
    }
    // next check to see if "isValid" is true or false - false means we have an error
    if (isValid){ // true
        // submit the form
        return true;
    } else { // false
        alert('something is wrong');
        return false; // dont submit the form
    }
}

Now as to your question, we now have a framework so-to-speak for adding any number of fields to validate. Just use the example “if” block for all your other fields.

function validate_my_form(){
    var isValid = true;

    // validate text field 1
    if  (document.getElementById('text_1').value.length == 0){
        // if the field is empty, then there is an error
        isValid = false;
    }
    // validate text field 2
    if  (document.getElementById('text_2').value.length == 0){
        // if the field is empty, then there is an error
        isValid = false;
    }
    // validate text field 3
    if  (document.getElementById('text_3').value.length == 0){
        // if the field is empty, then there is an error
        isValid = false;
    }
    if (isValid){
        // submit the form
        return true;
    } else {
        alert('something is wrong');
        return false; // dont submit the form
    }
}

As you can see – “isValid” is tripped to false at any point during the validation… and we only check for errors at the very end. if “isValid” is false – then we know the form is not valid and we show an alert.

OK – but that doesn’t tell people whats wrong. So then, lets add something else…. lets create a “message” variable to house all of the error messages. As each “if” statement determines the validity of the text fields it will add on the error message if necessary. See below…

function validate_my_form(){
    var isValid = true;
    var message = '' // THIS IS THE ERROR MESSAGE

    // validate text field 1
    if  (document.getElementById('text_1').value.length == 0){
        // if the field is empty, then there is an error
        isValid = false;
        message += 'Text 1 is empty\n';
    }
    // validate text field 2
    if  (document.getElementById('text_2').value.length == 0){
        // if the field is empty, then there is an error
        isValid = false;
        message += 'Text 2 is empty\n';
    }
    // validate text field 3
    if  (document.getElementById('text_3').value.length == 0){
        // if the field is empty, then there is an error
        isValid = false;
        message += 'Text 3 is empty';
    }
    if (isValid){
        // submit the form
        return true;
    } else {
        alert('There has been an error:\n' + message);
        return false; // dont submit the form
    }
}

Again – this is very elementary – you will have to customize per your needs but it is a good starting point.

This could be made simpler by not using “isValid” but only use the “message” valriable. In that case, all you do is check for the length of “message” – and if its bigger than zero you know there is an error message in it and as a result you shouldn’t submit the form and should instead display the “message”.