Tag Archives: javascript

Instance and class based plugins in YUI 3

Using plugins in YUI 3 is a way to add functionality to existing objects in YUI (very much like you would use prototype to augment a function to a normal JavaScript object). Objects in YUI all derive from Y.Base.

Note that this post is about YUI 3 and not YUI 2.

However, the current YUI 3 documentation does really only get into how to add a plugin to a current instance. What about adding functionality to the class level so all instances of the same type of objects automatically get it?

First an example of how a simple plugin may look like:

NodePlugin = function(config) {
    this._node = config.host;
}

NodePlugin.NS = 'vis';
NodePlugin.NAME = 'NodeVisibiltyPlugin';

NodePlugin.prototype = {
    show: function() {
        this._node.setStyle('display', 'block');
        return this_node;
    },

    hide: function() {
        this._node.setStyle('display', 'none');
        return this_node;
    }
};

What this plugin does it to add hide() and show() functions to the vis namespace (as specified in the code) of Y.Node.

This plugin would typically be used like this:

var node = Y.one('#myElement');
node.plug(NodePlugin); // add plugin functionality
node.vis.hide();

If you would like to use the plugin functionality for all node objects it would be somewhat tedious to invoke plug() for each and every instance. The way to get around this, which is not really documented except in the API reference, is to use the static method Y.Plugin.Host.plug.

Y.Plugin.Host.plug(Y.Node, NodePlugin); // plug once ...

var node = Y.one('#myElement');
node.vis.hide(); // ... use anywhere

Now any new instances of Y.node have access to the plugin methods through the specified namespace. I also discovered that in order for this to work the static NAME property must be defined in the plugin object.

Progressive enhancements with JavaScript and CSS

It is clear that IE6 is not going away just yet, at least not for mainstream websites targeting a large (and non-tech) audience. Quite some time and effort is put into making design and functionality work in IE6 which otherwise works perfectly well in more modern browsers. This is inefficient, but just because IE6 support cannot be dropped does not mean the cause is lost. The key is to not think black and white, instead think in terms of progressive enhancements.

The fundamental concept of progressive enhancements are that anyone may use the functionality, but a more enhanced version is available for those with more advanced browsers.

Having a complex feature with pixel perfect appearance and functionality in all browsers usually means that you implement it on standard compliant browsers, and then provide workarounds to make it work in Internet Explorer (usually a significant amount of work). The key to reduce this waste of time is not to skip IE6 entirely (which may not be an option), but to let go of having everything exactly the same in all browser. Instead, provide the basic functionality to IE6 but add the shinyness and bling to newer browsers.

The old approach

About a year ago I did a JavaScript map implementation (similar to Google Maps) and it had an info box popup that could contain additional information. The original requirements were that it should have rounded corners and a pointer that depending on position could point in four directions (flipping when the map moved). In addition the dimensions should automatically size to the content unless a fixed width and/or height were set.

Old (complex) InfoBox implementation

Old (complex) InfoBox implementation

And it should look exactly the same in all browsers. This single line of requirement resulted in the implementation becoming one of the largest and most complex modules in the whole mapping application. Using css sprites for the corners, off-screen rendering to estimate size, lots of IE workarounds and so on… probably 60% of the development time were spent on compatibility issues.

The modern way

I recently had the opportunity to rewrite the info box implementation, using a much smarter approach. The key was that the basic functionality should work in IE6, but it shouldn’t necessary look the same.

This resulted in an implementation that was 1/5 of the number of lines of the original, didn’t require additional images for the corners (reducing number of HTTP requests), no IE hacks and was a lot more flexible.

By using border-radius and box-shadow available in CSS3 the implementation was very simple, and we even got a drop shadow for free.

InfoBox in modern browsers

InfoBox in modern browsers

Using an old or non-standard browser like Internet Explorer resulted in the following look.

InfoBox in Internet Explorer

InfoBox in Internet Explorer

Still functional and useful, but without the extra visual appearance. However, instead the development time was dramatically reduced and the implementation dead easy instead of very complex.

The challenge

This is all pretty straightforward, why do it any other way? Usually the challenge is not technical, but rather to communicate this to the stakeholder. Most organizations simply state “Support IE6+, FF2+, …” implicitly resulting in requirements demanding exactly the same functionality pixel by pixel between the supported browsers.

This is where it gets wrong and the challenge is to change it! It is not about being supported or not supported – it is about to what extent should a browser be supported! IE6 may be supported in a way that the core functionality is useful, but it may look less attractive and some fancy (but non critical) features may be removed.

To what extent each browser should be supported should be a conscious and strategical choice! The benefits are huge with productivity gains, less complex code and faster time-to-market. And it is up to you, as a developer, to communicate this and make it happen!

Virtual Earth point in bounding box test

I’m currently working with the Microsoft Virtual Earth SDK and wanted to test if a point is inside a box as where coordinates are specified using latitude and longitude.

It appears there are no built in function the API that does this, so this is what I came up with. Please note the special case where the bounding box overlaps the 180th degree longitude.

