Javascript/A Way of Building Widgets
Contents
Focus
The idea is to make it easy to write widgets.
Goal
Whenever I write a widget, I need to do one or more of the following:
- Specify the DOM element that make up the widget in the browser.
- Specify the special attributes of the DOM element.
- Connect up the events that the DOM element may generate to code I have written.
- Specify any children of that DOM element in like manner.
- Remember the DOM element for later use.
What I've Done in the Past
There are several methods I've used in the past, all of them with varying degrees of success.
HTML
I've written pure HTML.
- Write out the element in HTML.
- Specify attributes as you would in HTML.
- Write javascript code for the "onclick", etc... handlers.
- Write out the HTML for the children. Use el.innerHTML or just include it in the document.
- Use the 'id' attribute to remember it, ensuring I use a unique name.
The problem with this method is finding unique IDs. It's just easier to pass objects around, not their IDs.
Also, the javascript for the events is clunky and is executed in the global scope. This isn't always optimal.
DOM manipulation
Using DOM manipulation from Javascript, I get better results, although the code is wordier.
- el = document.createElement('div')
- el.attr = value.
- el.onclick = function() { ... }
- el.appendChild(el2), ...
- We remember widgets with variables.
The DOM manipulation method can easily be abstracted. However, in the end, I've found my code to be repetitive. I want to write code that is straightforward and clear, specifying the bare minimum in simple expressions.
Javascript's Limitations
Certain limitations make Javascript hard to use.
- It's hard to write strings, particularly long strings.
- To convert /HTML strings to DOM, you have to do some finagling.
The future
I see some sort of system that works like the second method (DOM manipulation), but uses a bare minimum of constructs.
The following functions are exciting but limited.
Creating DOM Elements
"document.createElement(type) is a bit wordy. It can easily be shortened. Example:
var DIV = function() { return document.createElement('div'); }
Those who don't see the benefit of the above haven't written code to create hundreds of elements yet.
Specifying Attributes
Specifying attributes is simple. Add a parameter to suck in attributes.
var DIV = function(attrs) { var el = document.createElement('div'); for (var attr in attrs) { el[attr] = attrs[attr]; } return el; }
Of course, you can combine the two steps more generically:
var EL = function(type, attrs) { var el = document.createElement(type); for (var attr in attrs) { el[attr] = attrs[attr]; } return el; } var DIV = function(attrs) { return EL('div', attrs); }
From now on, I'm going to discuss the EL function.
Adding Children
This one is a bit tougher. But you can easily imagine a function to do it:
var EL = function(type, attrs, children) { var el = document.createElement(type); for (var attr in attrs) { el[attr] = attrs[attr]; } for (var i=0; i<children.length; i++) { el.appendChild(children[i]); } return el; }
Couple of problems, though. Every child needs to be a DOM node. Creating text nodes isn't trivial---document.createTextNode. We have two ways to solve that problem.
Assume all text is a text node
This is a good assumption, but it has limitations as we'll see below. To do this, simply check the type of each item.
for (var i=0; i<children.length; i++) { var child = children[i]; if (typeof child == 'string') { child = document.createTextNode(child); } el.appendChild(child); }
You will probably want to include checks for 'undefined' and 'null', and you'll probably want to convert numbers to strings first. This is easy to add.
Now, creating a new element looks like this:
var my_new_el = EL('div', {style:'color:red;'}, [ "This is my ", EL('em', ["new"]), " nifty function"]);
Pretty nifty, eh?
Assume text is HTML code
If we assume that text is HTML code, then things get a bit more interesting. We'll need an HTML to DOM converter function. It looks like this:
var HTMLtoDOM = function(html) { var el = document.createElement('div'); el.innerHTML = html; return el.childNodes; }
Note that it returns a list of nodes. Some people prefer using a DOM document fragment. Whatever floats your boat.
Now, assume all text children are really HTML fragments and bring them in.
if (typeof child == 'string') { var html = HTMLtoDOM(child); for (var j=0; j<html.length; j++) { el.appendChild(html[j]); } continue; }
Using this method, the code size shrinks.
var my_new_el = EL('div', {style:'color:red;'}, [ "This is my new nifty function"]);
Note that we can do all sorts of fancy things as well as we could with HTML. But now we suffer from the limitations of HTML---naming and event handlers.
Well, not really. Not if we tweak the HTMLy into a new form of code that can reference the surrounding Javascript namespace and do interesting things. I'm talking about a template language.
What to do?
As of today (April 2011), I use YUI3. I haven't been able to get it to play nicely for all the situations I describe above, but it is close enough.
jQuery is nice and has several innovative and useful ideas (that YUI has as well), but ends up being little more than a toy because it lacks the industrial qualities I expect to see in a GUI system. Keep in mind I'm used to dealing with Qt which is extremely refined.