After posting about my adaptation of the del.icio.us playtagger, I decided to reduce the code in size a bit… and maybe even do some comparisons. After looking at the previous lines of code, I concluded that the most space was occupied by DOM methods like setAttribute or document.createElement. So the best way of reducing all that space was by creating some sort of ‘element object creator’… surely, I wasn’t the only one to suffer from ‘manually-dynamically’ creating elements… but there were only a couple of initiatives,… nothing compatible that degraded gracefully when using ‘application/xhtml+xml’ or ‘text/html’.
So, after coding for a while, I came up with this:
function crEl() {
var a = arguments; //arguments
var nN = a[0].toLowerCase(); //node Name
var tE = document.createElement(nN); //temporary element
var aPL = (a.length - 1)/2; //length of attribute and value pairs
var dM = (aPL%2)-(Math.floor(aPL%2)); //decision maker
if(dM === 0) { //if there is a value for each attribute name
if(nN == "param") { //this applies for the param element
for(var i = 0; i < aPL; ++i) {
var j = 1+(i*2);
tE.setAttribute("name", a[j]);
tE.setAttribute("value", a[j+1]);
}
} else { //if the element is not a param element...
for(var k = 0; k < aPL; ++k) {
var l = 1+(k*2);
tE.setAttribute(a[l], a[l+1]);
}
}
return tE; //returns the element
} else {
return false; //Perhaps an alert(nN); would be better?
}
}
You can copy and paste the above function, and test it out at the Mini Javascript Editor. Here’s a small test case (copy and paste it in the editor along with the function):
var par = crEl("p");
var div = crEl("div","id","mydiv","style","background-color:#ffffff;");
out(par.nodeName+", "+div.nodeName);
out(typeof(par)+", "+typeof(div));
out(par.hasAttributes()+", "+div.hasAttributes());
Usage
The function crEl() accepts any number of arguments (all of them strings… for now), and it creates an element (a node) with the name of the first string. The rest of the arguments are pairs of attribute names and values. An example would be:
var image = crEl(“img”,”src”,”http://example.com/image.png”,”alt”,”great image”,”width”,”80″,”height”,”15″);
The previous line of code would create an object with a nodeName of img, with an attribute ‘src’ of ‘http://example.com/image.png’ and so on… In an XHTML document it would be:
<img src=”http:example.com/image.png” alt=”great image” width=”80″ height=”15″ />
The only thing left to do is append the image object somewhere. Like, document.getElementById(‘mydiv’).appendChild(image);.
If you revise the code, you’ll see that it has room for improvement (as in including special rules for elements that may have textNodes),… but for now it has a special case for ‘param’ elements…
I’ll compare the original del.icio.us playtagger code for adding an object with attributes and some children to a span element (they use the innerHTML method).
Original code:
Delicious.Mp3.player = document.createElement('span')
Delicious.Mp3.player.innerHTML = '<object style="vertical-align:bottom" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"' +
'codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0"' +
'width="100" height="14" id="player" align="middle">' +
'<param name="allowScriptAccess" value="sameDomain" />' +
'<param name="flashVars" value="theLink='+url+'" />' +
'<param name="movie" value="http://test.dev.del.icio.us/static/swf/playtagger.swf" /><param name="quality" value="high" />' +
'<param name="bgcolor" value="#ffffff" />' +
'<embed style="vertical-align:bottom" src="http://test.dev.del.icio.us/static/swf/playtagger.swf" flashVars="theLink='+url+'"'+
'quality="high" bgcolor="#ffffff" width="100" height="14" name="player"' +
'align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash"' +
' pluginspage="http://www.macromedia.com/go/getflashplayer" /></object>'
When I ported the del.icio.us playtagger to use document.createElement instead of using innerHTML, the code for creating an object, and appending some variables to it was:
Delicious.Mp3.player = document.createElement('span');
Delicious.Mp3.player.setAttribute("id", "delicious");
var tempobj = document.createElement('object');
tempobj.setAttribute("type", "application/x-shockwave-flash");
tempobj.setAttribute("data", "http://del.icio.us/static/swf/playtagger.swf");
tempobj.setAttribute("width", "100");
tempobj.setAttribute("height", "14");
tempobj.setAttribute("id", "player");
var param0 = document.createElement('param');
var param1 = document.createElement('param');
var param2 = document.createElement('param');
var param3 = document.createElement('param');
var param4 = document.createElement('param');
var param5 = document.createElement('param');
var param6 = document.createElement('param');
param0.setAttribute("name", "allowScriptAccess");
param0.setAttribute("value", "always");
param1.setAttribute("name", "flashVars");
var tlu = "theLink=" + url;
param1.setAttribute("value", tlu);
param2.setAttribute("name", "movie");
param2.setAttribute("value", "http://del.icio.us/static/swf/playtagger.swf");
param3.setAttribute("name", "quality");
param3.setAttribute("value", "high");
param4.setAttribute("name", "bgcolor");
param4.setAttribute("value", "#FFFFFF");
param5.setAttribute("name", "menu");
param5.setAttribute("value", "false");
param6.setAttribute("name", "wmode");
param6.setAttribute("value", "transparent");
tempobj.appendChild(param2);
tempobj.appendChild(param3);
tempobj.appendChild(param4);
tempobj.appendChild(param5);
tempobj.appendChild(param6);
tempobj.appendChild(param1);
tempobj.appendChild(param0);
Delicious.Mp3.player.appendChild(tempobj);
If you look closely, I ommited some attributes for the object, or decided to add some params of my own… this was because appending an embed element through javascript causes mysterious errors in IE6. With this function… a direct translation from del.icio.us’ innerHTML method would look like:
Delicious.Mp3.player = crEl("span","id","delicious");
var tempobj = crEl("object","type","application/x-shockwave-flash","data","http://del.icio.us/static/swf/playtagger.swf","style","vertical-align:bottom;","width","100","height","14","id","player");
var p0 = crEl("param","allowScriptAccess","sameDomain");
var p1 = crEl("param","flashVars","theLink="+url);
var p2 = crEl("param","movie","http://del.icio.us/static/swf/playtagger.swf");
var p3 = crEl("param","quality","high");
var p4 = crEl("param","bgcolor","#ffffff");
var p5 = crEl("param","wmode","transparent");
tempobj.appendChild(p0);
tempobj.appendChild(p1);
tempobj.appendChild(p2);
tempobj.appendChild(p3);
tempobj.appendChild(p4);
tempobj.appendChild(p5);
Delicious.Mp3.player.appendChild(tempobj);
So… as you see, the more code that is used, the more you save space.
What’s next
What will happen next… ? I guess I can try porting the code to have a switch/case model for the tags… or try to include a method for appending Children nodes on the fly… Or if anyone is interested in making an Ecmascript (Javascript) library… feel free to use the code (just let me know).
Regarding the more dynamic way of appending child nodes, I was thinking of a recursive pair of functions that could be called multiple times when detecting an argument type of a number… and that could create text nodes on the fly…
XHTML-PlayTagger
Here is some basic testing… modified playtagger (application/xhtml+xml), modified playtagger (text/html)… The del.icio.us playtagger finds links to mp3 files in a document and places a button next to them. When the button is clicked, it embeds a flash player. I still haven’t managed for the flash player to be displayed in IE6 (appending an embed causes a javascript error saying ‘Invalid argument’). Feedback as to how to work around this would be greatly appreciated.
The original playtagger is only 2.43 kilobytes in size, and my latest modified tagger is 2.87 kilobytes… but when both are compressed, the filesize is, respectively, 2.25 and 2.34 kilobytes. So, there’s not much harm done. Also, I think the size will decrease considerably once I find a workaround for using an embed element as alternate content… (just using objects and params should suffice).
Test Cases
I’m going to make a test file… to see if I could bypass the need to store the created element in a variable and then append that variable… and also test what happens if dM !== 0. Oh well… I might as well do that after lunch
I was reading your post and had a few ideas how you might improve the function.
First of all, you could pass in attribute values as an object, which lets you have natural name/value pairing, such as:
If you’re interested in a sophisticated element assembler, you could create an ElementBuilder object. Since I’m on the topic anyway, let me try:
You would use it like this:
I apologize for any errors. Feel free to edit this comment for formatting and coding errors.
Hello Matthias, and thank you for your insightful comment. Your idea of the assembler sounds really intriguing, I’ll give it a try and see what I can come up with…
I did spend some time before reading your comment expanding the function, so that it could accept objects and append an onclick event (this last one was unsuccesful).
Here is the formula I came up with: (dropped some conditions, and a for, and went with a while… but your object oriented arguments seem much better
I made some test cases for it (using another modified delicious playtagger)… I’m having trouble with the onclick attribute, I think it might be a mistake of mine… Anyhow, I’m looking for an argument called ‘onclick’, and I’m inferring the next argument to be a function… so that I can then use the onclick method and pass it a function. Here is the content creation function.
I did however upload the tests with my latests modifications for object creation in xhtml and html…
Though I think I’ll head in the direction you’ve pointed. I’m currently in my polymer chemistry lab, but when I get home I’ll give your functions a try… they seem much better. I dunno if I have to leave a statement to declare my code as open source or GPL, but yeah… it is free, and anyone that stops by can pitch in and ‘steal’ it or improve it.
First of all, a correction on my first comment. The last line should be:
Just a tip on your onclick handler. Where you have:
you are calling the alert function and passing its result into the function. Instead, you should pass in a reference to a function. You might do this:
Or this (note that there are no parentheses after showAlert):
There is yet another approach that I considered presenting this morning. Essentially, instead of setting attributes, it sets properties on the JavaScript object just as you do for the onclick handler. For example:
You might call it like this:
Let me know if more explanation would be helpful. I’m interested in seeing what you come up with.
[...] But, thanks to the great and insightful comments of Matthias Miller, I came up with this interesting piece of code: [...]