Thanks to visit codestin.com
Credit goes to blog.jasoncust.com

Showing posts with label JavaScript. Show all posts
Showing posts with label JavaScript. Show all posts

Thursday, August 15, 2013

AngularJS Missing Directives: Native Form Resets Part 2

This is a followup to a previous post discussing a directive that supports native form resets. While that directive goes a long way for simplifying form resets with AngularJS, it does have a glaring shortcoming. What if a form utilizes multiple models? AngularJS makes no judgement about this and actually there can be various reasons for why that is beneficial.

The previous solution expected to have one and only one model. In fact it should throw an error if more than one model is passed. Here is the original code from the original reset directive.



To support multiple models in an updated directive lets first make a checklist for what would need to be updated.
  • A string format to pass in multiple models through the form attribute
  • Create getter/setters for each model
  • Create a master copy of each model
  • Set each model to its respective master copy when the reset event fires
The attribute string format is simple enough since there is an established format for specifying a list, namely comma separated. But since this list is also an HTML attribute, it should include the format supported by lists contained in HTML attributes, namely space separated (think CSS class attributes). This way either format will work if favored by a designer or a developer.



Since the attribute string will be a list, the directive will be dealing with an array of models to iterate over for creating both the getter/setters and the master copies of each model. Also it will iterate over the setters to set the corresponding models to their master copy during a reset event.



And low and behold the result. It works! It would have been a bit awkward if it hadn't.

In the end there was nothing very tricky about it but it can be extremely useful.



NOTE: I borrowed the map function from the AngularJS source since IE 8 does not support Array.prototype.map. Once again the source code proves it's a great place to pick up some tips and tricks.

Wednesday, August 14, 2013

AngularJS 1.2.0rc1 Now Supports Focus & Blur Events

A quick update on a previous post about focus and blur directives. The AngularJS team has released 1.2.0rc1, which among a host of great changes now supports focus and blur events natively.

The best thing is since I used the existing event code from the AngularJS source code as a template for these directives there is no need to change any code that utilized these methods! You will see another example of this in a post that I am editing that was scheduled to be posted later today.

Here is the updated example from my previous post with the only modification being the switch to use the native focus and blur directives in 1.2.0rc1.







Once again using the source code as a guide and a reference has proven to be both enlightening and also a savior since I won't have to make any extensive edits to switch to the native event support in the new release.

Monday, August 12, 2013

AngularJS Missing Directives: Focus & Blur

After my last post discussing the missing native form reset directive, I was asked what other missing directives there might be. Which is a great question. AngularJS provides so many great directives out of the box, what could it possibly be missing?

Well, lets start with simple user input events. AngularJS 1.1.5 supports the following events as ng-directives:
* There is of course also ng-submit, which was mentioned in the last post.

While that list is pretty extensive there are two glaring omissions, namely blur and focus events. Luckily these can be easily added as you will see below.

Since there is nothing uniquely special about these two events compared to the list above, we can use the AngularJS source code as a template for adding two directives to support these events. The bonus for doing that is extrapolating and slightly modifying the code for our goals is quick and painless. Lesson to be learned: always look to the source for tips and guidance.

First, a note discussing this approach. I have read various development threads discussing how a focus and/or a blur directive should work. The main debate seems to boil down to a disagreement between essentially adverbs. Specifically 'on' and 'while'. The latter assigns a state indicator that can be monitored to determine if an element is currently blurred or focused. The former, which is the approach the following example will use, follows the native browser event listeners for firing when an event occurs.

Got it? Good. Lets jump in then. Click on the input element to give it focus and then click outside of it to blur it.



It works, but how? Focusing on the HTML tab we can see the directives are simply passing an AngularJS expression that sets the scope variable focused to either true or false if the input element is focused or blurred respectively.



Looking at the directive code on the JavaScript tab, the first thing you will notice is rather than repeating identical code for each directive I use an angular.forEach to loop through both events and dynamically create a directive for each one.



Again, there is nothing very special about the code in this directive. In fact anything of interest was discussed in the last post (review here if you need to). Basically the directive will evaluate whatever the AngularJS expression is when the event fires.

NOTE: If you want to do something only when an element has focus (or blurred) you would need to use both directives to monitor the element as this example does.

UPDATE (2013-08-13): AngularJS 1.2.0rc1 now includes blur and focus events! Wooohoooo! Check out the follow-up post to see how it compares.

UPDATE (2013-8-26): Part 2 - Supporting the 'while' adverb through a directive.

Friday, August 9, 2013

AngularJS Missing Directives: Native Form Resets

