I have an article on Behaviour Driven Development for JavaScript in June's edition of the excellent JavaScript Magazine.
If you haven't seen or read the magazine before (it's quite new), it's well worth the few dollars charged. The magazine format allows for in-depth articles that require more space, time and effort to write than a typical blog post, and which therefore often go unwritten.
The thrust of my article is that too much of our JavaScript goes untested, but that nowadays it's easy to fix that. I go through an example of a client side shopping cart, using the JSpec BDD library. Even if you don't buy/read the magazine, I highly recommend checking out JSpec and other libraries like it. As JavaScript powered applications become the norm, BDD will only become more important in ensuring our applications work properly, so now is a good time to start.
Also in this month's issue is a guide to using the Canvas tag, tips on how to use build scripts to optimise your JavaScript for each environment, AJAX security pointers and a roundup of community news.
Wednesday, 10 June 2009
Sunday, 7 June 2009
Darwin, Humanism and Science
On Saturday I had the good fortune to be able to attend a conference entitled "Darwin, Humanism and Science", held at London's Conway Hall. For those not able to attend here is a short roundup of what happened:
Richard Dawkins starts us off
The conference kicked off with a quick introduction from BHA President Polly Toynbee, after which Professor Dawkins took to the stage. His lecture revolved around the concluding paragraph of Darwin's On the Origin of Species, which can be read online for free here (the relevant passage starts "Thus, from the war of nature ..."). Dawkins analysed each segment of the text in turn, giving us his insights into its meaning and slipping in some fascinating information about our modern-day understanding of evolution, such as how we know that all species in the world today must be descended from a single progenitor.
The professor left some time for questions at the end of his lecture. He had commented on the lamentable state of the public's understanding of science, proffering the alarming statistic that some 18% of the British population believes that the Earth orbits the Sun once a month (presumably we go around faster in February), which lead me to ask him what we can do to combat this. His answer was to "get out more".
Referring to the role scientists have to play in the public's awareness and understanding of science, rather than (I hope) to my social life, he made the point that scientists and educators must make greater efforts to reach out to the public and disseminate not only the knowledge that modern science has obtained, but the joy that this knowledge can bring. It was a point that was returned to time and again throughout the day.
Insidious Creationism in Education
Following Professor Dawkins were two quick talks about the teaching of evolution in schools - first from a European perspective from Professor Charles Susanne, and then from a British one from James Williams. Though both highlighted the growing influence Creationist organisations are having on educational materials, Mr Williams' speech was for me the more alarming:
Quickly firing through a series of ridiculous materials showing how children and dinosaurs once lived and played together, and even an endearing image of Jesus cuddling a small Velociraptor, Williams showed how creationist books, comics and literature represent an "intellectual abuse of children". Entitled "Insidious Creationism", his talk opened our eyes to the battle being waged over children's education, in this country and around the world. I for one am very pleased that we have people like Williams fighting in our corner, and for his troubles he was presented with the rather dubious prize of an Atlas of Creation.
Human understanding of Evolution
After lunch we were treated to talks from Johan De Smedt and Dr Michael Schmidt-Salomon. Johan's talk revolved around the three themes of essentialism, teleology and the design stance. Tackling these in turn he described the biases inherent within us that give these ideas more prominence in our mental model of the world than they deserve, especially when we are young.
Dr Schmidt-Salomon rebutted the idea that evolution can be objected to on moral grounds. Though it may seem obvious that a moral objection to a natural phenomenon does not make it any less real, he reminds us that there are those who disagree. His most impressive moment though was in revealing his efforts to turn Ascension Day into Evolution Day, aided by a spectacularly bizarre music video featuring Charles Darwin as an unlikely rock star:
Brilliant.
Hinduism and The Two Cultures
For me an unexpected highlight of the day came in the form of Babu Gogineni's description of the devastating effect that some interpretations of Hinduism are having on science in India. He described how many Hindus believe that modern science backs up Hinduism's central tenets, and can therefore turn their backs on further progress made by the scientific community. It was a startling and eye-opening description, and one could feel his frustration at how quackery and superstition are considered more important (or at least more profitable) than science and understanding in India today.
The conference was rounded off by what seemed a short talk from Professor A C Grayling. This was the first time I had heard Grayling speak, and the calm lucidity with which he spoke made his 45 minutes seem more like 5. Speaking on the 'two cultures' - that of science and that of the humanities - he referred to a lecture given by C P Snow some 50 years ago decrying the divergence of these two cultures, and the widening communication gap between them. Snow's original point was that this divergence was getting in the way of solving the world's problems - 50 years later Grayling points out that we still have some way to go in closing that gap.
The 'Special' Dinner
In the evening a special dinner was laid on for the speakers and delegates attending the conference (today was just one day in a week of Humanism conferences). For some reason they let some of the unwashed masses in too, and so it was that I sat down with a delightful group of fellow conference-goers to enjoy a good meal punctuated by good conversation.
After we had eaten Professor Grayling presented Professor Dawkins with an award in recognition of his efforts in spreading rationality and clear thinking around the world, and in return Dawkins read out a modern day episode of Jeeves and Wooster, albeit with a rather Atheistic stance. I'm not sure whether or not he penned the parable himself but it was extremely well written and its British humour well received.
All too soon the coffee came round and it was time to leave. Overall the day was very well run and extremely enjoyable. A wise gentleman on my table was moved to remark that "it was the best 8 quid I've ever spent". Amen.
Richard Dawkins starts us off
The conference kicked off with a quick introduction from BHA President Polly Toynbee, after which Professor Dawkins took to the stage. His lecture revolved around the concluding paragraph of Darwin's On the Origin of Species, which can be read online for free here (the relevant passage starts "Thus, from the war of nature ..."). Dawkins analysed each segment of the text in turn, giving us his insights into its meaning and slipping in some fascinating information about our modern-day understanding of evolution, such as how we know that all species in the world today must be descended from a single progenitor.
The professor left some time for questions at the end of his lecture. He had commented on the lamentable state of the public's understanding of science, proffering the alarming statistic that some 18% of the British population believes that the Earth orbits the Sun once a month (presumably we go around faster in February), which lead me to ask him what we can do to combat this. His answer was to "get out more".
Referring to the role scientists have to play in the public's awareness and understanding of science, rather than (I hope) to my social life, he made the point that scientists and educators must make greater efforts to reach out to the public and disseminate not only the knowledge that modern science has obtained, but the joy that this knowledge can bring. It was a point that was returned to time and again throughout the day.
Insidious Creationism in Education
Following Professor Dawkins were two quick talks about the teaching of evolution in schools - first from a European perspective from Professor Charles Susanne, and then from a British one from James Williams. Though both highlighted the growing influence Creationist organisations are having on educational materials, Mr Williams' speech was for me the more alarming:
Quickly firing through a series of ridiculous materials showing how children and dinosaurs once lived and played together, and even an endearing image of Jesus cuddling a small Velociraptor, Williams showed how creationist books, comics and literature represent an "intellectual abuse of children". Entitled "Insidious Creationism", his talk opened our eyes to the battle being waged over children's education, in this country and around the world. I for one am very pleased that we have people like Williams fighting in our corner, and for his troubles he was presented with the rather dubious prize of an Atlas of Creation.
Human understanding of Evolution
After lunch we were treated to talks from Johan De Smedt and Dr Michael Schmidt-Salomon. Johan's talk revolved around the three themes of essentialism, teleology and the design stance. Tackling these in turn he described the biases inherent within us that give these ideas more prominence in our mental model of the world than they deserve, especially when we are young.
Dr Schmidt-Salomon rebutted the idea that evolution can be objected to on moral grounds. Though it may seem obvious that a moral objection to a natural phenomenon does not make it any less real, he reminds us that there are those who disagree. His most impressive moment though was in revealing his efforts to turn Ascension Day into Evolution Day, aided by a spectacularly bizarre music video featuring Charles Darwin as an unlikely rock star:
Brilliant.
Hinduism and The Two Cultures
For me an unexpected highlight of the day came in the form of Babu Gogineni's description of the devastating effect that some interpretations of Hinduism are having on science in India. He described how many Hindus believe that modern science backs up Hinduism's central tenets, and can therefore turn their backs on further progress made by the scientific community. It was a startling and eye-opening description, and one could feel his frustration at how quackery and superstition are considered more important (or at least more profitable) than science and understanding in India today.
The conference was rounded off by what seemed a short talk from Professor A C Grayling. This was the first time I had heard Grayling speak, and the calm lucidity with which he spoke made his 45 minutes seem more like 5. Speaking on the 'two cultures' - that of science and that of the humanities - he referred to a lecture given by C P Snow some 50 years ago decrying the divergence of these two cultures, and the widening communication gap between them. Snow's original point was that this divergence was getting in the way of solving the world's problems - 50 years later Grayling points out that we still have some way to go in closing that gap.
The 'Special' Dinner
In the evening a special dinner was laid on for the speakers and delegates attending the conference (today was just one day in a week of Humanism conferences). For some reason they let some of the unwashed masses in too, and so it was that I sat down with a delightful group of fellow conference-goers to enjoy a good meal punctuated by good conversation.
After we had eaten Professor Grayling presented Professor Dawkins with an award in recognition of his efforts in spreading rationality and clear thinking around the world, and in return Dawkins read out a modern day episode of Jeeves and Wooster, albeit with a rather Atheistic stance. I'm not sure whether or not he penned the parable himself but it was extremely well written and its British humour well received.
All too soon the coffee came round and it was time to leave. Overall the day was very well run and extremely enjoyable. A wise gentleman on my table was moved to remark that "it was the best 8 quid I've ever spent". Amen.
Wednesday, 29 April 2009
'function' in JavaScript - operator vs statement
In JavaScript we have at least 4 ways of defining functions:
These are not all the same, and the crucial thing here is the word 'function' as used in each case. In the first example we're using the function statement, and in the second and third examples we're using the function operator. We'll come back to the fourth example later.
So what's the difference between the function statement and the function operator? Well first we need to understand a bit about anonymous functions. Most of us are familiar with using anonymous functions as event listeners - something like this:
In this example we've passed in a function without a name as a listener callback. But what do we mean when we say a function does have a name? Do we mean this:
No we don't. Assigning a function to a variable does not give it a name. The function assigned to our variable above is still an anonymous function. To give a function a name we need to do something like this:
Now we have declared a function with the name myFunctionName and assigned it to the variable myFunction. Giving a function a name in this way adds a read-only name property to it:
Coming back to our very first example, we can see that we're using a different form here - the function statement:
Under the hood, what this is actually doing is something like this:
The function statement created a named function and assigns it to a variable of the same name. Note that in this case although the function name and the variable name are the same, they don't have to be:
Let's take a look at the last of our four initial examples:
Functions defined this way are always anonymous, and cannot be given a name. In general you shouldn't define functions this way, for several reasons:
One last thing to note is that if you use the function operator, it has to be within the context of an expression. For example you can't do this:
That doesn't work because it's not part of an expression - the function isn't being assigned to anything and you get a syntax error. If you want to run an anonymous function and not assign it to a variable, it can be done like this, which runs the function straight away:
For further reading on this check out the Mozilla function reference docs.
function myFunction() { alert('hai!'); }
var myFunction = function() { alert('hai!'); }
var myFunction = function myFunctionName() { alert('hai!'); }
var myFunction = new Function("alert('hai!');")
These are not all the same, and the crucial thing here is the word 'function' as used in each case. In the first example we're using the function statement, and in the second and third examples we're using the function operator. We'll come back to the fourth example later.
So what's the difference between the function statement and the function operator? Well first we need to understand a bit about anonymous functions. Most of us are familiar with using anonymous functions as event listeners - something like this:
this.on('render', function() {... do some stuff when 'this' has rendered ...});
In this example we've passed in a function without a name as a listener callback. But what do we mean when we say a function does have a name? Do we mean this:
var myFunction = function() {... do some stuff ...};
No we don't. Assigning a function to a variable does not give it a name. The function assigned to our variable above is still an anonymous function. To give a function a name we need to do something like this:
var myFunction = function myFunctionName() {... do some stuff ...};
Now we have declared a function with the name myFunctionName and assigned it to the variable myFunction. Giving a function a name in this way adds a read-only name property to it:
var myVar = function captainKirk() {... do some stuff ...};
alert(myVar.name); //alerts 'captainKirk'
//we can't update it though
myVar.name = 'williamShatner';
alert(myVar.name); //still 'captainKirk'
Coming back to our very first example, we can see that we're using a different form here - the function statement:
function myFunction() { alert('hai!'); }
Under the hood, what this is actually doing is something like this:
myFunction = function myFunction() { alert('hai!'); }
The function statement created a named function and assigns it to a variable of the same name. Note that in this case although the function name and the variable name are the same, they don't have to be:
function myFunction() { alert('hai!'); }
alert(myFunction.name); //alerts 'myFunction'
//assigning this function to another variable preserves the function name
var myVar = myFunction;
alert(myVar.name); //alerts 'myFunction'
Let's take a look at the last of our four initial examples:
var myFunction = new Function("alert('hai!');")
Functions defined this way are always anonymous, and cannot be given a name. In general you shouldn't define functions this way, for several reasons:
- The function body has to be parsed by the JS engine every time it is run, compared to just once for a normal function definition. This is slow
- Functions defined this way do not inherit the current scope. If you define a function this way the only scope it inherits is the global scope, which means it does not have access to any variables or functions in your current scope chain
- Defining functions this way requires the body to be entered as a string, which should sicken you enough not to use it.
One last thing to note is that if you use the function operator, it has to be within the context of an expression. For example you can't do this:
function() {alert('hai!');}
That doesn't work because it's not part of an expression - the function isn't being assigned to anything and you get a syntax error. If you want to run an anonymous function and not assign it to a variable, it can be done like this, which runs the function straight away:
(function() {alert('hai!');})();
For further reading on this check out the Mozilla function reference docs.
Labels:
anonymous,
functions,
javascript
Thursday, 23 April 2009
The case for Ext.applyOnly
** Update: Ext 3.0RC1 has included something like this, but called Ext.copyTo. Obviously my name is better though **
We should have something like this:
You could use this every time you write a method or class that requires a config Object as one of it's parameters. These methods ought to only apply those properties of the config object they actually need, but usually this will just be done with an Ext.apply(this, config). This means anything in your object could be overwritten by this config object. Sometimes that's a good thing, but sometimes it's definitely not.
Ext.applyOnly() applies only a whitelist of the properties in the config object. These are specified by an optional third argument, which is an array of property names. Here's how you might write applyOnly:
While you can't stop code maliciously overwriting properties this way, it would stop people from unknowingly overwriting your object's properties. They could overwrite them manually, but they'll do this knowing that this wasn't an intended use for the class. Let's have a look at an extension that would do a great job opening popups telling people they've won lots of money:
How it works:
In my example class I've added the whitelist as an accessibleProperties property on the class, which makes it easy for others to see what they should and should not be updating.
In this example we're also sanitizing output with applyOnly. WinnerEarnings lightly wraps around a series of Ext.Windows and we'd like to be able to pass our WinnerEarnings object as config. We want to make sure we're not passing our 'closable' property, 'close()' function and others to the Ext.Window constructor, so we pass that in via a whitelist too, inside the run() function in our constructor.
Check out the unit tests for the function to see a couple more use cases. Here's one final example - sanitizing output from a function:
We should have something like this:
Ext.applyOnly(this, config, ['width', 'height']);
You could use this every time you write a method or class that requires a config Object as one of it's parameters. These methods ought to only apply those properties of the config object they actually need, but usually this will just be done with an Ext.apply(this, config). This means anything in your object could be overwritten by this config object. Sometimes that's a good thing, but sometimes it's definitely not.
Ext.applyOnly() applies only a whitelist of the properties in the config object. These are specified by an optional third argument, which is an array of property names. Here's how you might write applyOnly:
/**
* Applies only a pre-specified set of properties from one object to another
* @param {Object} receiver The object to copy the properties to
* @param {Object} sender The object to copy the properties from
* @param {Array} whitelist The whitelist of properties to copy (e.g. ['width', 'height'])
* @return {Object} The receiver object, with any of the whitelisted properties overwritten if they exist in sender
*/
Ext.applyOnly = function(receiver, sender, whitelist) {
if (receiver && sender) {
Ext.each(whitelist || [], function(item) {
if (typeof sender[item] != 'undefined') receiver[item] = sender[item];
}, this);
};
return receiver;
};
While you can't stop code maliciously overwriting properties this way, it would stop people from unknowingly overwriting your object's properties. They could overwrite them manually, but they'll do this knowing that this wasn't an intended use for the class. Let's have a look at an extension that would do a great job opening popups telling people they've won lots of money:
/**
* Pops up windows telling lucky visitor she's won big money!!!!
*/
Ext.ux.WinnerEarnings = Ext.extend(Ext.util.Observable, {
/**
* @property accessibleProperties
* @type Array
* All properties intended to be mass-updatable
*/
accessibleProperties: ['height', 'width'],
/**
* Message to show lucky winners!! You can't change this!!!!!
*/
message: "You're the 1000000000000th visitor!!!!!!!1 Click here to claim money. Now!!!",
constructor: function(config) {
//apply only the fields that are deemed writable
Ext.applyOnly(this, config, this.accessibleProperties);
Ext.ux.WinnerEarnings.superclass.constructor.apply(this, arguments);
Ext.applyIf(this, {
version: 2.9,
coolFeature: Ext.util.TaskRunner({
interval: 1000,
scope: this,
run: function() {
//version, coolFeature, updateDetails, closable and close won't be sent to Ext.Window
new Ext.Window(Ext.applyOnly({}, this, ['height', 'weight', 'message'])).show();
}
}).start()
}
},
/**
* Updates this WinnerEarnings opportunity with options from the supplied object
* @param {Object} updates An object containing updates to make to this precious opportunity
* @return {Ext.ux.WinnerEarnings} The WinnerEarnings object
*/
updateDetails: function(updates) {
return Ext.applyOnly(this, updates, this.accessibleProperties)
},
//secret tricks to let the user stop the popups
closable: false,
close: function() {
this.coolFeature.stop();
}
})
How it works:
var myObj = new Ext.ux.WinnerEarnings({height: 200, width: 150});
myObj.updateDetails({width: 300, message: "My Message"})
myObj.width: // => 300
myObj.message; // => "You're the 1000000000000th visitor!!!!!!!1 Click here to claim money. Now!!!"
//updating message didn't work, but we can still do it manually
myObj.message = "My message";
myObj.message; // => "My message"
In my example class I've added the whitelist as an accessibleProperties property on the class, which makes it easy for others to see what they should and should not be updating.
In this example we're also sanitizing output with applyOnly. WinnerEarnings lightly wraps around a series of Ext.Windows and we'd like to be able to pass our WinnerEarnings object as config. We want to make sure we're not passing our 'closable' property, 'close()' function and others to the Ext.Window constructor, so we pass that in via a whitelist too, inside the run() function in our constructor.
Check out the unit tests for the function to see a couple more use cases. Here's one final example - sanitizing output from a function:
myFunction = function(input) {
//do some stuff to make input useful
//guarantee our returned object only has relevant properties
return Ext.applyOnly({}, input, ['important-thing-1', 'important-thing-2']);
}
Wednesday, 11 February 2009
Force Ext.data.Store to use GET
Say you have a simple Ext store:
Which you put in a grid, along with a paging toolbar:
Your grid loads up and the store performs a GET request to /widgets.json, which returns your widgets along with a total (see an example).
Awesome, but now we click one of the paging buttons on the PagingToolbar and we have a problem - our request has turned into POST /widgets.json, with "start=20" and "limit=20" as POST params.
Now we don't really want that - we're not POSTing any data to the server after all, we're just trying to GET some. If you're using a nice RESTful API on your server side this may cause you a real problem, as POST /widgets will likely be taken as an attempt to create a new Widget.
Luckily, as with most things the solution is simple if you know how. An Ext.data.Store delegates loading its data off to an Ext.data.DataProxy subclass. By default your store will create an Ext.data.HttpProxy using the url: '/widgets.json' you passed in your store config. To make sure your stores are always requesting data using GET, just provide a proxy like this:
var myStore = new Ext.data.Store({
url: '/widgets.json',
reader: someReader
});
Which you put in a grid, along with a paging toolbar:
var myGrid = new Ext.grid.GridPanel({
store: myStore,
columns: [.....],
bbar: new Ext.PagingToolbar({
store: myStore
})
... etc ...
});
Your grid loads up and the store performs a GET request to /widgets.json, which returns your widgets along with a total (see an example).
Awesome, but now we click one of the paging buttons on the PagingToolbar and we have a problem - our request has turned into POST /widgets.json, with "start=20" and "limit=20" as POST params.
Now we don't really want that - we're not POSTing any data to the server after all, we're just trying to GET some. If you're using a nice RESTful API on your server side this may cause you a real problem, as POST /widgets will likely be taken as an attempt to create a new Widget.
Luckily, as with most things the solution is simple if you know how. An Ext.data.Store delegates loading its data off to an Ext.data.DataProxy subclass. By default your store will create an Ext.data.HttpProxy using the url: '/widgets.json' you passed in your store config. To make sure your stores are always requesting data using GET, just provide a proxy like this:
var myStore = new Ext.data.Store({
proxy: new Ext.data.HttpProxy({
url: '/widgets.json',
method: 'GET'
}),
reader: someReader
});
Sunday, 1 February 2009
Adding a loading mask to your ExtJS application
Adding a loading mask like the one on the ExtJS API application is a nice way of showing the user that something is happening while their browser downloads the source code. It's also extremely easy to do.
First, place the following HTML above all of your javascript include tags, ideally just after the <body> tag:
If you are currently including javascript files inside the <head>, don't - put them at the bottom.
With a bit of CSS (see below), this provides a white mask over all underlying content, and a loading message. When everything has loaded, remove the mask like this:
The above simply fades out the HTML elements to reveal the now ready page. The setTimeout call gives your app a little time to render, which is useful if you're doing something like pulling external content down from the server.
Finally, here's the CSS I use to style up the loading mask. You'll need to download a loading image and stick it in the appropriate directory.
First, place the following HTML above all of your javascript include tags, ideally just after the <body> tag:
<div id="loading-mask"></div>
<div id="loading">
<div class="loading-indicator">
Loading...
</div>
</div>
If you are currently including javascript files inside the <head>, don't - put them at the bottom.
With a bit of CSS (see below), this provides a white mask over all underlying content, and a loading message. When everything has loaded, remove the mask like this:
Ext.onReady(function() {
setTimeout(function(){
Ext.get('loading').remove();
Ext.get('loading-mask').fadeOut({remove:true});
}, 250);
});
The above simply fades out the HTML elements to reveal the now ready page. The setTimeout call gives your app a little time to render, which is useful if you're doing something like pulling external content down from the server.
Finally, here's the CSS I use to style up the loading mask. You'll need to download a loading image and stick it in the appropriate directory.
#loading-mask {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 20000;
background-color: white;
}
#loading {
position: absolute;
left: 50%;
top: 50%;
padding: 2px;
z-index: 20001;
height: auto;
margin: -35px 0 0 -30px;
}
#loading .loading-indicator {
background: url(../images/loading.gif) no-repeat;
color: #555;
font: bold 13px tahoma,arial,helvetica;
padding: 8px 42px;
margin: 0;
text-align: center;
height: auto;
}
Thursday, 22 January 2009
Why you should be using History in your ExtJS applications
I've been making a few updates to the ExtJS API documents application recently. The actual updates include remembering which tabs you have open and using Ext.History to go between tabs (you can follow the forum post or see a beta version).
That's not quite ready yet, but what has been made very clear to me is that any ExtJS application with more than one view should be using Ext.History. With History we get urls inside the application itself, we can parse them and dispatch accordingly. For example, I'm using a Rails-like Router, which lets you define an internal url map like this:
The router knows how to decode urls based on the regular expression-like syntax above, and parse the matches into an object - for example:
You can of course define any url matching scheme using the connect() function. I then use a simple Dispatcher, which looks at the decoded parameters. It finds the appropriate controller and calls that action on the controller, passing any other parameters as arguments. For example:
And so on. Each controller knows what to do for that action. It's easy then to say to someone "go to http://myapp.com/admin#users/152/comments" - which will take them straight to the comments that user 152 has written. Compare that with saying: "go to http://myapp.com/admin, then click the List Users tab, then find the user called Joe Bloggs, then double click the bubble icon next to his name". It's obvious which approach is better.
You don't even need to use something as elaborate as a router, just a simple switch statement or some regular expressions would be enough for many applications. Once you've got Ext.History setup, you could do something as simple as:
Obviously you don't hard code user IDs like that but it's easy to see how to roll your own. With just a few lines of code, you've decoded a url into a function to call, which can do anything you need it to. All your internal navigation needs to do is call Ext.History.add("some/new/url"), which will now be picked up by your dispatch code.
It's important to only route like this for idempotent actions (i.e. actions which display data rather than change it), so that data changing actions are not repeated. This is equivalent to using GET and POST correctly in normal web applications.
When the simplest implementation takes just a few lines of code, what reason could there be not to be using it?
That's not quite ready yet, but what has been made very clear to me is that any ExtJS application with more than one view should be using Ext.History. With History we get urls inside the application itself, we can parse them and dispatch accordingly. For example, I'm using a Rails-like Router, which lets you define an internal url map like this:
map.connect(":controllers/:action/:id");
map.connect(":controllers/:action");
The router knows how to decode urls based on the regular expression-like syntax above, and parse the matches into an object - for example:
#users/new <= becomes {controller: 'users', action: 'new'}
#users/edit/2 <= becomes {controller: 'users', action: 'edit', id: 2}
#colours <= becomes {controller: 'colours'}
You can of course define any url matching scheme using the connect() function. I then use a simple Dispatcher, which looks at the decoded parameters. It finds the appropriate controller and calls that action on the controller, passing any other parameters as arguments. For example:
#users/new <= calls UsersController's "new" action
#colours/edit/2 <= calls ColoursController's "edit" action, with {id: 2} as the argument
And so on. Each controller knows what to do for that action. It's easy then to say to someone "go to http://myapp.com/admin#users/152/comments" - which will take them straight to the comments that user 152 has written. Compare that with saying: "go to http://myapp.com/admin, then click the List Users tab, then find the user called Joe Bloggs, then double click the bubble icon next to his name". It's obvious which approach is better.
You don't even need to use something as elaborate as a router, just a simple switch statement or some regular expressions would be enough for many applications. Once you've got Ext.History setup, you could do something as simple as:
//decodes a url and decides how to dispatch it
dispatch = function(token) {
switch (token) {
case "users" : displayUsers(); break;
case "users/new": displayNewUser(); break;
case "users/2/edit: editUser(2); break;
default: displayDefault(); break;
};
};
Ext.History.on('change', dispatch);
//Call dispatch on initial page load as Ext.History's change event is not fired here
Ext.History.init(function() {
var token = document.location.hash.replace("#", "");
dispatch(token);
});
Obviously you don't hard code user IDs like that but it's easy to see how to roll your own. With just a few lines of code, you've decoded a url into a function to call, which can do anything you need it to. All your internal navigation needs to do is call Ext.History.add("some/new/url"), which will now be picked up by your dispatch code.
It's important to only route like this for idempotent actions (i.e. actions which display data rather than change it), so that data changing actions are not repeated. This is equivalent to using GET and POST correctly in normal web applications.
When the simplest implementation takes just a few lines of code, what reason could there be not to be using it?
Labels:
extjs,
history,
javascript,
opinion
Subscribe to:
Posts (Atom)
