Small JavaScript Goodies: Prototyping

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • Dormilich
    Recognized Expert Expert
    • Aug 2008
    • 8694

    Small JavaScript Goodies: Prototyping

    I'd like to post some functions that proved useful for me.

    apply a function to a HTMLCollection (node-list)
    note: a HTMLCollection* (or NodeList**) is the type of object you get when you call the function getElementsByNa me(), getElementsByTa gName(), getElementsByCl assName()
    originating from MDC's Array.forEach() code

    * the HTML interface of such an object (only works in (X)HTML files)
    ** the DOM interface of such an object (works also on XML files)

    Code:
    // Firefox does not allow prototyping into the NodeList or HTMLCollection interface
    // so their parent object has to be used
    Object.prototype.applyForEach = function(fn)
    {
    	if (!(fn instanceof Function)) {
    		throw new TypeError("passed parameter is not a function.");
    	}
    	var args  = Array.prototype.slice.call(arguments, 1);
    	for (var i = this.length; i--;) {
    		if (i in this) {
    			fn.apply(this[i], args);
    		}
    	}
    }
    example usage
    Code:
    // wrap a text in two given strings (just an example, it could be anything)
    function foo(sign1, sign2)
    {
        this.textContent = sign1 + this.textContent + sign2;
    }
    // wrap all quotes in french quotation marks
    document.getElementsByTagName("quote").applyForEach(foo, "«", "»");
    Code:
    <p>Hamlet said <quote>To die or not to die …</quote>.</p>
    would produce:
    Hamlet said «To die or not to die …».

    assign an Event for each element of a node-list
    note: the used addEvent() function is explained below, but you can use any addEvent() function you want (just change the call)
    Code:
    Object.prototype.addEventForEach = function(type, fn, cpt)
    {
    	if (!(fn instanceof Function)) 
    		throw new TypeError("passed parameter is not a function.");
    	}
    	for (var i = this.length; i--;) {
    		if (i in this) {
    			Events._addEvent(this[i], type, fn, cpt); // [I]use[/I] addEvent(this[i], type, fn, cpt); [I]for the generic addEvent() function[/I]
    		}
    	}
    }
    example usage:
    Code:
    // again an example function, alert the elements title attribute
    function show()
    {
        if (this.title) alert(this.title);
    }
    
    // trigger an alert by clicking on any table cell
    document.getElementsByTagName("td").addEventForEach("click", show);
    Code:
    <table>
        <tr>
            <td title="english">green</td>
            <td title="german">grün</td>
            <td title="french">verde</td>
        </tr>
    </table>

    converting addEvent(elemen t, …) to element.addEven t(…)
    this is just for convenience to be able to call addEvent() in the same manner as addEventListene r().
    Code:
    HTMLElement.prototype.addEvent = function(type, fn, capture)
    {
        Events._addEvent(this, type, fn, capture);
    }
    a modification of addEvent() where you can pass parameters directly (needless to say that you can do this also with addEventForEach ())
    Code:
    Element.prototype.addEvent = function(type, fn, capture) 
    {
    	if (!(fn instanceof Function)) {
    		throw new TypeError("Function expected!");
    	}
    	if (arguments.length < 4) {
    		Events._addEvent(this, type, fn, capture);
    	} else {
    		var args  = Array.prototype.slice.call(arguments, 3);
    		var tmpfn = function() {
    			fn.apply(this, args);
    		}
    		Events._addEvent(this, type, tmpfn, capture);
    	}
    }
    Code:
    // calling foo("bar") on click
    document.getElementById("test").addEvent("click", foo, false, "bar");
    one addEvent() function put into an object
    (to prevent the pollution of the global scope with functions that should be treated private*)

    * JavaScript (in contrast to PHP and other OOP capable languages) does not know the concept of private properties/methods
    Code:
     /**
      * Crossbrowser event handling functions.
      *
      * A set of functions to easily attach and detach event handlers to HTML elements.
      * These functions work around the shortcomings of the traditional method ( element.onevent = function; )
      * where only 1 handler could be attached for a certain event on the object, and mimic the DOM level 2
      * event methods addEventListener and removeEventListener for browsers that do not support these
      * methods (e.g. Internet Explorer) without resorting to propriety methods such as attachEvent and detachEvent
      * that have a whole set of their own shortcomings.
      * Created as an entry for the 'contest' at quirksmode.org:
      * http://www.quirksmode.org/blog/archives/2005/09/addevent_recodi.html
      *
      * @author Tino Zijdel ( crisp@xs4all.nl )
      * @version 1.2
      * @date 2005-10-21
      */
    Events = function() {
     
         /**
          * [private] array_search
          * 
          * Searches the array for a given value and returns the (highest) corresponding key if successful, -1 if not found.
          *
          * @param val The value to search for.
          * @param arr The array to search in.
          * @return (mixed) array index of val.
          */
        function array_search(val, arr)
        {
            var i = arr.length;
            while (i--) {
                if (arr[i] && arr[i] === val) break;
            }
            return i;
        }
     
         /**
          * [private] IEEventHandler
          * 
          * IE helper function to execute the attached handlers for events.
          * Because of the way this helperfunction is attached to the object (using the DOM level 0 method)
          * the 'this' keyword will correctely point to the element that the handler was defined on.
          *
          * @param e (optional) Event object, defaults to window.event object when not passed as argument (IE).
          * @return (mixed)
          */
        function IEEventHandler(e)
        {
            e = e || window.event;
            var evTypeRef = '__' + e.type, retValue = true;
     
            for (var i = 0, j = this[evTypeRef].length; i < j; i++) {
                if (this[evTypeRef][i]) {
                    if (Function.call) {
                        retValue = this[evTypeRef][i].call(this, e) && retValue;
                    }
                    else {
                        this.__fn = this[evTypeRef][i];
                        retValue = this.__fn(e) && retValue;
                    }
                }
            }
     
            if (this.__fn) {
                try { 
                    delete this.__fn; 
                } 
                catch(e) { 
                    this.__fn = null; 
                }
            }
            return retValue;
        }
     
        return {
     
             /**
              * [public] addEvent
              *
              * Generic function to attach event listeners to HTML elements.
              * This function does NOT use attachEvent but creates an own stack of function references
              * in the DOM space of the element. This prevents closures and therefor possible memory leaks.
              * Also because of the way the function references are stored they will get executed in the
              * same order as they where attached - matching the behavior of addEventListener.
              *
              * @param obj The object to which the event should be attached.
              * @param evType The eventtype, eg. 'click', 'mousemove' etcetera.
              * @param fn The function to be executed when the event fires.
              * @param useCapture (optional) Whether to use event capturing, or event bubbling (default).
              * @return (void)
              */
            _addEvent : function(obj, evType, fn, useCapture)
            {
                if (!useCapture) useCapture = false;
     
                if (obj.addEventListener) {
                    obj.addEventListener(evType, fn, useCapture);
                }
                else {
                    if (useCapture) {
                        alert('This browser does not support event capturing!');
                    }
                    else {
                        var evTypeRef = '__' + evType;
                        if (obj[evTypeRef]) {
                            if (array_search(fn, obj[evTypeRef]) > -1) return;
                        }
                        else {
                            obj[evTypeRef] = [];
                            if (obj['on'+evType]) {
                                obj[evTypeRef][0] = obj['on'+evType];
                            }
                            obj['on'+evType] = IEEventHandler;
                        }
                        obj[evTypeRef][obj[evTypeRef].length] = fn;
                    }
                }
            },
     
             /**
              * [public] removeEvent
              *
              * Generic function to remove previously attached event listeners.
              *
              * @param obj The object to which the event listener was attached.
              * @param evType The eventtype, eg. 'click', 'mousemove' etcetera.
              * @param fn The listener function.
              * @param useCapture (optional) Whether event capturing, or event bubbling (default) was used.
              * @return (void)
              */
            _removeEvent : function(obj, evType, fn, useCapture)
            {
                if (!useCapture) useCapture = false;
     
                if (obj.removeEventListener) {
                    obj.removeEventListener(evType, fn, useCapture);
                }
                else {
                    var evTypeRef = '__' + evType;
                    if (obj[evTypeRef]) {
                        var i = array_search(fn, obj[evTypeRef]);
                        if (i > -1) {
                            try {
                                delete obj[evTypeRef][i];
                            }
                            catch(e) {
                                obj[evTypeRef][i] = null;
                            }
                        }
                    }
                }
            },
     
            /**
             * [public] loading
             *
             * simplifies the window.onload calls
             *
             * @param callback the function called onload
             * @return (void)
             */
            loading : function(callback)
            {
                Events._addEvent(window, "load", callback);
            }
     
        };
    }();
    making extending an object (OOP) easy
    Code:
    // by Gavin Kistner
    Function.prototype.extend = function(parentClassOrObject)
    {
    	if (parentClassOrObject.constructor instanceof Function) {
    		//Normal Inheritance
    		this.prototype = new parentClassOrObject;
    		this.prototype.constructor = this;
    		this.prototype.parent = parentClassOrObject.prototype;
    	}
    	else {
    		//Pure Virtual Inheritance
    		this.prototype = parentClassOrObject;
    		this.prototype.constructor = this;
    		this.prototype.parent = parentClassOrObject;
    	}
    	return this;
    }
    Code:
    // use like this
    function theParent() { // code comes here }
    function theChild() { // code comes here }
    theChild.extend(theParent);
    
    // now any instance of theChild can use the code from theParent
    create a new child element (if innerHTML should fail or you want to code standard compliant)
    Code:
    // uses DOM-level-1 for backward compatibility
    HTMLElement.prototype.appendCustomElement = function(name, text, attr) 
    {
    	var CE = document.createElement(name);
    	var TN = document.createTextNode(text);
    	CE.appendChild(TN);
    
    	if (attr) {
    		for (var j in attr) {
    			// if you extended the Object interface:
    			if ("function" == typeof attr[j]) continue;
    			var CA = document.createAttribute(j);
    			CA.nodeValue = attr[j];
    			CE.setAttributeNode(CA);
    		}
    	}
    	this.appendChild(CE);
    	return CE;
    }
    Code:
    // use like this
    document.getElementById("test").appendCustomElement("a", "next page", {href: "next.html", class: "sidebar"});
    
    // would result in
    <div id="test">
        <a href="next.html" class="sidebar">next page</a>
    </div>
    Last edited by Dormilich; Feb 22 '10, 11:15 AM. Reason: minor code changes
Working...