Firstly and most importantly, this is absolutely mindblowing, and I WANT ONE, Apple take note! etc. This hit the 'sphere about ten years ago but I also want to note it here.
Secondly, I have said many times before and I will say it again, defining page navigation as traversal within a directed state graph is a short, weak substitute for using a powerful language able to express arbitrarily complex logic, i.e. a programming language equipped with continuations, such as Ruby, Lisp, or Java.
Fourthly, most of what is written in a modern frontend should not be. We have page->BOM and BOM-> page inference/generation, smart binding code and ActiveRecord to light the way. XML config syntaxes and convoluted frameworks are not the answer. Java framework developers have no excuse for making their end-users write XML with the arrival of annotations in 1.5.
A good solution makes the simple things easy and the complex things possible. Too often, we make the simple things stupid and the complex things idiotic.
2006/02/27
2006/01/27
2006/01/17
Idempotent event queue in JavaScript for use with Behaviour, etc.
[caveat: my original copy now does more, i.e. copies arguments and uses "this" properly. Will paste in later.]
So anyway, there's me using my (modified) copy of the very groovy Behaviour to extract all the JavaScript from my nice clean semantic HTML(or JSP (*cough* bleh)), and wondering how I ever lived with the mish-mash I just rescued.
Generally speaking, Behaviour rulesets are applied on page load, (hence Behaviour.addLoadEvent), whereupon all the selectors in all the rulesets are evaluated and all the events are assigned and added.
However, whenever the page changes (as a result of the Prototype Ajax.Updater, for example), the standard way of ensuring that the rules still apply is to call Behaviour.apply() again, which re-assigns all the events to all the elements etc.
BUT, we have two issues here. Fit the first: suppose one element matches two selectors. If we assign our events in the normal way that works 90% of the time, as follows (to take a simple example):
The standard answer to this is simply to use addEventListener (FF/W3C) or attachEvent(MS). This solves our first problem. A second possible answer is to adapt Simon Willison's addLoadEvent code to enable the addition of multiple functions to arbitrary events, as follows:
HOWEVER, when adding events using addEventListener, attachEvent, or addEvent, subsequent calls to Behaviour.apply() will re-add new events to the same elements, with the result that events added via Behaviour will fire once for each re-application of the given ruleset, i.e. potentially far too many times.
The code that demonstrates this is as follows:
My solution was to write my own event queue for which the "add" function was idempotent. This means that adding the same function three times (as above) has no effect on the second and third time. (More properly, an idempotent operation is one in which one or many executions is functionally identical.)
This requires the ability to compare functions, and luckily the toString() of a Function object returns the source of that function - the ideal way to find identical chunks of code.
So, without further ado:
Enjoy. I think a nice touch is the ability to list the events attached to an element by calling (e.g.) window.onload.list(); but this is generally debug stuff.
I should sleep.
So anyway, there's me using my (modified) copy of the very groovy Behaviour to extract all the JavaScript from my nice clean semantic HTML(or JSP (*cough* bleh)), and wondering how I ever lived with the mish-mash I just rescued.
Generally speaking, Behaviour rulesets are applied on page load, (hence Behaviour.addLoadEvent), whereupon all the selectors in all the rulesets are evaluated and all the events are assigned and added.
However, whenever the page changes (as a result of the Prototype Ajax.Updater, for example), the standard way of ensuring that the rules still apply is to call Behaviour.apply() again, which re-assigns all the events to all the elements etc.
BUT, we have two issues here. Fit the first: suppose one element matches two selectors. If we assign our events in the normal way that works 90% of the time, as follows (to take a simple example):
".button" : function(el) { el.onclick = pressButton(el.id); },Here, two events (one for each rule) would be assigned to the same element, and the first one would overwrite the second one since el.onclick can only ever be one thing.
".important" : function(el) { el.onclick = warn("Atchung!"); }, //...
For this element:
<a class="important button">click</a>
The standard answer to this is simply to use addEventListener (FF/W3C) or attachEvent(MS). This solves our first problem. A second possible answer is to adapt Simon Willison's addLoadEvent code to enable the addition of multiple functions to arbitrary events, as follows:
/*functionally equivalent to the original*/
function addLoadEvent(func) {
addEvent(window, "onload", func);
}
/*generic version*/
function addEvent(obj, evt, func) {
var oldEvt = obj[evt];
if (typeof oldEvt != 'function') {
obj[evt] = func;
} else {
obj[evt] = function() {
oldEvt();
func();
}
}
HOWEVER, when adding events using addEventListener, attachEvent, or addEvent, subsequent calls to Behaviour.apply() will re-add new events to the same elements, with the result that events added via Behaviour will fire once for each re-application of the given ruleset, i.e. potentially far too many times.
The code that demonstrates this is as follows:
var f = function() { alert("foo"); };
var o = new Object();
addEvent(o, "bang", f);
addEvent(o, "bang", f);
addEvent(o, "bang", f);
o.bang(); With this example, using the above addEvent code (and the addEventListener etc. (although I haven't tested it and I'd look mighty stupid if I'm wrong)), the "foo" alert will appear three times. This is not what we want.My solution was to write my own event queue for which the "add" function was idempotent. This means that adding the same function three times (as above) has no effect on the second and third time. (More properly, an idempotent operation is one in which one or many executions is functionally identical.)
This requires the ability to compare functions, and luckily the toString() of a Function object returns the source of that function - the ideal way to find identical chunks of code.
So, without further ado:
/*
this is functionally equivalent with the original - for reverse
compatibility
*/
function addLoadEvent(func) {
addEvent(window, "onload", func);
}
/*
usage as above, e.g. addEvent(element, "onclick", function() {} );
generalized and adapted from
http://simon.incutio.com/archive/2004/05/26/addLoadEvent
*/
function addEvent(obj, evt, func) {
var oldFunc = obj[evt];
if (typeof oldFunc != 'function') {
obj[evt] = getEvent();
obj[evt].addListener(func);
} else {
if(oldFunc.__EVT_LIST) {
obj[evt].addListener(func);
} else {
obj[evt] = getEvent();
obj[evt].addListener(oldFunc);
obj[evt].addListener(func);
}
}
}
/*
this could be put within the above function, but that causes a memory
leak in IE
*/
function getEvent() {
var list = [];
/*
this extends the array instance to allow
for string comparison of functions
*/
list.hasFunction = function(val) {
for (var i = 0; i != this.length; i++) {
if(this[i].toString() == val.toString()) return true;
}
return false;
}
/*
this is the actual function that is called when the event is fired.
if any of the listeners return false, then false is returned,
otherwise true
*/
var result = function(event) {
var finalResult = true;
for(var i = 0; i != list.length; i++) {
var evtResult = list[i](event);
if(evtResult == false) finalResult = false;
}
return finalResult;
}
/*
this is the function on the event that adds a listener
*/
result.addListener = function(f) {
if(f == null) return;
if(list.hasFunction(f)) return;
list.push(f);
}
/*
this is a debug function - feel free to remove
usage example: window.onload.list();
*/
result.list = function() {
var log = "";
for(var i = 0; i != list.length; i ++) {
log += "<pre>"+list[i]+"</pre><hr/>";
}
var wnd = window.open("", "Event dump");
wnd.document.write(log);
}
/*
this is a semaphore to ensure that we play nice with other code
*/
result.__EVT_LIST = true;
return result;
}
Enjoy. I think a nice touch is the ability to list the events attached to an element by calling (e.g.) window.onload.list(); but this is generally debug stuff.
I should sleep.
"Referer" header not set on HTTP requests originating from assignment to "window.location" variable on IE6
This one is annoying. Suppose you were to click on the below link:
In both Firefox and IE6 the "Referer" [sic, TBL we love you!] header is set to the URL of the page on which the clicked link existed, e.g.:
However this code, which is functionally (but not semantically) equivalent:
Omits the "referer" header from the request it generate. (Ignore for the moment that the example is deliberately facetious. In RL, the onclick might call a function that might call a confirm that might change the page location.)
Why does this suck? Because you may want to be able to launch an operation sequence from a view page, and then return to that page to view changed state upon completion of that operation. And you might want to do the same operation from multiple view pages. Which means that you have to keep track of where you came from in order to direct the user back to the same place afterwards. This is an ideal use case for the "referer" header.
However, if you decide to direct the user to a new page that (for example) had it's URL constructed in JavaScript, then this becomes annoying. The workaround to this trivial, stupid bug that I only need because I'm using a nasty hack is as follows:
(Normal caveats apply, i.e. this is probably me being ill and sleep-deprived and casting about wild accusations concerning specks in the eyes of MS developers while smashing windows with the redwood stuck in mine, but anyway.)
<a href="http://google.com">Google</a>In both Firefox and IE6 the "Referer" [sic, TBL we love you!] header is set to the URL of the page on which the clicked link existed, e.g.:
GET / HTTP/1.1
Host: google.com
Referer: http://ianso.blogspot.com
However this code, which is functionally (but not semantically) equivalent:
<span onclick="window.location='http://google.com'">Google</span>Omits the "referer" header from the request it generate. (Ignore for the moment that the example is deliberately facetious. In RL, the onclick might call a function that might call a confirm that might change the page location.)
Why does this suck? Because you may want to be able to launch an operation sequence from a view page, and then return to that page to view changed state upon completion of that operation. And you might want to do the same operation from multiple view pages. Which means that you have to keep track of where you came from in order to direct the user back to the same place afterwards. This is an ideal use case for the "referer" header.
However, if you decide to direct the user to a new page that (for example) had it's URL constructed in JavaScript, then this becomes annoying. The workaround to this trivial, stupid bug that I only need because I'm using a nasty hack is as follows:
function goTo(url) {
var a = document.createElement(a);
if(!a.click) { //only IE has this (at the moment);
window.location = url;
return;
}
a.setAttribute("href", url);
a.style.display = "none";
$("body").appendChild(a); //prototype shortcut
a.click();
}
(Normal caveats apply, i.e. this is probably me being ill and sleep-deprived and casting about wild accusations concerning specks in the eyes of MS developers while smashing windows with the redwood stuck in mine, but anyway.)
2006/01/05
MUSIC! CODE! MONKEYS!
(I should be asleep.)
Firstly, 50FOOTWAVE have published "Free music" which is a 5-track EP of good hard stuff. Available in MP3, etc. and also FLAC. I'm so used to MP3 that the quality of FLAC is like a breath of fresh air.
Secondly, and on this subject: I was recently re-acquainted with just how beautiful a decent record player with nice can sound when given decent vinyl to groove on, esp. Dire Straits guitar solos and classic jazz. So given how I come across new music (friends give it to me, sometimes on a wholesale basis), an ideal music infrastructure begins to look as follows:
Firstly, 50FOOTWAVE have published "Free music" which is a 5-track EP of good hard stuff. Available in MP3, etc. and also FLAC. I'm so used to MP3 that the quality of FLAC is like a breath of fresh air.
Secondly, and on this subject: I was recently re-acquainted with just how beautiful a decent record player with nice can sound when given decent vinyl to groove on, esp. Dire Straits guitar solos and classic jazz. So given how I come across new music (friends give it to me, sometimes on a wholesale basis), an ideal music infrastructure begins to look as follows:
- MP3 for general music consumption
- CDs for archival & liner art
- Vinyl for when the music is just so good.
- Lisp, spreadsheets, and RagTime (from it's description) all seem to embody a spirit of computational fungibility that shows what computers should be like in future. The reason I can't take the aforementioned 3 programs/languages/environments and produce said Nirvana is because computers suck.
- Closures in JavaScript are nice. var f = function() {} and all that makes events much nicer to use.
- I've been forced to confront my instinctive fear of big, sophisticated IDEs from gigantic megacorporations. I'm worried that their code is so smart that my job will become no more challenging than that of the average Visual Basic droid. This would suck.
- This may simply be post-Microsoft-IDE trauma. I remember VB4... VBA... generated code that should never have seen the light of day... *shudder*
- Legitimate reasons for ignoring these things still exist, lock-in to their own evolutionary path being the biggest and baddest.
- This is why, if I have to move intelligence into code, I'd rather it was open-source code. That way, when I've trained Rhesus monkeys equipped with build scripts to construct web applications based on my legion of sequence diagrams, then I could hack on the code to make the computer do even more of the boring stuff computers are good at and which I detest. (repeat after me: a good programmer is a lazy programmer...)
- Speaking of Work:
- Time was, people would sleep through Winter 'cos there were no crops to harvest and no light to work by. Humanitys biorythms are adjusted to this pattern.
- Now, we work 8-hour days all year round, and waking up at 7:00 in the dark is a truly crushing way to start the day (not to mention Brussels public transport.)
- Therefore, why not work 10-hour days for half the year, and 6-hour days for the other half, when I'd rather be in bed? Eh?
- I'm currently reading a translation of Les Miserables by Victor Hugo (who is a genius), and it truly is an absolutely incredible work.
2006/01/02
Prototype-style shortcut function for XPath expressions
Background: using Prototype is allegedly like using crack - immediately gratifying and bad for the whole application. The wisdom of using for(in) notwithstanding, I dunno.
Anyway, the nicest things of all in Prototype are the shortcuts: $() and $F(), which make my life much more chilled out (pass the pipe dude,) and so I hereby introduce an equivalent for XPath munging: $X().
This needs Sarissa to paper over the differences between IE and FF for this to work. Needless to say, Safari users can go swivel until Apple gets of their butt and improves their XSLT support. </flamebait>. The commented out 'Logger' is using Lumberjack syntax.
Anyway, the nicest things of all in Prototype are the shortcuts: $() and $F(), which make my life much more chilled out (pass the pipe dude,) and so I hereby introduce an equivalent for XPath munging: $X().
This needs Sarissa to paper over the differences between IE and FF for this to work. Needless to say, Safari users can go swivel until Apple gets of their butt and improves their XSLT support. </flamebait>. The commented out 'Logger' is using Lumberjack syntax.
function $X(xPathExpr, ctxNode, doc) {
if(!ctxNode) {
ctxNode = document;
}
if(!doc) {
if(ctxNode instanceof Document) {
doc = ctxNode;
} else {
doc = ctxNode.ownerDocument;
}
}
var result = doc.selectNodes(xPathExpr, ctxNode);
if(result == null) {
//Logger.debug("no match for "+xPathExpr);
return null;
}
if(result.length == 1) return result[0];
return result;
}Usage is as follows:- $X("//p") returns all paras in a document.
- $("//p[@id=foo]") returns one para with id foo.
- arg[1] optionally specifies the node context (relative root)
- arg[2] can specify a different document to work on, for example one retrieved via XMLHttpRequest.
Coffee and laptops:
If you can afford a decent laptop, you should also make sure you have nice thick ceramic coffee cups, and saucers, to go alongside it.
Decent coffee cups are harder to tip over than disposable plastic cups.
Posted because I recentlykilled severely maimed a laptop in the time-honoured tradition of all over-caffienated coders.
Decent coffee cups are harder to tip over than disposable plastic cups.
Posted because I recently
2005/12/01
Web development:
It seems as if we're slowly adding more and more languages to the web development mix. We had HTTP and HTML, then we added JavaScript for scripting and CSS for styling, and we also have XSLT for transformations, XML for data transfer, and not including MathML and SVG, oh my! One client-server protocol, one markup language, one declarative expression language, one scripting language, what is the world coming too?
(I should add, for the benefit of any Microsoft coders out there: if you don't get support MathML and SVG in IE right soon you'll end up displaying GIFS and loading klunky Adobemedia plugins when the rest of the world is dragging and dropping formulas and vectors, not to mention serving all this stuff inlinethat not even a plugin will help you with, no siree. - see Paul's comment below
)
Should I get worried? Well, I know all this stuff, so no. Also, it is still easy to get started. You don't have to know HTTP to know HTML, which is the substrate upon which the rest is built.
Also, I would add that we've got these things for a reason: having played around with REST XML APIs + JavaScripting XSLT + CSS + XHTML, I have to say that this is a sweet combo that makes me emit an evil laugh everytime I do something ridiculously complex in ten lines of JavaScript.
Anyway, this leads me to the conclusion that a) dumping XML on the client for transformation is not intrinsically wrong, b) JSON-RPC is nice, c) passing HTML clips via XMLHttpRequest is not an ideal situation but works.
Also, despite me not working on Gradient for ages, I still believe that the web is missing a real-time control channel and the ability for clients to communicate more-or-less directly with each other, and that XMPP is the solution to that.
What's changed is that Firefox 1.5 supports SVG now... I'm thinking of digging up the old and obsolete JiM or fiddling with Mozchat and seeing if I can wire either of them directly into the DOM.
(I should add, for the benefit of any Microsoft coders out there: if you don't get support MathML and SVG in IE right soon you'll end up displaying GIFS and loading klunky Adobemedia plugins when the rest of the world is dragging and dropping formulas and vectors, not to mention serving all this stuff inline
)
Should I get worried? Well, I know all this stuff, so no. Also, it is still easy to get started. You don't have to know HTTP to know HTML, which is the substrate upon which the rest is built.
Also, I would add that we've got these things for a reason: having played around with REST XML APIs + JavaScripting XSLT + CSS + XHTML, I have to say that this is a sweet combo that makes me emit an evil laugh everytime I do something ridiculously complex in ten lines of JavaScript.
Anyway, this leads me to the conclusion that a) dumping XML on the client for transformation is not intrinsically wrong, b) JSON-RPC is nice, c) passing HTML clips via XMLHttpRequest is not an ideal situation but works.
Also, despite me not working on Gradient for ages, I still believe that the web is missing a real-time control channel and the ability for clients to communicate more-or-less directly with each other, and that XMPP is the solution to that.
What's changed is that Firefox 1.5 supports SVG now... I'm thinking of digging up the old and obsolete JiM or fiddling with Mozchat and seeing if I can wire either of them directly into the DOM.
When creating DOM-related library / utility functions in JavaScript:
Using document to do stuff like createElement is a good idea 99% of the time, and the other 1% of the time, guess what - I've loaded an XMLDocument from XMLHttpRequest. Therefore, take your operand element and use element.ownerDocument to construct stuff.
This advert was paid for by the Council for Prevention of Headaches in Web Developers.
This advert was paid for by the Council for Prevention of Headaches in Web Developers.
Attention Java webapp developers: No JSP in your JavaScript.
This has been both a confession and a public service announcement.
Trust me, it's a bad idea, except maybe to rewrite the odd URL...
Trust me, it's a bad idea, except maybe to rewrite the odd URL...
Before somebody else says it:
I get the sinking feeling that half a dozen bugs that I've observed will turn out to be unreproducible, and therefore my fault, and therefore yet another case of hubris on my part. Oh well, such is life.
One more annoying IE6 bug:
Assuming this CSS:
Much as I sympathize with frustration at the W3C's glacial process of standards-making (in 'net years), it does seem as if people who don't pay attention to standards bodies end up producing products markably inferior to develop for than those who do.
Grr.
div { border: solid black 1px; }And this HTML:
<div id="foo" style="background-color:lightgrey; border:solid red 1px">...</div>When I evaluate $("foo").style.border=""; I expect to see a div with a light grey background and a black border. And lo, Mozilla doth fulfill my expectations, whereas the venerable IE nuketh not only the element-level style declaration, but also the entire cascade of styles applied heretofore, despite MS and IE having INVENTED Cascading Style Sheets.
Much as I sympathize with frustration at the W3C's glacial process of standards-making (in 'net years), it does seem as if people who don't pay attention to standards bodies end up producing products markably inferior to develop for than those who do.
Grr.
The only cross-platform way to add dynamically parameterized events to DOM elements:
To explain what I mean. This is static:
You might wonder why I'm passing this when I could use the event target instead. First, getting the event target is non-cross platform, and second, I might want to do foo( $("id") ...), which again is more cross-platform than fireEvent or whatever.
<div onclick="foo()">...</div>This is dynamic, unparameterized:
div.setAttribute("onclick", "foo()"); //not in IE6This is dynamic, parameterized (and works on Mozilla):
//or:
div.onclick = foo; // works everywhere
div.setAttribute("inclick", "foo(this, 1, 2, 3, 'bar')")This allows you do do stuff like construct your function as you would a string. But you can't do div.onclick = foo; and pass arguments as well. So you have to do this:
div.onclick = function() { foo(this, 1, 2, 3, "bar"); };Which basically creates an anonymous function each time it's evaluated. If you want to construct your function call as a string, do that and then use eval(). (I would also add that if you have to do that here, you have bigger problems than dynamically parameterized events.) But hey, it works.
You might wonder why I'm passing this when I could use the event target instead. First, getting the event target is non-cross platform, and second, I might want to do foo( $("id") ...), which again is more cross-platform than fireEvent or whatever.
2005/11/24
The earth goes round the sun, the moon goes round the earth, JSP sucks.
blogsMoaningAboutJSP.add(
Blogger.getInstance("http://ianso.blogspot.com")
.createBlankAnnoyedProgrammerEntry()
);
So anyway, although I do realise that JSP came a long before MVC was applied to webapp development, but WTF is a templating solution doing setting the MIME type on my response?
This is not even taking into account the stupid, stupid, requirement to compile JSPs into Java files and then Java classes, meaning that there are twice as many places for compilation errors, and twice as many places for logical errors, and a PIA to write a decent IDE for, Hell! JSP is never a good idea. But it's standard.
Blogger.getInstance("http://ianso.blogspot.com")
.createBlankAnnoyedProgrammerEntry()
);
So anyway, although I do realise that JSP came a long before MVC was applied to webapp development, but WTF is a templating solution doing setting the MIME type on my response?
This is not even taking into account the stupid, stupid, requirement to compile JSPs into Java files and then Java classes, meaning that there are twice as many places for compilation errors, and twice as many places for logical errors, and a PIA to write a decent IDE for, Hell! JSP is never a good idea. But it's standard.
Ingredients for a seamless iframe with no border in IE6:
In most webapp development, you don't want to draw attention to your techniques - iframes are a good example, you don't want a "look ma, a page within a page!" feeling, you just want it to display and look & feel like the rest of your app.
In FF this is easy: the CSS is "iframe {border:0;}". Naturally, this also applies to stuff you add to the DOM via scripting.
In IE, it's a complete pain in the ass, as usual. Not only do you have to set the old-style "like it's 1999!" attributes as follows:
iFrame.setAttribute("border", "0");
iFrame.setAttribute("frameborder", "0");
you also have to set the style on the body of the iframe you loaded, as follows:
body {border:0;}
As if there was any other circumstance in which the border style of your body affects the container. Grr.
In FF this is easy: the CSS is "iframe {border:0;}". Naturally, this also applies to stuff you add to the DOM via scripting.
In IE, it's a complete pain in the ass, as usual. Not only do you have to set the old-style "like it's 1999!" attributes as follows:
iFrame.setAttribute("border", "0");
iFrame.setAttribute("frameborder", "0");
you also have to set the style on the body of the iframe you loaded, as follows:
body {border:0;}
As if there was any other circumstance in which the border style of your body affects the container. Grr.
Subscribe to:
Posts (Atom)