Small Javascript Goodies

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

    Small Javascript Goodies

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

    a quick search showed that JQuery and Prototype already include such functionalities , so the intended audience is non-JQuery/Prototype users.

    is this useful then to post such functions?

    apply a function to a HTMLCollection (node-list)
    originating from MDC's Array.forEach() code
    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 ("function" != typeof fn) 
    	{
    		throw new TypeError();
    	}
    	var args  = Array.prototype.slice.call(arguments, 1);
    	for (var i=0, len=this.length >>> 0; i<len; i++)
    	{
    		if (i in this)
    		{
    			fn.apply(this[i], args);
    		}
    	}
    }
    example usage
    Code:
    // it’s up to your imagination what foo() is doing…
    document.getElementsByTagName("p").applyForEach(foo, "bar")
    assign an Event for each element of a node-list
    Code:
    Object.prototype.addEventForEach = function(type, fn, cpt)
    {
    	if (typeof fn != "function") {
    		throw new TypeError();
    	}
    	for (var i=0, len=this.length >>> 0; i<len; i++)
    	{
    		if (i in this)
    		{
    		//	this[i].addEvent(type, fn, cpt);
    			Events._addEvent(this[i], type, fn, cpt);
    		}
    	}
    }
    example usage:
    Code:
    // trigger a function by clicking on any table cell
    document.getElementsByTagName("td").addEventForEach("click", doSmthWithTableCell);
    converting addEvent(elemen t, …) to element.addEven t(…)
    Code:
    HTMLElement.prototype.addEvent = function(type, fn, capture)
    {
    	Events._addEvent(this, type, fn, capture);
    }
    one addEvent() function put into an object
    Code:
     // stripped off the inline comments
     /**
      * 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);
    		}
    		
    	};
    }();
    Last edited by Dormilich; Nov 9 '09, 07:36 PM. Reason: using direct arguments conversion
  • acoder
    Recognized Expert MVP
    • Nov 2006
    • 16032

    #2
    Originally posted by Dormilich
    a quick search showed that JQuery and Prototype already include such functionalities , so the intended audience is non-JQuery/Prototype users.

    is this useful then to post such functions?
    Why not? Not everyone uses JQuery/Prototype and it's good to have a small collection of useful functions - like a mini-library.

    Comment

    • Dormilich
      Recognized Expert Expert
      • Aug 2008
      • 8694

      #3
      ok, then there are some more:

      making extending an object (OOP) easy
      Code:
      // by Gavin Kistner
      Function.prototype.extend = function( parentClassOrObject )
      {
      	if ( parentClassOrObject.constructor == 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; Nov 9 '09, 10:27 AM. Reason: fix for removing self-defined methods

      Comment

      • Dormilich
        Recognized Expert Expert
        • Aug 2008
        • 8694

        #4
        using logical XOR
        Code:
        if (expr1 ^ expr2)
        it simply works because the boolean result of expr1 and expr2 represent bitwise 0 (false) and 1 (true). Nevertheless, it is still a bitwise operation. I f you get an error, typecast to Boolean.
        Code:
        if (Boolean(expr1) ^ Boolean(expr2))

        Comment

        • Dormilich
          Recognized Expert Expert
          • Aug 2008
          • 8694

          #5
          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 ("function" != typeof fn) {
          		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");
          Last edited by Dormilich; Nov 10 '09, 12:56 PM. Reason: d*** closures… fixed

          Comment

          • Dormilich
            Recognized Expert Expert
            • Aug 2008
            • 8694

            #6
            sometimes you need to check, whether in a set of tests at least one condition fails. one good way of programming this includes Exceptions.
            Code:
            // the test function
            function test(v)
            {
                if ("x" == v) // or whatever condition you like
                {
                    throw new Error("Condition X failed.");
                }
            }
            // the test
            for (var i=0; i<l; i++)
            {
                test(value[i]); // throws an Exception on the first failed test
            }
            suppose you want to check if at least one of the tests does not fail*. here is one possibility to do that using the same test function.
            Code:
            // I call it the “inverted failure test” or “escape test”
            for (var i=0; i<l; i++)
            {
                try
                {
                    test(value[i]);
                }
                // every failed test continues the loop, bypassing the final Exception
                catch (e)
                {
                    continue;
                }
                // if a test does not fail, the catch block is not executed
                // and the final Exception is thrown
                throw new Error("Condition X met.");
            }
            * which in certain circumstances is not wanted

            Comment

            • Dormilich
              Recognized Expert Expert
              • Aug 2008
              • 8694

              #7
              if you want to use WDDX (Web Distributed Data Exchange), several programming languages offer functions to use it. However, WDDX is hardly used with JavaScript (and I only found one useful library), thus I wrote my own WDDX library, which relies on the fact that you normally get such a file via an XMLHttpRequest (specifically the responseXML property).

              here is the derserializer (the serializer is not done commenting yet)
              Code:
              /**
               * WDDX Deserializer for Javascript
               * File: mod_wddx_des.js
               * Author: Bertold von Dormilich (Dormilich@netscape.net), 2009
               * Contributor: Nate Weiss (nweiss@icesinc.com), 1999
               * See www.wddx.org for usage and updates
               */
              
              // Most of the code used in the simple data type conversion (bool, null, number, 
              // date) is taken from the original code. considerable changes were made for 
              // string (the text is already parsed), array and object (using a different 
              // parser model).
              
              // And of course the OOP style was changed to a more modern approach.
              // The main advantage over Nate's WDDX string parser is, that I don't 
              // have to parse the WDDX file, thus I can use DOM.
              
              // used K&R indent style, so it looks somewhat condensed...
              
              /**
               * Prototyping into the DOM interface. Although Firefox provides some 
               * properties that do the same, I can't rely on those yet.
               */
              Element.prototype.getElementChildren = function(tagname)
              {
              	var c, i = 0, elem = [];
              	tagname = tagname || "*";
              	while (c = this.childNodes[i++]) {
              		var tag = ("*" == tagname) ? true : (tagname == c.tagName);
              		if (1 == c.nodeType && tag)
              			elem.push(c);
              	}
              	return elem;
              }
              
              Element.prototype.getFirstElementChild = function(tagname)
              {
              	var c, i = 0;
              	tagname = tagname || "*";
              	while (c = this.childNodes[i++]) {
              		var tag = ("*" == tagname) ? true : (tagname == c.tagName);
              		if (1 == c.nodeType && tag)
              			return c;
              	}
              	return null;
              }
              
              /**
               * Wrapper object for deserialization.
               *
               * @param (Document) xmldoc     an XML document
               * @return (void)
               * @throws (Error)              source not conforming to the DOM
               */
              function WDDX(xmldoc)
              {
              	if (!xmldoc.getElementById)
              		throw new Error("Source is not a Document.");
              	// get the data & comment element
              	var data = xmldoc.getElementsByTagName("data")[0];
              	var comt = xmldoc.getElementsByTagName("comment")[0];
              	if (!data)
              		throw new Error("WDDX document does not contain data.");
              	this.content = new WDDXNode(data.getFirstElementChild());
              	if (comt)
              		this.comment = comt.firstChild.data;
              	this.setApplicationType();
              }
              
              /**
               * Setting a global for using timezone info.
               *
               * @param (bool) use            the useTimezonInfo variable used in getDate()
               * @return (void)
               */
              WDDX.prototype.setTimezoneInfo = function(use)
              {
              	window.useTimezoneInfo = Boolean(use);
              }
              
              /**
               * Setting a global for object classname detection. (some languages save it
               * as type attribute, some in the first <var>)
               *
               * @param (bool) app            application id setting the classname. defaults
               *                              to javascript.
               * @return (void)
               */
              WDDX.prototype.setApplicationType = function(app)
              {
              	window.wddx_application = app || "javascript";
              }
              
              /**
               * Start the deserialization.
               *
               * @return (void)
               */
              WDDX.prototype.deserialize = function()
              {
              	return this.content.parse();
              }
              
              /**
               * Object representing a node of the XML document. Checks for DOM 
               * compliance and extracting some core data (tag name, text content, 
               * element children).
               *
               * @param (Element) knoten      an XML Element node
               * @return (void)
               * @throws (Error)              source not conforming to the DOM
               */
              function WDDXNode(knoten)
              {
              	if (!knoten.getElementChildren)
              		throw new Error("Source is not an Element.");
              	// save the node for DOM manipulations
              	this.node = knoten;
              	// get basic properties
              	this.name = knoten.tagName.toLowerCase();
              	this.text = (knoten.firstChild) ? knoten.firstChild.data : null;
              	this.elements = knoten.getElementChildren();
              }
              
              /**
               * Get the Javascript types & values from the WDDX element.
               *
               * @return (mixed)              deserialized value/object
               * @throws (Error)              unsupported data type
               */
              WDDXNode.prototype.parse = function()
              {
              	switch (this.name) {
              		case "array"   : return this.getArray();
              		case "boolean" : return this.getBoolean();
              		case "datetime": return this.getDate();
              		case "null"    : return this.getNull();
              		case "number"  : return this.getNumber();
              		case "string"  : return this.getString();
              		case "struct"  : return this.getStruct();
              		// just in case...
              		case "var"     : 
              			var vn = new WDDXNode(this.elements[0]);
              			return vn.parse();
              		default: 
              			throw new Error("This type is not supported yet.");
              	}
              }
              
              /**
               * return the boolean value.
               *
               * @return (bool)               deserialized Boolean
               * @throws (Error)              WDDX element not a boolean type
               */
              WDDXNode.prototype.getBoolean = function()
              {
              	if ("boolean" != this.name)
              		throw new Error("WDDX node is not a Boolean.");
              	
              	return ("true" == this.node.getAttribute("value"));
              }
              
              /**
               * return the numeric value.
               *
               * @return (float)              deserialized Number
               * @throws (Error)              WDDX element not a number type
               */
              WDDXNode.prototype.getNumber = function()
              {
              	if ("number" != this.name)
              		throw new Error("WDDX node is not a Number.");
              	
              	return parseFloat(this.text);
              }
              
              /**
               * return null.
               *
               * @return (null)               deserialized value
               * @throws (Error)              WDDX element not a null type
               */
              WDDXNode.prototype.getNull = function()
              {
              	if ("null" != this.name)
              		throw new Error("WDDX node is not a Null type.");
              	
              	return null;
              }
              
              /**
               * return a Date object.
               *
               * @return (Date)               deserialized dateTime
               * @throws (Error)              WDDX element not a dateTime type
               */
              WDDXNode.prototype.getDate = function()
              {
              	if ("datetime" != this.name)
              		throw new Error("WDDX node is not a Date.");
              	
              	var NewDate, TempDate = new Date;
              	this.timezoneOffset = TempDate.getTimezoneOffset();
              	this.timezoneOffsetHours = Math.round(this.timezoneOffset/60);
              	this.timezoneOffsetMinutes = (this.timezoneOffset % 60);
              	
              	// Split date string into component parts
              	var Value = this.text.split("T");
              	var dtDateParts = Value[0].split("-");
              	if ( (Value[1].indexOf("-") == -1) && (Value[1].indexOf("+") == -1) ) {
              		// create native JS Date object
              		var dtTimeParts = Value[1].split(":");
              		NewDate = new Date(dtDateParts[0], dtDateParts[1]-1, dtDateParts[2], dtTimeParts[0], dtTimeParts[1], dtTimeParts[2]);
              	} else {  
              		// There is timezone info for this <dateTime></dateTime> element.
              		// Get just the timezone info by getting everything after the "-" or "+"
              		if (Value[1].indexOf("-") > -1) 
              			dtTimeTZParts = Value[1].split("-");
              		else 
              			dtTimeTZParts = Value[1].split("+");
              		var dtTimeParts = dtTimeTZParts[0].split(":");
              		
              		// Create a new JS date object (no timezone offsetting has happened yet)
              		NewDate = new Date(dtDateParts[0], dtDateParts[1]-1, dtDateParts[2], dtTimeParts[0], dtTimeParts[1], dtTimeParts[2]);
              		
              		// If we are supposed to do timezone offsetting
              		if (true == window.useTimezoneInfo) { 
              			var dtTZParts = dtTimeTZParts[1].split(":");
              			var dtOffsetHours = parseInt(dtTZParts[0]);
              			var dtOffsetMins  = parseInt(dtTZParts[1]);
              			if (Value[1].indexOf("-") > -1) {
              				dtOffsetHours = this.timezoneOffsetHours - dtOffsetHours;
              				dtOffsetMins  = this.timezoneOffsetMinutes - dtOffsetMins;
              			} else {
              				dtOffsetHours += this.timezoneOffsetHours;
              				dtOffsetMins  += this.timezoneOffsetMinutes;
              			}
              			NewDate.setHours(NewDate.getHours() - dtOffsetHours);
              			NewDate.setMinutes(NewDate.getMinutes() - dtOffsetMins);
              		}  
              	}
              	return NewDate;
              }
              
              /**
               * return the string value.
               *
               * @return (strin)              deserialized value
               * @throws (Error)              WDDX element not a string type
               */
              WDDXNode.prototype.getString = function()
              {
              	if ("string" != this.name)
              		throw new Error("WDDX node is not a String.");
              
              	var Value = this.text;
              	// check for text and CDATA nodes
              	var l = this.node.childNodes.length;
              	if (l > 1) {
              		Value = "";
              		for (var i=0; i<l; i++) {
              			var knoten = this.node.childNodes[i];
              			if (3 == knoten.nodeType || 4 == knoten.nodeType) 
              				Value += String(knoten.data); // trim() ???
              		}
              	}
              	return Value;
              }
              
              /**
               * return an Array object.
               *
               * @return (Array)              deserialized array
               * @throws (Error)              WDDX element not a boolean type
               */
              WDDXNode.prototype.getArray = function()
              {
              	var JSArray = new Array;
              	// ArrayLength is the length of the WDDX-style array to parse
              	var ArrayLength = parseInt(this.node.getAttribute("length"));
              	
              	// check the real size of the WDDX array
              	var leng  = this.elements.length;
              	// For each element in the WDDX array, set the corresponding
              	// Element in the JS array to the WDDX element's parsed value  
              	for (var c = 0; c < ArrayLength; c++) { 
              		// if the denoted array length is larger than the real length, 
              		// pad the array with null
              		if (c >= leng) {
              			JSArray[c] = null;
              		} else {
              			// parse the array elements
              			var item = new WDDXNode(this.elements[c]);
              			JSArray[c] = item.parse();
              		} 
              	}
              	return JSArray;
              }
              
              /**
               * return an Object object.
               *
               * @return (Object)             deserialized object
               * @throws (Error)              WDDX element not a boolean type
               */
              WDDXNode.prototype.getStruct = function()
              {
              	var JSObject, StructIndex, items;
              	// Call object constructor if a "type" attribute has been provided
              	if ("javascript" == window.wddx_application) 
              		type = String(this.node.getAttribute("type"));
              	// check if a php_class_name is provided--and should be used
              	var pcn = this.node.getFirstElementChild("var");
              	if ("php_class_name" == pcn.getAttribute("name")) {
              		if ("php" == window.wddx_application) {
              			try {
              				var kn = new WDDXNode(pcn.getFirstElementChild());
              				type = String(kn.getString());
              			} catch (e) {
              				type = false;
              			}
              		}
              		// always remove php_class_name variable
              		this.node.removeChild(pcn);
              	}
              	// call object constructor if provided
              	if (type && "function" == typeof window[type])
              		JSObject = new window[type];
              	else
              		JSObject = new Object;
              
              	// there should only be <var> children, but you never know...
              	items = this.node.getElementChildren("var");
              	for (var l, c = 0, l = items.length; c < l; c++) { 
              		// use the <var> element's child node 
              		var item = new WDDXNode(items[c].getFirstElementChild());
              		StructIndex = items[c].getAttribute("name");
              		JSObject[StructIndex] = item.parse();
              	}
              	return JSObject;
              }

              Comment

              • Niheel
                Recognized Expert Moderator Top Contributor
                • Jul 2005
                • 2433

                #8
                Break this down into several smaller articles or combine all the posts into one if you can. Insights is really article format so it should be one @ a time per submission, sorry if this caught you off gaurd.

                PM me if you need help combining them or editing it into several small pieces in a series.
                niheel @ bytes

                Comment

                Working...