My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.
Showing posts with label transform. Show all posts
Showing posts with label transform. Show all posts

Sunday, November 29, 2009

XML To HTML Snippet

This John Resig post about nodeName demonstrates once again how trustfulness are edge cases in JavaScript.
I must agree 100% with @jdalton: frameworks or selector libraries should not be concerned about these cases.

First of all there is no universal solution so whatever effort able to slow down libraries won't be perfect, then why bother?

Secondly, I cannot even understand why on earth somebody could need to adopt XML nodes in that way.
Agreed that importNode or adoptNode should not be that buggy, but at the same time I have always used XSL(T) to inject XML into HTML and I have never had problems.

Different Worlds

In an XML document a tag is just a tag. It does not matter which name we chose or which JavaScript event we attached, XML is simply a data protocol, or transporter, and nothing else.
A link, a div, an head, the html node itself, does not mean anything different in XML so again: why do we need to import in that way?
In Internet Explorer we have the xml property which is, for HTML represented inside XML docs, the fastest/simplest way to move that node and what it contains inside an element and via innerHTML.
Moreover, namespaces are a problem, we cannot easily represent them into an HTML document so, in any case, we need to be sure about represented data.

A Better Import

Rather than ask every selector library to handle these edge cases we could simply adopt a better import strategy.
This XML to HTML transformer is already a valid alternative, but we can write something a bit better or more suitable for common cases.
As example, a truly common case in these kind of transformations is a CDATA section inside a script node.
With CDATA we can put almost whatever we want inside the node but 99.9% of the time what we need is a JavaScript code, rather than a comment.
Another thing to consider is that if we need to import or adopt an XML node, 99.9% of the time we need to import its full content and not an empty node (otherwise we should ask us why we are representing data like that, no?).
I bet the deep variable will be basically true by default so, here there is my alternative proposal which should be a bit faster, avoiding implicit boolean cast for each attribute or node, and considering what I have said few lines ago:

function XML2HTML(xml){
// WebReflection Suggestion - MIT Style License
for(var
nodeName = xml.nodeName.toUpperCase(),
html = document.createElement(nodeName),
attributes = xml.attributes || [],
i = 0, length = attributes.length,
tmp;
i < length; ++i
)
html.setAttribute((tmp = attributes[i]).name, tmp.value)
;
for(var
childNodes = xml.childNodes,
i = 0, length = childNodes.length;
i < length; ++i
){
switch((tmp = childNodes[i]).nodeType){
case 1:
html.appendChild(XML2HTML(tmp));
break;
case 3:
html.appendChild(document.createTextNode(tmp.nodeValue));
break;
case 4:
case 8:
// assuming .text works in every browser
nodeName === "SCRIPT" ?
html.text = tmp.nodeValue :
html.appendChild(document.createComment(tmp.nodeValue))
;
break;
}
};
return html
};

I have tried to post above snippet into John post as well but for some reason it is still not there (maybe waiting to be approved)
We can test above snippet via this piece of code:

var data = '<section data="123"><script><![CDATA[\nalert(123)\n]]></script><option>value</option></section>';
try{
var xml = new ActiveXObject("Microsoft.XMLDOM");
xml.loadXML(data);
}catch(e){
var xml = new DOMParser().parseFromString(data, "text/xml");
}
var section = xml.getElementsByTagName("section")[0];
onload = function(){
document.body.appendChild(XML2HTML(section));
alert(document.body.innerHTML);
};

We can use latest snippet to test the other function as well and as soon as I can I will try to compare solutions to provide some benchmark.

Tuesday, January 27, 2009

png to gif transformation - an excellent free solution via PHP GD2

Why we still need png to gif in our web pages


There a couple of valid plugins for different libraries able to manage "silently" png transparency in order to fix those browsers (cough: IE6) that are still used, for god knows which reason, but not able to understand properly the alpha channel. The problem is that these plugins cause overhead in our pages and for each png images, which is nearly a non-sense since these plugins fix old browsers whose performances have never been brilliant!
Especially for modern GUIs, where little icons are widely used, these plugins could make the application incredibly slow.
If we could choose old good GIF instead of png, 'till the end of those browsers, the problem will not exist and performances will be acceptable.