If you haven't played around with AngularJS yet, I highly advise you give it a try. There are plenty of introductory articles and videos out there that will whet your appetite and get you up and running (here, here and here to name a few). Go ahead, go play. This post will still be here later when you need it. And you will need it.

Okay, for everyone else, after the excitement and wonderment of two-way binding begins to wane you will start to notice some of the peculiarities about AngularJS. And then after that you will start to wonder why it doesn't do some of the things you would expect to it to do (the simple answer is generally the great dev team working on AngularJS hasn't gotten around to it yet). I refer to these missing functionalities as missing directives. Which means we can write our own directives to add any desired functionality!

Lets jump in and take a look at form resets. If you have been developing for the web for any period of time you know that there is a native INPUT element type that will reset all form input elements (including selects, textareas, etc.) to their original values. The following fiddle has a simple form that includes a native reset button and an Angular controller to initialize the model ("myModel").

Try editing the fields and hitting reset.



Wait. What happened? The expected behavior is for the form to reset the values of the input fields ('foo' and 'bar') to their original values ('Boop' and 'Beep' respectively). But as you can see this does not happen. What gives?

First, two underlying points:

  1. Since AngularJS forms do not use the value attribute to specify original values for forms, the native form elements therefore have no initial values outside of the AngularJS world.
  2. AngularJS forms do not understand which model is being used to manage the two-way binding for the input fields (this example uses 'myModel').

Second, you might be wondering why this is how AngularJS behaves. I did and so at a recent AngularJS meetup I asked Igor Minar, a core Google Angular developer, why it hadn't been implemented and he responded that the team just hadn't done it yet.

So, that leads to the question of what would be needed to make it work. Interestingly enough it's not a difficult thing to do once you have a firm understanding of AngularJS and its concept of directives. If you don't know what a directive is, this should be a good example.

Based on the points mentioned above, the directive would need to be:
  • aware of which model is represented by the form
  • what the original input element values are (the original state of the model)
  • capturing the native form reset event and preventing it from reseting the inputs to no values
  • restoring the original values to the form input elements from the model on the form reset event

Since directives can be attributes of an element, the directive can be an attribute of the form element itself similar to the ngSubmit directive. We then can use the attribute value to pass in the model for the form. This placement will also allow the capture of the native form reset event.

Lets preview with the finished directive before we dissect it. Once again, edit the form and hit the reset button.



Whew! It works.

If you notice on the HTML tab, the directive is invoked by applying an attribute to the form element with the value set to the model. The directive is then called and executes the returned function.



From the JavaScript tab we can see how the directive accomplishes our goals.



But first, some background on some of the key pieces used in the directive:

  • $parse - since you can't pass the model object itself through the attribute (at least without creating an isolated scope), you can use this handy dandy service to get the model object from the attribute value string
  • angular.copy - creates a deep copy of an object or an array
  • scope.$apply - executes the model assignment in an AngularJS context
  • $setPristine - used for setting the form to a 'pristine' state indication

We can see the first thing the directive linking function does is to create a getter/setter for the passed model and then it creates a deep copy of this model. This deep copy will be the value that the model is reset to on any form reset events. Since $parse will only return an object with method assign if it is a model, we can use that to error check the input from the attribute.

The next step binds the custom reset handler to the native form reset event. When the native form reset event is fired, the custom reset handler prevents the default action (resetting the input elements to their non-existent value attributes), sets the model to the original copied state and finally updates the form state to indicate it is pristine.

That's it. That's all it takes. Nothing more. If you haven't realized it yet, while two-way binding is magical, directives are the building blocks of AngularJS.

Friday, January 6, 2012

IIFEs, Closures, Unit Testing and Privacy

One of the toughest decisions within JavaScript code design is the choice between testability and privacy. It's definitely one of the most frustrating aspects of JavaScript for me personally (and that is definitely saying something). This choice can lead to a less than ideal design if unit testing is important to you... which it should be.

Lets start with an example to see how this choice can affect design. We are going to create an object that will represent a basic parallelogram that requires as input the measurements of two adjacent sides and an angle. Assuming the second measurement is the base, the object will internally compute the height and area of the parallelogram from the inputs on object creation exposing the original measurements and the two new computed values.

