XMLPlayground.com - my new project
So I finally got this off the ground. Months in the making, this is a sandbox environment for XML development, including (E)XSLT, XQuery (to a point), DTD, Schema and more.
It's sort of a JS Fiddle for XML. Sessions can be saved, resumed and shared. Output is generated and optionally styled with CSS.
I built it primarily because in my current job I'm working a lot with XSLT (1.0 - the horror) and found I could do with a test environment.
To that end it's probably XSL it's best suited to. XSL includes are supported and parameters can be passed into it. Your generated output can also pass parameters back into your XSL, allowing your output to be dynamic and responsive.
The downside is it's built in PHP, which is never the best choice for XML technologies.
For a start there's no XSL 2.0, nor any support for XQuery. The latter is mitigated to a point by the use of XQIB (XQuery in the Browser).
Here's a demo session showing how XML, XSL and CSS combine to make a sortable table of Soviet composers.
post a commentA look at native JSON support in ECMA5
A few weeks ago I started a series of posts on what we as JavaScript developers took delivery of with ECMA5. I recently gave a talk on the native support for JSON that arrived as part of ECMA5, and that's what I'll be looking at today.
Previously, of course, handling JSON data meant using something like jQuery or the JSON.org library. But JSON has grown hugely as a serious means of serialised data transfer that native support for it in JavaScript was very much necessary.
ECMA5 defines an object of the global scope, JSON. It comes with two rather predictable methods: parse() and stringify().
JSON.parse()
By far the simplest of the two is parse(), which, as its name suggests, parses a wel-formatted string of JSON data into a usable, traversable JavaScript object.
1var str = '{"foo": ["bar", "baz"]}',
2 obj = JSON.parse(str);
3console.log(obj.foo[1]); //baz
So far, so simple.
JSON.stringify()
Things get interesting with the second method, stringify(), however. This does the reverse operation: a JavaScript object is turned into a JSON string.
1var obj = {foo: ["bar", "baz"]},
2 str = JSON.stringify(obj);
3console.log(typeof str); //string
Remember JSON is derived from but not synonymous with JavaScript objects, and as such cannot accommodate certain data types. When stringifying your objects, note that any values that are not strings, numbers, booleans, null, arrays or objects, will be cast either to strings, nulls or, in the case of REGEXP objects, objects.
1var obj = [undefined, function(){}, new Date(), new RegExp()],
2 str = JSON.stringify(obj);
3console.log(str); //[null,null,"2012-05-18T21:39:58.333Z",{}]
A point worth making is that this method, perhaps more than any other, marks JavaSript's move away from its foundings as a rather limited, sober API.
For one, the advent of methods whose names have the "-ify" suffix is a clear sign of influence of third-party libraries. (Ten years ago, this method would have been called toString()).
Secondly, its optional second and third arguments are somewhat bizarre.
Replacer functions
The second argument to stringify() is a function or array which controls how and if a piece of data from the object makes it to the string.
As a function - called iteratively and recursively on each property - whatever you return, provided it's not undefined, gets through. (Interestingly, only undefined prevents this - no other falsy value.
1//allow only dogs through, no cats
2var obj = [
3 {name: "Fido", type: "dog"},
4 {name: "Flossy", type: "cat"}
5 ],
6 str = JSON.stringify(obj, function(key, val) {
7 if (!val.type || val.type == 'dog') return val;
8 });
So like with any filter or transformer, it's all about what you return. You might even want to tweak the value en-route.
As an array, it acts as a simple whitelist of properties names - not of values.
1//keep only animal names - not interested in type
2str = JSON.stringify(obj, ['name']);
3console.log(str);
There's a couple of things to note when using replacers. Firstly, remember the replacer is called recursively - and where your replacer is a function, this means starting with the object itself. I've known this catch people out who expect it to begin from within.
In other words, the first thing passed to the replacer is the object itself, so you will need to return a value for it.
1var obj = [
2 {name: "Fido", type: "dog"},
3 {name: "Flossy", type: "cat"}
4 ],
5 str = JSON.stringify(obj, function(key, val) {
6 if (val.type == 'dog') return val;
7 });
8console.log(str); //undefined
There we wrongly assume that the replacer starts work inside the object, i.e. on the first animal object. It doesn't; it starts on the array. Since our replacer allows data through only if it has a type property equal to "dog", our outer array clearly fails that test, so no value is passed through for it.
Recursive also means it will be called on any objects or arrays the replacer returns, whether it is merely passing them on from the source object or has itself created them.
Secondly, you can prevent properties from being transferred to the string only when dealing with objects, not arrays. If you return undefined from your replacer function and your source object is an array, the value will still go through, but arrive as null.
1//attempt to remove the cat
2var obj = ['dog', 'cat'],
3 str = JSON.stringify(obj, function(key, val) {
4 return val != 'cat' ? val : undefined;
5 });
6console.log(str); //["dog", null]
Thirdly and finally, replacers enact some curious data type coercion. Even though JSON is perfectly capable of handling numbers and booleans, if your replacer returns one of these the value will be cast into string form.
1var obj = ['something'],
2 str = JSON.stringify(obj, function() { return true; });
3console.log(str+' ('+typeof str+')'); //"true (string)"
Spacers
The third and final argument JSON.stringify() accepts allows you to indent the outputted string with a given padder.
You can pass either a string or a number. In the case of a string, that string is used as the padding. In the case of a number, that number of white spaces will be used for padding.
For some reason, the length of the padder is limited to ten, so if your string is longer it will be truncated, else if your number is over ten it will be limited to ten.
1var obj = {"foo": ["bar", "baz"]},
2 str = JSON.stringify(obj, null, 6);
3console.log(str);
That outputs in the following format:
1{
2 "foo":[
3 "bar",
4 "baz"
5 ]
6}
The interesting (but obviously necessary) thing here is that passing a spacer means the output is automatically broken into lines. (It would otherwise have been a single line.)
Et voila
While this is all great stuff, I have to confess I'm still a little shocked at the downright convenience of the whole thing. JavaScript developers just aren't used to that from the JS API!
The replacer argument in particular is downright luxurious, to the point of being of questionable necessity. If you really wanted to sanitise the object before turning it into a string (which in itself would surely not happen that often), you could easily do so yourself by making a copy and looping over it, changing it as required.
Don't get me wrong, I'm all for this brave new world of lavish, PHP-esq, rich API. I'm just taking a little while to acclimatise...
1 comments | post newDistinguishing real events from simulated events
Simulated events are one of the real joys of jQuery. Geniusly, to simulate an event in jQuery you simply call the method relating to the event in question (or use the trigger() method) without any arguments.
1//set up the event
2$('#tabs').on(''click', 'li', function() {
3 alert('Hello!');
4});
5
6//simulate the event immediately on the first LI
7$('#tabs li:first').click();
This is a common situation. Here, I have some tabs which, when clicked, show a corresponding tab content area, which are all hidden to start with. By simulating the event on the first tab, it is automatically turned on as though we had clicked it ourselves.
(Yes, I could avoid this by having the first tab area showing by default via CSS, but let's imagine our event handler does more than simply show and hide DIVs, but also some more complex code).
So far, so easy, and so common. But how do you differentiate a simulated event from a real one?
I had a situation the other day where I had a carousel-like widget on the page, and a bigger version of the same carousel, hidden, to appear in a lightbox should the user wish. Traversing one of the carousels (i.e. changing slide) should update the other one, too.
Consider the following.
1var carousels = $('.carousel');
2
3//listen for clicks to carousel left & right buttons
4carousels.on('click', 'button', function(evt) {
5
6 //get this carousel, other carousel and click direction
7 var this_carousel = $(this).closest('.carousel'),
8 other_carousel = carousels[$(this).is('#carousel1') ? 'first' : 'last'](),
9 direction = $(this).is('.left') ? 'left' : 'right';
10
11 //simulate the same click on the other carousel
12 other_carousel.find('button.'+click_direction).click();
13
14 //more code here...
15
16});
There, we listen for clicks to the left/right buttons of each carousel and then simulate the same click on the other carousel.
But there's an obvious problem; the simulated click uses the same event handler, so it too will invoke a simulated click on the first carousel, and so on and so on, without end.
What we actually want, of course, is for a simulated click to occur only in response to a real click. In other words, how do we spot the difference between a real and simulated event with jQuery?
Simple: with a simulated event, the originalEvent property of the event object will be undefined, whereas with a real event it will be the native event object.
1$('#tabs').on('click', 'li', function(evt) {
2 if (evt.originalEvent) {
3 //real event
4 } else {
5 //simulated event
6 }
7});
Obviously you don't have to simulate events - you could assign their callbacks to a named function and call the function manually. But then you'd have no event object passed, so this often means filling your code with lots of conditions - to work both as an event callback and as a direct function call. Simulated event calls save the need for this reengineering.
post a commentNon-AJAX use for jQuery's deferred objects
I'm currently writing another article for .NET magazine on the newer features of jQuery, for developers who perhaps got comfy with jQuery 1.3 or 1.4 and didn't keep up.
One of the obvious candidates for the article is deferred objects, which landed in jQuery 1.5 as part of the overhaul to jQuery's AJAX module.
AJAX is the obvious use-case for deferred objects, and it's simple to come up with examples for that. But I was also trying to show a non-AJAX example.
I'm talking about cases where you would manually make your own deferred objects and apply subjective logic as to whether, when and how it is resolved or rejected. So other forms of asynchronous code execution.
This is quite a different beast from using deferreds with AJAX, since, at least usually, jQuery's AJAX methods automatically create, return and resolve/reject deferred objects for you. In other words, you can use deferreds in an AJAX context without ever going near methods like $.Deferred(), $.when() or deferred.resolve().
Click the vowels
I eventually came up with a slightly contrived game for children where they have to identify and click the vowels in a sequence of words. Each vowel would constitute a deferred object. When clicked, the vowel fades out and its deferred object is set to resolved. When all deferreds are resolved (i.e. all vowels have been clicked), we give feedback and move on. I think it's quite a nice pattern.
You can see a demo of the game here.
First, some simple HTML:
1<h1>Click all the vowel letters</h1>
2<div id='words'></div>
And CSS:
1#words { height: 100px; }
2#words div { display: inline-block; width: 100px; height: 100%; text-align: center; font-size: 50px; line-height: 90px; margin-right: 10px; background: #e50; color: #fff; cursor: default; }
3#words div:last-child { margin: 0; }
Now on to the JS (all inside a DRH, of course, as we're dealing with the DOM).
1//prep
2var
3words = ['square', 'hosepipe', 'canine', 'flower'],
4container = $('#words'),
5vowels = 'aeiou',
6next_word_index = 0;
All rather self-explanatory. Now for the bulk of the code:
1function build_word(word) {
2
3 //increment the next-word index
4 next_word_index++;
5
6 //remove the previous word, if any
7 container.empty();
8
9 //an array to store our deferreds (one for each vowel)
10 var deferreds = [];
11
12 //loop over the word's letters
13 for (var i=0, len=word.length; i<len; i++) {
14
15 var
16 letter = word.substr(i, 1),
17 isVowel = vowels.indexOf(letter) != -1,
18 letter_div = $('<div />', {text: letter}).appendTo(container);
19
20 //if this letter is a vowel...
21 if (isVowel) {
22
23 //set up a deferred object for it and log it in the array
24 var deferred = $.Deferred();
25 deferreds.push(deferred);
26
27 //on click, fade it out then resolve its deferred
28 letter_div.click({deferred: deferred}, function(evt) {
29 $(this).animate({opacity: .2}, evt.data.deferred.resolve).unbind('click');
30 });
31 }
32 }
33
34 //when all deferreds are resolved, do feedback and move on
35 $.when.apply(null, deferreds).done(function() {
36 var msg = 'Well done - you got all the vowels! ';
37 if (words[next_word_index]) {
38 alert(msg+"Let's try the next word...");
39 build_word(words[next_word_index]);
40 } else
41 alert(msg+"That's the end of the game!");
42 });
43}
44
45//on page entry, do the first word
46build_word(words[next_word_index]);
A few points
Hopefully the comments make it possible to follow what's going on there, but here's some points of particular note.
Firstly, I invoke $.when not directly but via the native apply(). This is because $.when() does not presently allow you to pass multiple objects as an array, which is necessary for my example. apply(), as you may know, allows you to stipulate arguments to a function as an array, so problem solved.
(If you're new to $.when(), I'll be covering that in a separate post, as it has a lot to offer your patterns.)
Secondly, in a production environment it would be prudent to expose not the deferreds themselves but their inherant promise objects (via the promise() method) instead. This allows environmental code to bind callbacks to them but not interfere with their state or progress. See the jQuery API page on promise objects for more detail.
Summary
I reiterate that this is a slightly contrived example, to highlight the use of deferreds independently of AJAX, but I think it's quite a nice pattern.
Of course, the same effect could be achieved several other ways without deferreds; one could continually check, in the fadeout callback, whether there were any vowels still remaining at full opacity. If no, the user has clicked all the vowels. That would require a more complex fadeout callback, but it would of course work.
5 comments | post new