PNG 2 GIF, simple? ... not, really!


First of all a couple of search via Google brought me in "free but you have to pay anyway" applications through some open source, simple, but not that nice, program language solution, incompatible with modern interpreters or not able to produce a good quality result.
The problem is basically this one: alpha channel is something amazing, but it changes border colors if we are putting the png over a bright background rather than a dark one.
For this reason it is really difficult to obtain a valid GIF copy of the original PNG feel using common transformers, since the main difference is given by our cool theme that can be bright or dark.
Thanks to PHP and its distributed GD2 library, I have been able to create a good compromise between pixelated borders and our chose theme.

png2gif, the php/gd2 way



function png2gif($pngs, $background = array(255, 255, 255), $dest = 'gif'){
// by WebReflection
foreach($pngs as $png){
$size = getimagesize($png);
$img = imagecreatefrompng($png);
$image = imagecreatetruecolor($width = $size[0], $height = $size[1]);
imagefill($image, 0, 0, $bgcolor = imagecolorallocate($image, $background[0], $background[1], $background[2]));
imagecopyresampled($image, $img, 0, 0, 0, 0, $width, $height, $width, $height);
imagecolortransparent($image, $bgcolor);
imagegif($image, str_ireplace('.png', '.gif', $dest.DIRECTORY_SEPARATOR.basename($png)), 100);
imagedestroy($image);
}
}

// example
png2gif(glob("icons/*.png"));

That's it, you copy and paste an entire directory into, for example, an icons folder, and you call the function passing the list of files contained in that directory.
Parameters are the file list itself, the background color, and finally the destination folder to put transformed png into gif, by default the dir "gif".
The main difference between this function and every other method I found, is that borders are gracefully adapted to your main theme, by default a bright one with white background.
In this way you can choose how borders should be readapted instead of black borders for alpha problems, or cutted one. Even circle icons result are, in my opinion, brilliant!
The array should contain R,G,B integer values, from 0 to 255, where array(0,0,0) is black and array(255,255,255) is white, the default background theme.

You can try this function to convert most common icons theme, and please tell me if the result was not good enough ;-)

P.S. for PHP maniacas, the imagecopyresampled gd2 function has been the only one able to merge properly the png into chose background color ... I don't want to tell you how long did it take to find this kind of solution (passing via pixel by pixel color allocation ... omg!)

Saturday, December 06, 2008

A fast and crossbrowser function to make an Array

An absolutely common task present in almost every library, is to transform a generic collection/list of objects or DOM Elements into a friendly Array.

Cases Scenario



  • It is possible to apply directly an Array.prototype.slice call to quickly return an Array

  • It is not possible at all apply every kind of Array prototype to the list



Cases scenario detection


When we execute a piece of JavaScript in a web page, we can assume that we have at least one DOM element in that page, as the script itself for example, so it is always possible to obtain a collection of elements calling document.getElementsByTagName("script") or a generic ("*") in this case probably superflous considering the latter assumption.

Cross browser and fast makeArray proposal



var makeArray = function(push, slice){
try{
// Andrea Giammarchi proposal
slice.call(document.getElementsByTagName("script"), 0);
return function(array, results){
array = array instanceof Array ? array : slice.call(array, 0);
results ? push.apply(results, array) : results = array;
return results
}
}catch(e){
return function(array, results){
if(!(array instanceof Array))
for(var ret = [], i = 0, length = array.length; i < length; i++)
ret[i] = array[i];
else
var ret = array;
results ? push.apply(results, ret) : results = ret;
return results
}
}
}(Array.prototype.push, Array.prototype.slice);


Summary


The try catch is executed only once, the Array prototypes are cached once as well to guarantee cross libraries compatibility. The function is assigned differently for those browsers that can apply the slice prototype to the collection and those that cannot (mainly Internet Explorer).
Accepted parameters are two, inspired by Sizzle function, to allow us to concatenate elements into a collection.

The collection is transformed into an Array only if necessary, since it does not make sense to perform a slice call in every case.

Compatiblity? It should be every browser that supports try and catch statement :-)