A typical design pattern for an object in JavaScript is to use an IIFE (Immediately Invoked Function Expression) to create a closure so we don't pollute the global namespace. A simple design (we aren't concerning ourselves with optimizations for this discussion) could look like:
(function( win, undef ) {
  // Global namespace object name
  var objName = 'myAwesomeParallelogram';

  // Compute the height of a parallelogram where length is the adjacent side
  // of the base and the angle is acute in radians length * sin(angle)
  function height( length, angle ) {
    return length * Math.sin( Math.min( angle, 180 - angle ) * Math.PI/180 );
  }

  // Compute the area of a parallelogram b * h
  function area( base, height ) {
    return base * height;
  }

  var obj = function( sideA, sideB, angle ) {
    if ( angle < 0 || angle > 180 ) {
      throw new SyntaxError('Angle is not between 0 and 180 degrees');
    }

    this.sideA = sideA;
    this.sideB = sideB;
    this.angle = angle;

    this.height = height( sideA, angle );
    this.area = area( sideB, this.height );
  };

  win[ objName ] = obj;
})(window);
Sample usage:
> var p = new myAwesomeParallelagram( 15, 12, 45);
undefined
> p.area
127.27922061357853
> p.height
10.606601717798211
Great! But aside from testing the object itself, how would we be able to test if height or area work correctly? How would we test the code flow? We could redesign our code to expose these functions and we could likewise include some logging calls to console to show the flow. But, is this what we want in production? Is it necessary?

There have been a few clever ways of handling this dilemma (I know I rolled a few of my own over the years) but Mr. Douglas Crockford (the author of JavaScript: The Good Parts and JSLint) has just released a new tool to help end the Sophie's choice between good design/security and testability. It's called JSDev and it allows you to include specially formated comments that can be transformed into development unit testing code and then removed through normal minification processes.

To use the new tool we first need to download the raw file (or use git) and compile it locally for a CLI.
$ curl 'https://raw.github.com/douglascrockford/JSDev/master/jsdev.c' -o jsdev.c && gcc jsdev.c -o jsdev
Now that we have a compiled version of jsdev we can edit our original object code to include development only code for unit testing.
(function( win, undef ) {
  // Global namespace object name
  var objName = 'myAwesomeParallelogram';

  // Compute the height of a parallelogram where length is the adjacent side
  // of the base and the angle is acute in radians length * sin(angle)
  function height( length, angle ) {
    return length * Math.sin( Math.min( angle, 180 - angle ) * Math.PI/180 );
  }

  // Compute the area of a parallelogram b * h
  function area( base, height ) {
    return base * height;
  }

  var obj = function( sideA, sideB, angle ) {
    if ( angle < 0 || angle > 180 ) {
      throw new SyntaxError('Angle is not between 0 and 180 degrees');
    }

    this.sideA = sideA;
    this.sideB = sideB;
    this.angle = angle;

    this.height = height( sideA, angle );
    this.area = area( sideB, this.height );
  };

  // JSDev code comments
  /*dev
    obj.height = height;
    obj.area = area;
  */
 
  win[ objName ] = obj;
})(window);
Now if we process it through or handy dandy new tool:
$ ./jsdev dev -comment "Development Version" < input.js > output-dev.js
The new file, output-dev.js looks like:
// Development Version
(function( win, undef ) {
  // Global namespace object name
  var objName = 'myAwesomeParallelogram';

  // Compute the height of a parallelogram where length is the adjacent side
  // of the base and the angle is acute in radians length * sin(angle)
  function height( length, angle ) {
    return length * Math.sin( Math.min( angle, 180 - angle ) * Math.PI/180 );
  }

  // Compute the area of a parallelogram b * h
  function area( base, height ) {
    return base * height;
  }

  var obj = function( sideA, sideB, angle ) {
    if ( angle < 0 || angle > 180 ) {
      throw new SyntaxError('Angle is not between 0 and 180 degrees');
    }

    this.sideA = sideA;
    this.sideB = sideB;
    this.angle = angle;

    this.height = height( sideA, angle );
    this.area = area( sideB, this.height );
  };

  // JSDev code comments
  {
    obj.height = height;
    obj.area = area;
  }
 
  win[ objName ] = obj;
})(window);
Before was continue there are a few things to notice between the input files, the command and the output file:
  • The comment from the CLI is now a header comment in the output file. This is handy to always include the type of output file in the file.
  • The other arguments for the CLI specify which multi line comments that match the format /*<argument> <code>*/ to include in the output file. This is handy if you have different testing levels or needs. Note that there cannot be a space between the opening comment notation and the argument.
  • Single line comment notations ("//") are unaffected by the jsdev tool as well as multi line comments that don't match the specified format above.
Now lets see if we can access our development code private functions using a console.
> myAwesomeParallelogram.area
  function area( base, height ) {
    return base * height;
  }
> myAwesomeParallelogram.height
  function height( length, angle ) {
    return length * Math.sin( Math.min( angle, 180 - angle ) * Math.PI/180 );
  }