/**
* Returns true if point is in rectangle.
*
* @param {VELatLong} point Point to check.
* @param {VELatLongRectangle} rect Bounding box.
*/
function isPointInRect(point, rect) {
return ( (rect.TopLeftLatLong.Longitude <= rect.BottomRightLatLong.Longitude && // longitude rect.TopLeftLatLong.Longitude <= point.Longitude && rect.BottomRightLatLong.Longitude >= point.Longitude)
|| (rect.TopLeftLatLong.Longitude > rect.BottomRightLatLong.Longitude && // longitude crosses 180 degrees
rect.TopLeftLatLong.Longitude >= point.Longitude &&
rect.BottomRightLatLong.Longitude <= point.Longitude) ) && (rect.TopLeftLatLong.Latitude >= point.Latitude && // latitude
rect.BottomRightLatLong.Latitude <= point.Latitude); } [/sourcecode] This may be used, among many things, to test if a pushpin shape is within the current map view, and if it isn't the map is panned to make it visible: [sourcecode language='javascript'] var point = shape.GetPoints()[0]; if (!isPointInRect(point, map.GetMapView())) { map.PanToLatLong(point); } [/sourcecode]

Detect if ExCanvas is loaded

When using ExplorerCanvas (excanvas) to emulate the Canvas object in Internet Explorer it might be a good idea to check that the excanvas lib really is loaded before trying to create a context object. If not, an informative error message may be displayed instead of some cryptic “object doesn’t support this property”.

Here is a way to check if excanvas.js has been loaded.

if (typeof window.CanvasRenderingContext2D == 'undefined' &&
    typeof G_vmlCanvasManager == 'undefined') {
        alert("ExCanvas not loaded!");
}

Cross-site data retrieval using JSONP

Traditional data retrieval (in JSON, XML or whatever) using XMLHttpRequest is limited to requests on the same domain. A way to work around this limitation is to use JSONP. The ‘P’ stands for Padding.

The main concept is to dynamically create a script tag and set its source attribute to the URL of the service which you would like your script to communicate with. The service responds with the relevant data in JSON format, but wraps it in a function call (that’s the padding!). The browser will evaluate the JavaScript immediately it is loaded and execute the function call with the JSON retrieved data as a regular object as it’s argument.

If the backend service takes the function name used to wrap the JSON data as an argument in the query string it is possible for the initiating script to set up a callback of choice.

If the backend service is called (i.e. setting the script src attribute) using the following URL:

http://www.stpe.se/service?callback=myMethod

The response from the backend service may look something like:

myMethod({firstValue: 123, secondValue: 456});

The only difference from a traditional JSON service is the padding with the function call (hence the P in JSONP). Of course additional parameters to the backend service may be appended as arguments or part of the URL in a RESTful way.

Here is an example of a JavaScript function that initiates the callback by creating a script tag:

function initJSONP(url) {
    // create script tag
    var script = document.createElement("script");
    script.setAttribute("type", "text/javascript");

    // append timestamp to avoid browser caching
    url += (url.indexOf("?") == -1 ? "?" : "&amp;amp;amp;") + (new Date().getTime());

    // set script source to the service that responds with thepadded JSON data
    script.setAttribute("src", url);

    // insert script tag
    document.body.appendChild(script);

    // remove script tag by timeout (remove responsibility from callback)
    setTimeout(function() {
        document.body.removeChild(script);
    }, 2000);
}

Given our previous example it would be called using:

initJSONP("http://www.stpe.se/service?callback=myMethod");

If the same URL is called multiple times it may be a good idea to append something relatively unique (timestamp in this example) to the URL to avoid having the browser cache the call.

The other peculiarity is how the just created script tag is removed from the document. In this example I’m using a timeout of 2 seconds. The advantage is that the removal is done automatically and there is no responsibility of the callback function to clean up the document (which makes sense since the callback function may be a regular function also invoked from the main JavaScript itself). The disadvantage is if the service takes more than 2 seconds to respond, then the callback will never be made. Your milage may vary, so apply the method that suits your particular demands.

That’s all there is. 🙂

Javascript Array.indexOf() and Array.remove()

Useful convenience methods for array manipulation in Javascript.

Array.indexOf()

Find index of given element in array. This method is implemented in some browsers, but not all.

if (!Array.prototype.indexOf)
{
    /**
     * Add array.indexOf() functionality (exists in >FF 1.5 but not in IE)
     *
     * @param {Object} elem Element to find.
     * @param {Number} [from] Position in array to look from.
     */    
    Array.prototype.indexOf = function(elem /*, from*/) {
        var len = this.length;
        
        var from = Number(arguments[1]) || 0;
        from = (from < 0) ? Math.ceil(from) : Math.floor(from);
        if (from < 0) {
            from += len;
        }
        
        for (; from < len; from++) {
            if (from in this && this&#91;from&#93; === elem) {
                return from;
            }
        }
        
        return -1;
    };
}
&#91;/sourcecode&#93;

<h3>Array.remove()</h3>
Remove element in array (depends on indexOf method).


if (!Array.prototype.remove)
{
    /**
     * Add array.remove() convenience method to remove element from array.
     * 
     * @param {Object} elem Element to remove.
     */
    Array.prototype.remove = function(elem) {
        var index = this.indexOf(elem);
        
        if (index !== -1) {
            this.splice(index, 1);
        }        
    };
}

Simply add these two your Javascript file augment the functionality with the existing Array implementation.