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 Missing Directives: Focus & Blur Part 2

In the first part of this series about supporting focus and blur events I elected to follow the template in the AngularJS source code for event directives. This proved to be quite insightful as the AngularJS team just released 1.2.0rc1 which now supports these events natively in the same manner. This means you can just drop in the new version, remove the custom directives and any dependencies on these directives should still work! Check it out for yourself!

But to illustrate this point even further, lets continue with a previous discussion from the first part of this series. Namely the discussion about which adverb should be used for these event directives, "on" or "while". In the first part the directives used the "on" approach for each event. But this doesn't mean we still can't support "while". In fact, we already did in the original example by using both "on" events and storing the state in a scoped variable, focused.

We can extrapolate that concept and create a directive that will do this automatically. There are a few ways as always to skin this cat. We could simply just bind the events independently of the focus and blur directives, but that isn't much fun. Alternatively we can use those directives (no need to reinvent the wheel) and just build on top of them. The later approach will also lead us down a new path which will include learning about a couple of very cool tools. Plus, it's more fun.

NOTE: This approach does have one possible issue: without specifying different scope variables to use between various elements within the same scope, they will of course share the same state indicator which would be very confusing. So, basically dumb in equals dumb out.

Okay, on to the fun! First give it a try so you know I am not full of it.



Boom! Still works. Now lets dig in. The first difference is the new directive myng-focused has replaced both myng-focus and myng-blur on the element. Also, only the scope variable focused is passed now and not an AngularJS expression. Once last warning, remember to specify unique scope variables to track states for different elements unless you want to have a field day tracking down that bug.



Now for the juicy parts but if you need to review how the previous directives work, go take a look here.

The myngFocused directive is basically doing four things.
  1. Getting the scope variable name from the attribute
  2. Removing the attribute (this is important due to number 4)
  3. Sets the attributes for the focus and blur directives
  4. Recompiles the element
Steps 1-3 are fairly straightforward although steps 2 & 3 do introduce $set. The trick here is that if no value is specified, the attribute is removed. Useful for step 4.

Step 4 is the key. Without it, the HTML is updated but AngularJS takes no notice of the new attributes on the element. That is where $compile comes into play. It's a fairly powerful and useful service in the AngularJS world and used quite extensively in the source code. We use it here to force Angular to recompile the element which triggers it to recognize the new directives, myngFocus and myngBlur.

IMPORTANT! This is where removing the directive for myngFocused is crucial. If we omitted this step, when the element is recompiled AngularJS would see that directive and would attempt to initiate it. This would put it into a loop. Bad. It's why nearly all examples only use $compile on children of an element. Consider yourself warned.



And there you go. It should really be apparent now how directives are the building blocks of AngularJS.

Finally, as promised, here is the same directive using the new hotness of 1.2.0rc1. As you can see the directive is identical aside from using the new native focus and blur directives.



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.

Thursday, April 26, 2012

The Lion Who Was An Insomniac

Sometime over the last couple weeks my MacBook Pro running OS X Lion (10.7.3) decided it no longer needed to sleep. While peculiar, I didn't worry about it since I had just installed some software recently and figured that might be the reason for my laptop's new found bout of insomnia. Well, two weeks later and my poor little guy hadn't slept a wink.

Everyday I'd close the lid and go about my business only to find it still purring along waiting for my return. While I do love when my pets are excited to see me, this seemed to be a bit of a waste. So, like any good techie, I backtracked everything I had done over the last two weeks to see what could be the pea in my laptop's mattress. I checked the software I installed (nope), the updates I applied (nope), the accessories I had plugged in (nope), the second monitor (nope), on and on but nothing seemed to be the thorn in his side.

Now, I could have just restarted my computer to see if that resolved the issue. Two things prevented me from doing this:
1. I am not on Windows.
2. Where is the fun in that?

So, digging into my techie war chest it was time to hit the old Terminal and get under the hood. First things first, pmset is a godsend for this exact issue. If something is keeping your mac awake, BAM! you know what it is. Why I didn't just do this from the beginning I don't know aside from maybe I am a masochist.

$ pmset -g assertions
4/25/12 11:35:13 PM EDT 
Assertion status system-wide:
   ChargeInhibit                           0
   PreventUserIdleDisplaySleep             0
   PreventUserIdleSystemSleep              1
   NoRealPowerSources_debug                0
   CPUBoundAssertion                       0
   EnableIdleSleep                         1
   PreventSystemSleep                      1
   DisableInflow                           0
   DisableLowPowerBatteryWarnings          0
   ExternalMedia                           0

Listed by owning process:
  pid 50: [0x0000012c00000032] PreventSystemSleep named: "org.cups.cupsd"

The last line in the output is the smoking gun. A cupsd process was preventing my laptop from taking a much deserved nap.

Who, what and why? CUPS allows macs to act as print servers essentially allowing the computer to queue up print jobs. So, more than likely there was a stalled print job that was keeping the process open, waiting to complete, before allowing the system to go to sleep (which makes logical sense to prevent a print job from being interrupted).

I opened up my print queue from the preferences pane and voila, a stalled job was sitting there mocking me. After quickly killing this unruly guest and checking pmset again:

$ pmset -g assertions
4/25/12 11:37:05 PM EDT 
Assertion status system-wide:
   ChargeInhibit                           0
   PreventUserIdleDisplaySleep             0
   PreventUserIdleSystemSleep              0
   NoRealPowerSources_debug                0
   CPUBoundAssertion                       0
   EnableIdleSleep                         1
   PreventSystemSleep                      0
   DisableInflow                           0
   DisableLowPowerBatteryWarnings          0
   ExternalMedia                           0

No more processes or assertion statuses indicating the system was preventing itself from sleeping. So, with baited breath I closed the lid and waited a couple seconds...pure silence. No purring, no whirling, just silence. My laptop had finally been cured of it's insomnia.

Wednesday, January 11, 2012

The problem with document.cookie

Accessing/modifying HTTP cookies with JavaScript is possible through the document.cookie interface which permits two operations: retrieving all of the cookies that are accessible from the current document domain and path; and setting/updating an individual cookie. This interface is limited and can cause unexpected behavior if used without a thorough understanding of the interface and the W3C spec for HTTP cookies.

Browser access/management of cookies is specified by the RFC 2109 spec which states:
  • Cookie uniqueness is controlled by the combination of the cookie's name, domain, and path.
  • When setting a cookie with a specified domain, it must begin with a dot (.jasoncust.com).
  • The domain will default to the host domain's location (www.jasoncust.com).
  • The path will default to host domain's path up to but not including the right most '/'.
  • Cookie access is restricted to the cookie path being a prefix of the document's full path (since paths were originally directory structures, cookies were accessible only from subdirectories).
  • Similarly, cookie access is restricted to the cookie domain being a suffix of the document's domain. Note this is only true for cookie domains starting with the required dot (.jasoncust.com). If a cookie domain was set by default to say 'jasoncust.com', the cookie would not be accessible from any subdomains.
That last two points could use a little more elaboration as people tend to gloss over them. Regarding just domains (assuming all cookie paths are set to '/'), if your document location is 'www.jasoncust.com', only cookies with domains set to either 'www.jasoncust.com' or '.jasoncust.com' are accessible. Any other subdomains including 'jasoncust.com' are not accessible. Combining paths with domains, if the document location is 'www.jasoncust.com/some/path/', only cookies with a domain of 'www.jasoncust.com' (or '.jasoncust.com') and paths of '/', '/some' or '/some/path' are accessible.

Retrieving cookie values is easy enough through the interface, simply go to www.nytimes.com, open a console and then enter document.cookie to see a string containing all of the cookies accessible from the current host domain and path. It will look something like:
> document.cookie
"RMID=0734175263b54f0d07f7801a; adxcs=s*2b53d=0:1; adxcl=t*2b53d=4f32014f:1326254071|ti=4f32014f:1326254071"
The format of the returned string is name=value with multiple cookies joined by a semicolon + space ('; '). So in order to get a particular cookie's value, a bit of parsing is required. One major caveat is only the cookie's value is returned. The cookie's path, domain, secure setting, and the expiration/max-age are not returned through the interface which can be problematic.

Why is this problematic? Lets walk through an example, albeit a trivial one, to illuminate this issue. Starting with two cookies:

name value domain path
uuid 19691231 .nytimes.com /
xid 42 .nytimes.com /

If the document location is 'www.nytimes.com', then the returned cookies will be:
> document.cookie
"uuid=19691231; xid=42"
To set or update a cookie a string representing the cookie is assigned to document.cookie. For example, to update the 'xid' cookie to 'lorum', a first approach might look like:
> document.cookie = "xid=lorum";
Checking the cookie value returns:
> document.cookie
"uuid=19691231; xid=42; xid=lorum"
What happened? There are now two cookies with the name 'xid'. Looking at the table again we see why this happened:

name value domain path
uuid 19691231 .nytimes.com /
xid 42 .nytimes.com /
xid lorum www.nytimes.com /

The two cookies while sharing the same name have a different subdomain. Since the domain wasn't specified in the assignment, it defaulted to the host domain ('www.nytimes.com') in the example. So in order to update an existing cookie the same domain and path originally assigned to the cookie must be used. However no meta data is returned via document.cookie. This means any cookies that are to be manipulated by JavaScript need to have a set domain and path (probably the root domain and path) that both the server and the JavaScript code use when setting cookies. This also applies to any optional cookie settings such as the expiration and the secure flag.

Assuming for some reason it was desirable to have two cookies with the same name but a different subdomain and/or path, how do you know which cookie is what? Since document.cookies does not return the domain or path of the cookies, there is no way to tell. This may seem trivial, but what if an operation needed to clear or overwrite the 'xid' cookie with the value 'lorum'? How would it do so?

tl;dr

When using the document.cookie interface, the lack of any meta data about the cookies returned can cause issues if not properly designed around. Two key points to remember are:
  1. Cookies with the same name but different subdomains and/or paths are allowed but this meta information is not returned by the interface. So there is no way to tell which cookie value is for what domain/path.
  2. When updating a cookie, the original cookie is replaced if the name/domain/path match. If they don't match, a new cookie with the same name but different domain/path will be created. Also, when replacing a cookie, any other meta data is overwritten even if not explicitly stated.
Should there be an update to the interface for ECMA5? I'm not positive since cookies should only be used if the data needs to be included in every request to/from the server. Otherwise newer APIs such as the Storage APIs probably are a better design choice and don't have this issue. Still, it would be a nice addition to have access to the meta data for each cookie when you do need to work with them from both the client and the server.