And when we minify the original code we get this (using JSMin for this example):
(function(win,undef){var objName='myAwesomeParallelogram';function height(length,angle){return length*Math.sin(Math.min(angle,180-angle)*Math.PI/180);}
function area(base,height){return base*height;}
var obj=function(sideA,sideB,angle){if(angle<0||angle>180){throw new SyntaxError('Angle is not between 0 and 180 degrees');}
this.sideA=sideA;this.sideB=sideB;this.angle=angle;this.height=height(sideA,angle);this.area=area(sideB,this.height);};win[objName]=obj;})(window);
We no longer need to sacrifice our design and security for testability. Is it the most elegant solution? Maybe not, but it works well and can be easily added to a build process for testing and will not impact your current build process for production.

For more information and more usage info, please refer to the README file on github.

Sunday, January 1, 2012

Can you see me? Can you see me now?

What is the Page Visibility API and what is it good for? It is a JavaScript API that allows developers to check if their page/application is visible and to attach an event listener for visibility state changes. What is that useful for? Well, lets discuss web applications in general for a moment to see how and why it is something developers should be concerned with.

Every window or tab a user opens for services like Google+, Facebook, Gmail, Twitter, and Reddit increases the resource requirement for each application to poll, update, animate and perform other intensive operations. The big question is do end-users really have to pay the resource costs for each of these web applications?

To answer this question, we can ask a few more questions about web applications in general:
  1. Do they need to constantly run?
  2. What operations need to be run and at what rate?
  3. Can these operation rates be altered based on interaction types/levels?
  4. What does user interaction entail?
First, let me state this is not a blanket solution nor an advocacy that all applications should be handled in this manner. That said, developers should think about not only how their applications are actively used but also what they need to do when not in active use.

Second, lets use a simple example to provide a context for pondering these questions. A basic news or message feed would work well enough. It is based around a data stream that is updated both asynchronously and irregularly so some form of polling and updating the stream with new items is required. Also, dynamic animation showing new items will be used to display these updates to an end-user.

Finally, the first three questions are in one way or another predicated on the answer for the fourth so we will begin there.

What does user interaction entail? The obvious cases are when a user is interacting with the application using an input device (mouse, keyboard, mic, camera, finger, stylus, etc.). But can someone use an application without interacting with it directly? What if it is open and updating with new items that the user reads as the show up on the screen? So, the commonality amongst all of these use cases for our application is that the user needs to see the application in order to use it.

Thinking about that a bit more generally, consider all of the tabs a user has open at any given moment. If only one is visible at a time, why do the rest need to do anything at all (aside from pending operations a user has queued up)? Shouldn't the visible tab be the sole active window?

So a more general question arises: if a user cannot see our application, does it need to run if at all?

For our application at least, this makes perfect sense. Why waste resources continuously polling and updating (especially with animations) if the user can't see it? Couldn't we fall back to less frequent polling with no special effects for updating the page? Seems reasonable with little to no user impact. In fact, since the user can't see the page, how would they know.

The only problem is how do we know if the user can see the page? Well, thanks to the Page Visibility API proposal, we can do this today (through browser prefixes at the moment). The API is available with a webkit prefix in Chrome 13+, a ms prefix in IE10+ and a moz prefix in FF10+.

For the example below, using the non-prefix version (which will be the standard) and only checking if the document is visible or not.
function pageVisibilityChanged() {
  if ( document.hidden ) {
    // Application is not visible to the user
    // Adjust polling rates and display update for inactive display mode
  }
  else {
    // Application is visible to the user
    // Adjust polling rates and display update for active display mode
  }
}

document.addEventListener( 'visibilitychange', pageVisibilityChanged, false );
That's it. Really. That's all you need to do to be a great application neighbor and also help reduce energy use by reducing unnecessary resource use by your application.

UPDATE: Here is a browser prefix version for those who want to play today with Chrome 13+, IE10+ and FF10+ and any browser that implements the standard.
var visibilityAPI = ( typeof document.hidden != 'undefined' && { 'hidden': 'hidden', 'visibilitychange': 'visibilitychange' } ) || ( typeof document.webkitHidden != 'undefined' && { 'hidden': 'webkitHidden', 'visibilitychange': 'webkitvisibilitychange' } ) || ( typeof document.mozHidden != 'undefined' && { 'hidden': 'mozHidden', 'visibilitychange': 'mozvisibilitychange' } ) || ( typeof document.msHidden != 'undefined' && { 'hidden': 'msHidden', 'visibilitychange': 'msvisibilitychange' } );

function pageVisibilityChanged() {
  if ( document[ visibilityAPI.hidden ] ) {
    // Application is not visible to the user
    // Adjust polling rates and display update for inactive display mode
  }
  else {
    // Application is visible to the user
    // Adjust polling rates and display update for active display mode
  }
}

document.addEventListener( visibilityAPI.visibilitychange, pageVisibilityChanged, false );