jQuery RememberState Version 1.1 Released
After seeing a fair amount of traffic on my previous post for my jQuery localStorage plugin I figured I should update it to make it a little easier to use. The rememberState plugin now has options for the localStorage object name, clearing the storage on submit, the ability to send a custom notice block, and a notice selector in the event the notice element already exists in the form. For more info, check out the readme at the plugin’s GitHub page or try the demo form.
Planned future additions include being able to exclude form fields or field types. Let me know if you think it could use anything else.
Saving Form State with jQuery and localStorage
Recently I was working on a project that had a lot of forms each with a large number of inputs, and I was thinking how frustrated a user must feel when they’ve filled out some of a form and for whatever reason they lose that filled in data. Maybe they accidentally clicked a link in the sidebar, or closed the tab, or Firefox died for the twentieth time that day. Whatever the reason, the user then has to navigate back to that page and re-enter the information. I wondered if there was a solution for this using Javascript, and thought localStorage would be a good candidate. Most modern browsers now have a localStorage object, so unless your users are using an old version of Firefox or using IE7, they should all have support for it.
The idea I had involves using jQuery to attach an unload event to the window and store the targeted form’s contents in a JSON object. If you don’t know how localStorage works, check out this article on the Mozilla Developer blog about how it works and its major shortcomings. In short, the only type you can store and retrieve is a string, which doesn’t make a whole lot of sense. That means that integers, floats, regexps, arrays, and objects need some pre-treatment before being stored in localStorage. In the instance of my plugin idea, ideally I would serialize the form and save it to localStorage. To do this, we can use the built in JSON.stringify and JSON.parse methods to convert the serialized object to a string and save it as a localStorage property, then convert it back to an object in order to loop through it. The only gotcha is when you want to store a function expression, you’re (almost) out of luck. JSON.stringify will strip them out, presumably because of potential security issues with parsing the then-stringified Javascript code. But in this context, we’re not doing anything of the sort so we’re good.
I put together a quick plugin that will store this information under whatever property name you give, or if none is given, by the form’s ID. I’ve tested it to work with the most common input types, but have not tested types such as file, range, number, etc. Most of the HTML5 input types should work fine, but for now I’ll only guarantee that it will work with text, hidden, checkbox, radio, select, and textarea form fields. I’ve also written it to prompt the user before it fills in the saved data, so if multiple people are using the same computer or if you really did intend to destroy the partially filled form, you don’t have to restore the form’s state.
You can check out a demo page, download the uncompressed source or grab a packed version for use on your own forms. You can also grab the uncompressed source along with the qUnit tests on GitHub.
When You Should Use jQuery’s Live and Delegate Methods
Recently, I was working on a project at Hashrocket that involved many different types of elements being introduced into the DOM after it was loaded, and most of those elements needed Javascript events to be attached to them when they were written. Since the release of jQuery version 1.3, everyone has heard of and used the .live() method for attaching these elements whenever possible. Live works great if you’re attaching behavior to an element that may show up pretty much anywhere in the DOM tree. However, most times the events being attached are for an element that only appears within a certain context.
“Well, hey, since these list items are only going to need this click event attached if they show up in my #xmas_list ul, why not reduce the scope by using a selector like $(‘#xmas_list li’)? That will speed things up, right?”
Well, that’s better. But reading through the API docs on .live(), we see this:
The handler passed to .live() is never bound to an element; instead, .live() binds a special handler to the root of the DOM tree.
Oh my. So when you write a live event binding like $(“a[rel=external]”).live(newWindow), you’re really attaching an event to the document and triggering this event when the element’s event “bubbles up” the DOM tree to the document. So here’s the basics of what transpires when this link is clicked:
- User clicks the link
- The click event fires on any parent elements and parents of those elements until we finally arrive at…
- the document. An event attached to the document that fires whenever a click event occurs (even on elements that do not fit the live binding selector criteria), a function is called that tests the event’s current target to see if it matches a selector that jQuery is currently keeping track of with a live event binding.
- If a match is found, the corresponding event is called with that element as its context.
Wow. Every click event that bubbles up to the document gets checked? That seems a bit inefficient. Even with the id prepended to the selector chain, this process occurs. How do we get the intended behavior of only listening up to a specified parent node instead? Depending on the version of jQuery you’re using, there’s one or two ways.
The first, which works in version 1.3 and later, is the .delegate() method. This allows you to start at a certain context and live bind events to any newly introduced descendants to that context. The syntax is very similar, with only a minor change to where the target element is specified. Following our Xmas list example, here’s how we’d bind a click event on any list items within it:
$(“ul#xmas_list”).delegate(“li”, “click”, function() { /* do stuff */ });
This ensures that the listener created to check whether or not a list item within the Xmas list was clicked bubbles up only to the Xmas list ul, and not all the way out to the document. A far more efficient way to “live bind” events.
The other way, which I think may be counter to the way many are used to reading jQuery selectors, is calling .live() with the specified context. According to the documentation, this method of passing in a context works with version 1.4 and later. Here’s what the same event binding method would look like using .live() instead:
$(“li”, $(“ul#xmas_list”)[0]).live(“click”, function() { /* do stuff */ });
For those who are wondering what the [0] is for after the Xmas list selector, this is referencing an individual DOM element rather than a jQuery object (or collection of objects). This means that if you had more than one Xmas list on a page (using a class selector in that instance, of course), you would have to switch back to the delegate syntax of live binding anyways. Personally, I think the selector reads wrong, since jQuery selectors are supposed to mimic the way CSS selector chains work. In CSS, you wouldn’t write “li, ul#xmas_list” to style any lis in that particular ul. You’d go from the outside in. Delegate has the same feel.
Now if anyone’s really interested in how .delegate() works under the hood, a great resource to check out is jQuery Deconstructed. Any time you want to see the underlying Javascript behind the methods and properties in jQuery, check out this resource. The specific area we’re going to look at is the delegate function. Expand that accordion and you’ll see this:
delegate: function( selector, types, data, fn ) {
return this.live( types, data, fn, selector );
},
What this shows us is that the .live() method is doing the work all along! In fact, if you check out other jQuery methods like .first() and .size(), you’ll see that many methods are either simple aliases or alternate method calls with additional parameters sent in (see: .detach()). What they are there for is to improve readability. If someone new to jQuery saw .first() being called on a set of elements, it would be pretty obvious what the return would be. However, seeing .eq(0) might not be as apparent.
So when you’re writing your live events using jQuery, always think about whether you should specify a context or not. In theory, your page interactions will be a little bit snappier, especially when you have a large amount of event-driven code.