Wednesday, January 29, 2014

Extending Javascript Array

It's surprising to some that Javascript has all sort of methods to manipulate Arrays but none of these (in current specs.) allows to quickly find Max/Min values stored in array. Sometimes its tempting to quickly extend native JS Array. So having array e.g.:

var beersDrunk=[22,33,44,32,1];

We trying to find max value stored in this array. One option is to add (static) max() method to Array below:

Array.max=function(v) {
    return Math.max.apply(this,v);
};


This will allow us to call:

Array.max(beersDrunk); // returns 44

Or other way you can define max() on Array's prototype as:

Array.prototype.max=function() {
    return Math.max.apply(this,this);
};


This allows you to call max() method directly on the Array's object itself e.g.:

beersDrunk.max(); // returns 44

If you think "cool, lets use it in my code!" beware.., its not bulletproof method and has obvious problems. It will fail (return NaN) if array contains anything else then numbers e.g. [22,"boom",44,32,1], its not the fastest way to look up Max/Min values etc. etc. Probably the worst however is that monkey patching like this breaks encapsulation. Imagine if at some point ECMAScript committee decides to add max() method to Javascript Array. Your implementation will override theirs and booom, this might cause serious problems. Extending array's prototype will also give you more headaches if you are using  (dont!) for in  to iterate over arrays. This is a huge topic and luckily for all of us there are ton's of resources all over the web on problems of extending native Javascript objects. It might be worth googling the topic if you decide to use this method in your code.

Despite all the problems it is interesting method nonetheless. If nothing else, being able to understand how the code above works will make you a better programmer.

Monday, January 6, 2014

Dynamically injecting elements into existing SVG

It might happen that when dealing with SVG in HTML page you will need to inject certain SVG tags somewhere inside of existing SVG element. It might seems straightforward and one would think that standard JS document.createElement will do the trick. Well, not quite... Yes, it will insert element into DOM and when inspected the element will be there as it should, however the SVG displayed will not change. Consider the code below with single SVG element with image. We will try to add mask to it:

HTML:

<svg height="600" version="1.1" width="800" xmlns="http://www.w3.org/2000/svg" style="overflow: hidden; position: relative;">      
    
    <image x="50" y="50" width="120" height="120" preserveAspectRatio="none" xlink:href="https://lh3.googleusercontent.com/-0afLqDT73Yk/AAAAAAAAAAI/AAAAAAAAAH0/wf_rwvjAuOY/s120-c/photo.jpg" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);" fill="#008000" mask="url(#mask)"></image>
    
</svg>

JavaScript where we injecting <mask ...> tag into HTML above:

// create two elements
var mask = document.createElement('mask');
var circle = document.createElement('circle');

// apply attributes and what not
mask.setAttribute("maskUnits","objectBoundingBox");
mask.setAttribute("maskContentUnits","objectBoundingBox");
mask.setAttribute("id", "mask");
mask.setAttribute("x", "0");
mask.setAttribute("y", "0");
mask.setAttribute("width", "1");
mask.setAttribute("height", "1");

circle.setAttribute("cx", "0.5");
circle.setAttribute("cy", "0.5");
circle.setAttribute("r", "0.5");
circle.setAttribute("fill", "white");

mask.appendChild(circle);

document.getElementsByTagName("svg")[0].appendChild(mask);

The code above will inject <mask ...> and <circle...> tags into DOM but the mask will have no effect. One dirty method is to remove SVG element from DOM and insert it again e.g. with jQuery call $("#elementId").html($("#elementId").html()); but its dirty workaround IMO. The fix to above code is as simple. All needs to be done  is to use document.createElementNS rather than document.createElement. Long story short this will create element with specified namespace. If you are a geek then have a look at MDN where createElementNS is documented or if you are true nerd with no girlfriend, thick glasses and nothing better to do check out namespaces in XML. For all the rest looking for quick fix simply have a look at code below. The only difference is document.createElementNS where we need to pass namespace URI as well as element's name we want to create:

var mask = document.createElementNS("http://www.w3.org/2000/svg", 'mask');
var circle = document.createElementNS("http://www.w3.org/2000/svg", 'circle');

circle.setAttribute("cx", "0.5");
circle.setAttribute("cy", "0.5");
circle.setAttribute("r", "0.5");
circle.setAttribute("fill", "white");

mask.setAttribute("maskUnits","objectBoundingBox");
mask.setAttribute("maskContentUnits","objectBoundingBox");
mask.setAttribute("id", "mask");
mask.setAttribute("x", "0");
mask.setAttribute("y", "0");
mask.setAttribute("width", "1");
mask.setAttribute("height", "1");

mask.appendChild(circle);

document.getElementsByTagName("svg")[0].appendChild(mask);

I hope above will save someone trouble of googling this stuff any further. Oy yeh, no need to copy/paste this stuff, fiddle is here.