16 really useful JavaScript solutions

Published on July 02, 2008

16 really useful JavaScript solutions

    © shamansir.wordpress.com

    I present to you the set of functions that I have in a separate utils.js file - these are the functions that I use most often. They try to be cross-browser and are tested on IE6 / 7, FF2 and Safari 2 and on a combat, complex system, in XHTML documents. They should, in theory, work on other, but not very old versions of browsers - I used browser verification only in exceptional cases. Some of them, of course, are simply dug on the Internet (where it is usually indicated) and borrowed due to openness, and most of them are constructed from many resources and their ideas (and the advice of colleagues) in order to work with a bang - because often in different scripts do not take into account different subtleties, which, nevertheless - upon closer examination - turn out to be communities :), well, and be quite readable.

    The functions are divided thematically:
    OOP - providing (or rather emulation) the ability to use the principles of OOP in JavaScript
    JS object model - using and extending JS built-in objects
    Browser definition - to be used in those rare cases when it is inevitably necessary
    Coordinates / Pozitsiionirovanie - calculation of coordinates and positioning of objects - in view of the fact that it is often quite tricky
    the DOM - work with the document object model
    the AJAX - support functions for the AJAX - as this means hour of applicable
    Logging -sometimes you just can’t do without it

    1. The first block is a set of three functions (two of which are empty) that allow you to apply (emulate?) all three OOP principles in JavaScript . Of the several options proposed on AJAXPath and AJAXPatterns, I chose this one because of its simultaneous comprehensibility and fast execution speed and modified it a bit, so that separately declared properties are perceived as static constants.
    function Class () {};

    Class.prototype.construct = function () {};

    Class.extend = function (def) {

      var classDef = function () {
        if (arguments [0]! == Class) {
          this .construct.apply ( this , arguments);
        }
      };

      var proto = new this (Class);
      var superClass = this .prototype;

      for ( var n in def) {
        var item = def [n];
        if (item instanceof Function) item. $ = superClass;
            else classDef [n] = item;
        proto [n] = item;
      }

      classDef.prototype = proto;
      classDef.extend = this .extend;

      return classDef;
    }; * This source code was highlighted with Source Code Highlighter .


    2. The next function - simple but elegant - is useful in combination with the previous set - it creates a function link to a method :
    function createMethodReference ( object , methodName) {
      return function () {
        return object [methodName] .apply ( object , arguments);
      };
    } * This source code was highlighted with Source Code Highlighter .


    Now you can, for example, do this:
    var ScrollingHandler = Class.extend ({

      construct:
        function (elementId) {
          this ._elementId = elementId;
          this .assignListener ();
        },

      assignListener:
        function () {
          var scrollControlElem =
                document .getElementById ( this ._elementId);
          if (scrollControlElem ) {
            scrollControlElem.onscroll =
              createMethodReference ( this , "_onElementScroll" );
          }
        },

      _onElementScroll:
        function (ev) {
          ev = ev || window.event ;
          alert ( "please stop scrolling,
                I've already got an event:„ + ev);
        }
    });

    var elmScrollHandler = new ScrollHandler ( 'SomeElmId' ); * This source code was highlighted with Source Code Highlighter .


    An object of this class can be associated with the event of scrolling the element with the specified ID and do something for this case.

    JS Object Model


    3. The function below clones any object with all its properties:

    function cloneObj (objToClone) {
      var clone = [];
      for (i in objToClone) {
        clone [i] = objToClone [i];
      }
      return clone;
    } * This source code was highlighted with Source Code Highlighter .

    Use - the simplest to the impossibility:
    var clonedObj = cloneObj (objToClone); * This source code was highlighted with Source Code Highlighter .


    4. Object Converter , the following function, allows you to conveniently use all sorts of conditional (and pretending to be) constructions of the form
    if (tablet.toLowerCase () in oc ([ 'cialis' , 'mevacor' , 'zocor' ])) {alert ('I will not!')}; * This source code was highlighted with Source Code Highlighter .

    The code is borrowed from here .

    function oc (a) {
      var o = {};
      for ( var i = 0; i <a.length; i ++) {
        o [a [i]] = ”;
      }
      return o;
    }
    * This source code was highlighted with Source Code Highlighter .


    As an example, let us take a situation where it is first necessary to determine whether an object is included in any set of single objects, and then whether it, in combination with another object, is included in another set of pairs of objects. Let’s say that only singles with certain names are allowed to the party, or pairs from the list with allowed combinations of names:
    function isPersonAllowed (maleName, femaleName) {
      var pairsAllowed = new Array ([ “John” , “Yoko” ],
          [ “Bill” ,  “Monica” ], [ “Phil” ,  “Sue” ],
          [ “Jason” ,  “ Harrison " ], [ " Adam " ,  " Eve " ]);
      var singlesAllowed = new Array (”Michael”, “Pete”, “John”,
          “Dave”, “Matthew”);
      return (femaleName
          ? ([maleName, femaleName] in oc (pairsAllowed))
          : (maleName in oc (singlesAllowed)));
    }

    alert (isPersonAllowed (”Jack”)); // false
    alert (isPersonAllowed (”Adam”)); // false
    alert (isPersonAllowed (”John”)); // true
    alert (isPersonAllowed (”Phil”, ”Marlo”)); // false
    alert (isPersonAllowed (”Jason”, ”Harrison”)); // true
    alert (isPersonAllowed (”Martin”, ”Luther”)); // false * This source code was highlighted with Source Code Highlighter .


    5. The function that allows you to create a hash at first seems a bit redundant: the objects in JavaScript are the same hashes, but sometimes you need to set the value of the variable as the name of the property / key and then the Hash function comes to the rescue. (yes, of course there are built-in features, but it’s probably just a little more obvious - you can exclude this function from useful ones if you want

    function Hash ()
    {
      this .length = 0;
      this .items = new Array ();
      for ( var i = 0; i <arguments.length; i ++) {
        this .items [arguments [i] [0]] = arguments [i] [1];
      }
    } * This source code was highlighted with Source Code Highlighter .


    Access to the elements is carried out due to the property items(by the way, it should be added in a heavier version keys):
    var Game = Class.extend ({
      STG_STOP: 0,
      STG_START: 1,
      STG_LOADING: 2,
      STG_MENU: 3,
      STG_PROCESS: 4,

      construct:
        function () { this ._stage = Game.STG_LOADING;},

      getStage:
        function () { return this ._stage;}

    });

    var stateMap = new Hash (
          [Game.STG_START,  "start"   ],
          [Game.STG_LOADING, "loading"  ],
          [Game.STG_MENU,   "menu"    ],
          [Game.STG_PROCESS, "process"  ],
          [Game.STG_STOP,   "stopping" ]);

    var someGame = new Game ();
    alert (”You are in “ + stateMap.items [someGame.getStage ()] + ”stage!”); * This source code was highlighted with Source Code Highlighter .


    6. Three other functions simply simplify and / or make some operations more obvious: it getTimereduces access to the current time by 11 characters , getTimeDeltaallows you to find the gap in milliseconds between time intervals (or the specified moment and current time, in a format with one parameter), and the last the function extends the properties of the object Numberso that at its valueNaN it is possible to get a0 little faster .
    function getTime () {
      return new Date (). getTime ();
    }

    function getTimeDelta (timeBegin, timeEnd) {
      timeEnd = timeEnd || getTime ();
      return timeEnd - timeBegin;
    }

    Number.prototype.NaN0 = function () { return isNaN ( this )? 0: this ; } * This source code was highlighted with Source Code Highlighter .


    Browser definition


    7. A small object, whose properties are named according to the names of browsers - the essence of the condition. This achieves a more readable (but not as meticulous as it could be) definition of most types of browsers . This object was borrowed by me from a project in which I participated - and somehow took root, but, I think, the true authors are still somewhere on the network, and the code is not so complicated and bulky to pretend strongly :) . In addition, it is certainly not perfectly reliable (and some say that it is not reliable at all), but so far on the listed browsers it has never failed me :). If you are not comfortable with this state of affairs, you can use something similar with HowToCreate. And I repeat: I try to use this definition (as it was said, for example, by reference) “only if a specific bug is known in a particular browser and needs to be bypassed” . Also - it is easy to rebuild this object into one long condition, for lower execution speed (see, again, the link )

    var USER_DATA = {
      Browser: {
        KHTML: /Konqueror|KHTML/.test(navigator.userAgent) &&

            ! /Apple/.test (navigator.userAgent),
        Safari: /KHTML/.test(navigator.userAgent) &&
            / Apple / .test (navigator.userAgent),
        Opera: !! window.opera,
        MSIE: !! (window.attachEvent &&! window.opera),
        Gecko: /Gecko/.test(navigator.userAgent) &&
            ! / Konqueror | KHTML / .test (navigator.userAgent)
      },
      OS: {
        Windows: navigator.platform.indexOf ( "Win" )> -1,
        Mac: navigator.platform.indexOf ( "Mac" )> -1,
        Linux: navigator.platform. indexOf ( "Linux" )>-one
      }
    } * This source code was highlighted with Source Code Highlighter .


    Coordinates / Positioning


    8. A set of functions that allow you to get the coordinates of the element on the user's screen. If your document is static relative to the window and does not have scrollbars - it is better to use the function getPosition- it will be faster. In the opposite case, use getAlignedPosition- it takes into account the position of scrollbars. Just pay attention: the value of topthe element can be indicative if the element is the upper part outside the window - for synchronization with the mouse cursor sometimes you need to reset the height in this case. The main script is borrowed from one blog , the Aligned version is the result of searches in the gimbal and combining with information from two articles (if foundDOCTYPEIE enters its own, somewhat unpredictable, mode). Also, this method is combined with obtaining positions from the sources of the Drag'n'Drop manual . Please note: the function NaN0from point 6 is used here , you will need to add it to the script so that everything works as it should (thanks, Homer ).
    var IS_IE = USER_DATA [ 'Browser' ] .MSIE;

    function getPosition (e) {
      var left = 0;
      var top = 0;

      while (e.offsetParent) {
        left + = e.offsetLeft + (e.currentStyle?
          (parseInt (e.currentStyle.borderLeftWidth)). NaN0 (): 0);
        top + = e.offsetTop + (e.currentStyle?
          (parseInt (e.currentStyle.borderTopWidth)). NaN0 (): 0);
        e = e.offsetParent;
      }

      left + = e.offsetLeft + (e.currentStyle?
          (parseInt (e.currentStyle.borderLeftWidth)). NaN0 (): 0);
      top + = e.offsetTop + (e.currentStyle?
          (parseInt (e.currentStyle.borderTopWidth)). NaN0 (): 0);  

      return{x: left, y: top};
    }

    function getAlignedPosition (e) {
      var left = 0;
      var top = 0;

      while (e.offsetParent) {
        left + = e.offsetLeft + (e.currentStyle?
          (parseInt (e.currentStyle.borderLeftWidth)). NaN0 (): 0);
        top + = e.offsetTop + (e.currentStyle?
          (parseInt (e.currentStyle.borderTopWidth)). NaN0 (): 0);
        e = e.offsetParent;
        if (e.scrollLeft) {left - = e.scrollLeft; }
        if (e.scrollTop) {top - = e.scrollTop; }
      }

      var docBody = document .documentElement?
        document .documentElement: document .body;

      left + = e.offsetLeft +
        (e.currentStyle?
            (parseInt (e.currentStyle.borderLeftWidth)). NaN0 ()
            : 0) +
        (IS_IE? (parseInt (docBody.scrollLeft)). NaN0 (): 0) - ( parseInt (docBody.clientLeft)). NaN0 ();
      top + = e.offsetTop +
        (e.currentStyle?
            (parseInt (e.currentStyle.borderTopWidth)). NaN0 ()
            : 0) +
        (IS_IE? (parseInt (docBody.scrollTop)). NaN0 (): 0) - ( parseInt (docBody.clientTop)). NaN0 ();

      return {x: left, y: top};
    } * This source code was highlighted with Source Code Highlighter .


    9. It is easy to determine the current coordinates of the mouse cursor and the offset of the element relative to the cursor , if you use the appropriate functions:
    function mouseCoords (ev) {
      if (ev.pageX || ev.pageY) {
        return {x: ev.pageX, y: ev.pageY};
      }

      var docBody = document .documentElement
                ? document .documentElement
                : document .body;

      return {
        x: ev.clientX + docBody.scrollLeft - docBody.clientLeft,
        y: ev.clientY + docBody.scrollTop - docBody.clientTop
      };
    }

    function getMouseOffset (target, ev, aligned) {
      ev = ev || window. event ;
      if (aligned == null ) aligned =to false ;

      var docPos = aligned
        ? getAlignedPosition (target)
        : getPosition (target);
      var mousePos = mouseCoords (ev);

      return {
        x: mousePos.x - docPos.x,
        y: mousePos.y - docPos.y
      };
    }
    * This source code was highlighted with Source Code Highlighter .


    The latter function can also be used in two modes due to the attribute alignedand is intended for convenient use in event handlers, for example:
    function onMouseMove (elm, ev) {
      var mouseOffset = getMouseOffset (elm, ev);
      console.log ( "x:% d; y:% d" , mouseOffset.x, mouseOffset.y);
    }
    ...
    <div id = "someId" onmousemove = "onMouseMove (this, event);
        return false; " > </div> * This source code was highlighted with Source Code Highlighter .


    10. Determining the height of an element is sometimes more difficult than determining its other parameters, but these two functions will come to the rescue:
    function findOffsetHeight (e) {
      var res = 0;
      while ((res == 0) && e.parentNode) {
        e = e.parentNode;
        res = e.offsetHeight;
      }
      return res;
    }

    function getOffsetHeight (e) {
      return this .element.offsetHeight ||
          this .element.style.pixelHeight ||
          findOffsetHeight (e);
    } * This source code was highlighted with Source Code Highlighter .


    Dom


    11. Sometimes you need to walk recursively through the DOM tree , starting with some element and performing some function on each of the descendants, climbing into the very depths. There is an object in the DOM TreeWalker, but it does not work in IE and is not always convenient / easy to use. The function walkTreeallows you to perform some other function on each of the elements and also allows you to transfer some data packet into it. The function searchTreediffers from it in that it stops the tree passage on the first successful result and returns the result to the call point:
    function walkTree (node, mapFunction, dataPackage) {
      if (node ​​== null ) return ;
      mapFunction (node, dataPackage);
      for ( var i = 0; i <node.childNodes.length; i ++) {
        walkTree (node.childNodes [i], mapFunction, dataPackage);
      }
    }

    function searchTree (node, searchFunction, dataPackage) {
      if (node ​​== null ) return ;
      var funcResult = searchFunction (node, dataPackage);
      if (funcResult) return funcResult;
      for ( vari = 0; i <node.childNodes.length; i ++) {
        var searchResult = searchTree (node.childNodes [i],
                  searchFunction, dataPackage);
        if (searchResult) return searchResult;
      }
    } * This source code was highlighted with Source Code Highlighter .


    In the example, the functions setElmAttrand are used getElmAttr, which will be considered later - in paragraph 13 . In fact, they are doing the same thing getAttributeas setAttribute. Explanations for the function used occan be found in paragraph 4 . In the first part of the example, the root element attribute is set to “nodeType” in “root”, and to all its descendants - in “child”. The second part also demonstrates the transmission of a data packet - when the first element is found with the “class” attribute equal to one of the names listed in the package, the “isTarget” attribute is set to “true”.

    var rootElement = document .getElementById ( 'rootElm' );

    setElmAttr (rootElement, "nodeType" , "root" );
    var childNodeFunc = function (node) {
      if (node.nodeName && (node.nodeName! == '#text' )
                && (node.nodeName! == '#comment' )) {
        setElmAttr (node, "nodeType" , " child " );
      }
    }
    walkTree (rootElement, childNodeFunc);

    var findTargetNode = function (node, classList) {
      if((node.nodeName && (node.nodeName! == '#text' )
              && (node.nodeName! == '#comment' )) &&
              (getElmAttr (node, "class" ) in oc (classList))) {
        return node;
      }
    }
    var targetNode = searchTree (rootElement, findTargetNode,
              [ 'headingClass' , 'footerClass' , 'tableClass' ]);
    setElmAttr (targetNode, “isTarget”, true ); * This source code was highlighted with Source Code Highlighter .


    NB! (be careful when using these functions and try to avoid them being called too often (more than once per second) even on a medium branching tree - they can consume a lot of resources. or, at least, call them in the background via setTimeout)

    12. Removing nodes - sometimes necessary task. Sometimes you need to remove the node itself, and sometimes only its descendants. The function removeChildrenRecursivelyrecursively deletes all descendants of the specified node, without affecting, of course, itself. The function removeElementById, as the name says, removes the node by it id- for all the simplicity of the task, the method is relatively tricky:
    function removeChildrenRecursively (node)
    {
      if (! node) return ;
      while (node.hasChildNodes ()) {
        removeChildrenRecursively (node.firstChild);
        node.removeChild (node.firstChild);
      }
    }

    function removeElementById (nodeId) {
      document .getElementById (nodeId) .parentNode.removeChild (
                  document .getElementById (nodeId));
    }
    * This source code was highlighted with Source Code Highlighter .


    13. It would seem - the elementary task of working with element attributes - sometimes encounters completely unexpected problems: for example, IE throws an exception when trying to access the height / width attributes of an element table, and Safari has a different way of accessing attributes with namespaces. The functions below bypass all the problems I have encountered without severely affecting the execution speed (of course, in standard cases it is better to use the built-in functions):
    var IS_SAFARI = USER_DATA [ 'Browser' ] .Safari;

    function getElmAttr (elm, attrName, ns) {
      // IE6 fails getAttribute when used on table element
      var elmValue = null ;
      try {
        elmValue = (elm.getAttribute
              ? elm.getAttribute ((ns? (ns + NS_SYMB): ”)
              + attrName): null );
      } catch (e) { return null ; }
      if (! elmValue && IS_SAFARI) {
        elmValue = (elm.getAttributeNS
              ? elm.getAttributeNS (ns, attrName)
              : null );
      }
      return elmValue;
    }

    function setElmAttr (elm, attrName, value, ns) {
      if (! IS_SAFARI ||! ns) {
        return (elm.setAttribute
              ? elm.setAttribute ((ns? (ns + NS_SYMB): ”)
              + attrName, value): null );
      } else {
        return (elm.setAttributeNS
              ? elm.setAttributeNS (ns, attrName, value)
              : null );
      }
    }

    function remElmAttr (elm, attrName, ns) {
      if (! IS_SAFARI ||! ns) {
        return (elm.removeAttribute
              ? elm.removeAttribute ((ns? (ns + NS_SYMB): ”)
              + attrName): null );
      } else {
        return (elm.removeAttributeNS
              ? elm.removeAttributeNS (ns, attrName)
              : null );
      }
    } * This source code was highlighted with Source Code Highlighter .


    Logging


    14. The function below to help with logging is very simple, add an element to the right place in the document <div id="LOG_DIV"></div>, set it to the required height, and information will be dumped into it + its scrolling will be provided:
    function LOG (informerName, text) {
      var logElement = document .getElementById ( 'LOG_DIV' );
      if (logElement) {
        logElement.appendChild ( document .createTextNode (
                informerName + ':' + text));
        logElement.appendChild ( document .createElement ( 'br' ));
        logElement.scrollTop + = 50;
      }
    } * This source code was highlighted with Source Code Highlighter .


    15. In the wonderful Firebug plugin for the Firefox browser there is a wonderful console where you can log in with wide capabilities . However, if you debug code in parallel in other browsers, accessing it can cause errors. In order not to clear the code from the logs each time, you can use the following stub:
    var Console = Class.extend ({
      // the stub class to allow using console when browser have it,
      // if not - just pass all calls
      construct: function () {},
      log: function () {},
      info: function () {},
      warn: function () {},
      error: function () {}
    });

    if (! window.console) {
      console = new Console ();
    } * This source code was highlighted with Source Code Highlighter .


    UPD: Corrected links to Source Code Highlighter, thanks.