Various DOM-related wrappers (Code Worth Recommending Project)

Discussion in 'Javascript' started by David Mark, Dec 8, 2007.

  1. David Mark

    David Mark Guest

    After a some reworking to support the "*" parameter properly in IE5
    (my previous version failed to filter non-element nodes), here are my
    proposals for various low-level DOM-related functions and the feature
    tests that enable them to be created without issue. I welcome any
    comments.

    I just noticed that I left some of the function declarations as
    variable declarations. There wasn't any specific reason for this,
    they were just created differently in my framework.

    var allElements, commonElementsByTagName, createElement,
    documentElementsByTagName, element, elementElementsByTagName, doc,
    headElement, html, htmlElement, i, xmlParseMode;
    var filter, filterLegacy;
    var canAdjustStyle, isStyleCapable, styles;
    var reFeaturedMethod = new RegExp('^function|object$', 'i');
    var global = this;

    // Test for properties of host objects known not to be callable (e.g.
    document nodes, elements)

    var isRealObjectProperty = function(o, p) {
    return !!(typeof(o[p]) == 'object' && o[p]);
    };

    // Test for host object properties that are typically callable (e.g.
    all, getElementById),
    // which may be of type function, object (IE and possibly others) or
    unknown (IE ActiveX methods)

    var isFeaturedMethod = function(o, m) {
    var t = typeof(o[m]);
    return !!((reFeaturedMethod.test(t) && o[m]) || t == 'unknown');
    };

    // Filter wrapper, which is included at this stage as it is needed for
    the gEBTN workaround
    // (all returns non-element nodes.)
    // Standard filter wrapper is not useful for IE5.0 as it requires
    Function.prototype.call.

    if (Array.prototype.filter) {
    filter = function(a, fn, context) { return a.filter(fn, context); };
    }
    else {
    // Note that Array.prototype.reverse is not tested as it is from JS
    1.1
    if (Function.prototype.call) {
    filter = function(a, fn, context) {
    var l = a.length, r = [], c = 0;
    context = context || global;
    // Didn't want to use in operator and for in loop does not preserve
    order
    while (l--) {
    if (typeof(l) != 'undefined') {
    if (fn.call(context, a[l], a)) { r[c++] = a[l]; }
    }
    }
    return r.reverse();
    };
    }
    else {
    // No simulated filter possible, so add a fallback with a slightly
    different interface
    // Parameter fn can be a string (method name) or function
    // Context ignored unless fn is a string
    filterLegacy = function(a, fn, context) {
    var l = a.length, r = [], c = 0;
    context = context || global;
    fn = (typeof(fn) == 'string')?context[fn]:fn;
    while (l--) {
    if (typeof(l) != 'undefined') {
    if (fn(a[l], a)) { r[c++] = a[l]; }
    }
    }
    return r.reverse();
    };
    }
    }


    function elementFilter(o) {
    // IE5 thinks comments and docTypes are elements.
    // Second conjunction is for agents that don't support nodeType.
    return (o.nodeType == 1 && o.tagName != '!') || (!o.nodeType &&
    o.tagName);
    }

    // Used to convert array-like host objects to arrays
    // IIRC, Array.prototype.slice didn't work with node lists
    function toArray(o) {
    var a = [];
    var l = o.length;
    while (l--) { a[l] = o[l]; }
    return a;
    }

    if (isRealObjectProperty(this, 'document')) {
    doc = this.document;

    // gEBI wrapper
    element = (function() {
    function idCheck(el, id) {
    return (el && el.id == id)?el:null;
    }
    if (isFeaturedMethod(doc, 'getElementById')) {
    return function(id, docNode) { return idCheck((docNode ||
    doc).getElementById(id), id); };
    }
    if (isFeaturedMethod(doc, 'all')) {
    return function(id, docNode) { return idCheck((docNode ||
    doc).all[id], id); };
    }
    })();


    if (isFeaturedMethod(doc, 'all')) {
    // Internal in my framework, called by: commonElementsByTagName and
    htmlElement
    allElements = (function() {
    var fnFilter = filter || filterLegacy;
    return function(el, bFilter) {
    return (bFilter)?fnFilter(toArray(el.all), elementFilter):el.all;
    };
    })();
    }

    // Called by both gEBTN wrappers
    commonElementsByTagName = (function() {
    if (isFeaturedMethod(doc, 'all') && allElements) {
    return function (el, t) {
    return(t == '*' && el.all)?allElements(el,
    true):el.getElementsByTagName(t);
    };
    }
    return function (el, t) { return el.getElementsByTagName(t); };
    })();

    // Defined only if document nodes feature gEBTN or all.
    // Returns an array or array-like host object.
    documentElementsByTagName = (function() {
    if (isFeaturedMethod(doc, 'getElementsByTagName')) {
    return function(t, docNode) {
    return commonElementsByTagName(docNode || doc, t);
    };
    }
    if (isFeaturedMethod(doc, 'all') && isFeaturedMethod(doc.all,
    'tags')) {
    return function(t, docNode) {
    return (docNode || doc).all.tags(t);
    };
    }
    })();

    // Returns the HTML element by default or optionally the first
    element it finds.
    htmlElement = function(docNode, bAnyElement) {
    var html, all;
    docNode = docNode || doc;
    html = isRealObjectProperty(docNode, 'documentElement')?
    docNode.documentElement:((documentElementsByTagName)?
    documentElementsByTagName('html', docNode)[0]:null);
    if (!html && allElements) {
    all = allElements(docNode); // Don't bother to filter for this
    html = all[(all[0].tagName == '!')?1:0];
    if (html && !bAnyElement && html.tagName.toLowerCase() != 'html')
    { html = null; }
    }
    return html;
    };

    // Retrieve any element (what follows doesn't care which one)
    html = htmlElement(doc, true);

    // Note that the bodyElement function is not included yet as events
    module is needed first
    // That function (among others) is defined after the document is
    ready.
    if (documentElementsByTagName) {
    headElement = function(docNode) {
    return documentElementsByTagName('head', docNode || doc)[0] ||
    null;
    };
    }

    if (html) {
    // Defined only if element nodes feature gEBTN or all.
    // Returns an array or array-like host object.
    elementElementsByTagName = (function() {
    if (isFeaturedMethod(html, 'getElementsByTagName')) {
    return commonElementsByTagName;
    }
    if (isFeaturedMethod(html, 'all') && isFeaturedMethod(html.all,
    'tags')) {
    return function(el, t) { return el.all.tags(t); };
    }
    })();

    // MS has been known to implement elements as ActiveX objects
    // (e.g. anchors that link to news resources)
    isStyleCapable = isRealObjectProperty(html, 'style');

    // These flags dictate which style-related functions should be
    initialized
    if (isStyleCapable) {
    canAdjustStyle = {};
    styles = ['display', 'visibility', 'position'];
    i = 3;
    while (i--) {
    canAdjustStyle[styles] = typeof(html.style[styles]) ==
    'string';
    }
    }
    }

    // This is called in two places,
    // createElement (below) and elements (an XPath wrapper.)
    // Each adds one additional line to work with XHTML (regardless of
    MIME type.)
    xmlParseMode = function(docNode) {
    docNode = docNode || doc;
    if (typeof(docNode.contentType) == 'string') {
    return docNode.contentType.indexOf('xml') != -1;
    }
    else {
    return typeof(docNode.body) == 'undefined';
    }
    };

    // Included at this stage as it will be needed for feature testing
    shortly
    createElement = (function() {
    if (doc.createElement) {
    return (function() {
    if (xmlParseMode() && doc.createElementNS) {
    return function(tag, docNode) {
    return (docNode || doc).createElementNS('http://www.w3.org/1999/
    xhtml', 'html:' + tag);
    };
    }
    return function(tag, docNode) {
    return (docNode || doc).createElement(tag);
    };
    })();
    }
    })();
    }
     
    David Mark, Dec 8, 2007
    #1
    1. Advertising

  2. David Mark

    David Mark Guest

    On Dec 8, 8:37 am, David Mark <> wrote:

    Correction:

    filterLegacy = function(a, fn, context) {
    var l = a.length, r = [], c = 0;
    context = context || global;
    while (l--) {
    if (typeof(l) != 'undefined') {
    if (((typeof(fn) == 'string')?context[fn]:fn)(a[l], a)) { r[c++] =
    a[l]; }
    }
    }
    return r.reverse();
    };

    I'm thinking this fallback doesn't really need to support context. It
    is only used for one thing at the moment (the gEBTN "*" fix.)
     
    David Mark, Dec 8, 2007
    #2
    1. Advertising

  3. Re: gEBI wrapper (Code Worth Recommending Project)

    On Dec 8, 5:37 am, David Mark <> wrote:
    > After a some reworking to support the "*" parameter properly in IE5
    > (my previous version failed to filter non-element nodes), here are my
    > proposals for various low-level DOM-related functions and the feature
    > tests that enable them to be created without issue. I welcome any
    > comments.


    [nitpick mode on]

    Please use a 70 character line length to avoid wrapping. Usenet wraps
    at 72 so a 70 character line allows one reply without wrapping.

    A 2 space "tab stop" conserves line width.

    Neither of these are my personal preferences but are appropriate to
    code that will be posted to Usenet.

    <URL: http://cljs.michaux.ca/trac/wiki/DesignGuidelines>

    [nitpick mode off]

    [code quoted below reformatted and reduced to just that related to the
    gEBI wrapper]

    > var element,
    > doc,
    > i;
    > var reFeaturedMethod = new RegExp('^function|object$', 'i');
    > var global = this;
    >
    > // Test for host object properties that are typically callable
    > // (e.g. all, getElementById),
    > // which may be of type function, object (IE and possibly others)
    > // or unknown (IE ActiveX methods)
    >
    > var isFeaturedMethod = function(o, m) {
    > var t = typeof(o[m]);
    > return !!((reFeaturedMethod.test(t) && o[m]) || t == 'unknown');
    > };
    >
    >
    > if (isRealObjectProperty(this, 'document')) {
    > doc = this.document;
    >
    > // gEBI wrapper
    > element = (function() {
    >
    > function idCheck(el, id) {
    > return (el && el.id == id) ? el : null;
    > }
    >
    > if (isFeaturedMethod(doc, 'getElementById')) {
    > return function(id, docNode) {
    > return idCheck((docNode || doc).getElementById(id), id);
    > };
    > }
    >
    > if (isFeaturedMethod(doc, 'all')) {
    > return function(id, docNode) {
    > return idCheck((docNode || doc).all[id], id);
    > };
    > }
    >
    > })();
    >
    > }


    Based on the repository design guidelines, the above could be
    substantially reduced to just

    if (document.getElementById) {
    var getEBI = function(id, d) {
    var el = (d||document).getElementById(id);
    if (el.id == id) {
    return el;
    }
    };
    }

    Notes:

    There is no need to check for the existence of "document" as no
    browser, NN4+ and IE4+, missing "document".

    There is no need to check that document.getElementById is a callable
    since there has never been a known implementation where
    document.getElementById that is not callable.

    Since this is a getter the name must start with "get". I'm tempted to
    call it "getElementById" since that won't clash with
    document.getElementById. "getEBI" is shorter.

    It is clear that supporting IE4 requires quite a bit more code which
    must be downloaded by everyone. I don't mean this to start a big
    discussion about supporting IE4. It is just quite obvious in this
    example.

    --
    Peter
    Code Worth Recommending Project
    http://cljs.michaux.ca
     
    Peter Michaux, Dec 9, 2007
    #3
  4. David Mark

    AKS Guest

    On 8 ÄÅË, 23:46, David Mark <> wrote:
    > On Dec 8, 8:37 am, David Mark <> wrote:


    > var l = a.length
    > ...
    > if (typeof(l) != 'undefined')


    What this checking is necessary for? The index of an array can be type
    "undefined"?
     
    AKS, Dec 9, 2007
    #4
  5. On Dec 8, 5:37 am, David Mark <> wrote:

    [code quoted below reformatted and reduced to just that related to the
    gEBTN wrapper]

    > var allElements, commonElementsByTagName,
    > documentElementsByTagName, elementElementsByTagName,
    > doc, html, htmlElement, i;
    > var filter, filterLegacy;
    > var reFeaturedMethod = new RegExp('^function|object$', 'i');
    > var global = this;
    >
    > // Test for properties of host objects known not to be callable
    > // (e.g.document nodes, elements)
    >
    > var isRealObjectProperty = function(o, p) {
    > return !!(typeof(o[p]) == 'object' && o[p]);
    > };
    >
    >
    > // Test for host object properties that are typically callable
    > // (e.g. all, getElementById),
    > // which may be of type function, object (IE and possibly others)
    > // or unknown (IE ActiveX methods)
    >
    > var isFeaturedMethod = function(o, m) {
    > var t = typeof(o[m]);
    > return !!((reFeaturedMethod.test(t) && o[m]) || t == 'unknown');
    > };
    >
    >
    > // Filter wrapper, which is included at this stage as it is needed
    > // for the gEBTN workaround (all returns non-element nodes.)
    > // Standard filter wrapper is not useful for IE5.0 as it requires
    > // Function.prototype.call.
    >
    > if (Array.prototype.filter) {
    > filter = function(a, fn, context) {
    > return a.filter(fn, context);
    > };
    > }
    > else {
    > // Note that Array.prototype.reverse is not tested as it is
    > // from JS 1.1
    > if (Function.prototype.call) {
    > filter = function(a, fn, context) {
    > var l = a.length,
    > r = [],
    > c = 0;
    > context = context || global;
    > // Didn't want to use in operator and for in loop does
    > // not preserve order
    > while (l--) {
    > if (typeof(l) != 'undefined') {
    > if (fn.call(context, a[l], a)) {
    > r[c++] = a[l];
    > }
    > }
    > }
    > return r.reverse();
    > };
    > }
    > else {
    > // No simulated filter possible, so add a fallback
    > // with a slightly different interface
    > // Parameter fn can be a string (method name) or function
    > // Context ignored unless fn is a string
    > filterLegacy = function(a, fn, context) {
    > var l = a.length, r = [], c = 0;
    > context = context || global;
    > fn = (typeof(fn) == 'string')?context[fn]:fn;
    > while (l--) {
    > if (typeof(l) != 'undefined') {
    > if (fn(a[l], a)) { r[c++] = a[l]; }
    > }
    > }
    > return r.reverse();
    > };
    > }
    > }
    >
    > function elementFilter(o) {
    > // IE5 thinks comments and docTypes are elements.
    > // Second conjunction is for agents that don't support nodeType.
    > return (o.nodeType == 1 && o.tagName != '!') ||
    > (!o.nodeType && o.tagName);
    > }
    >
    > // Used to convert array-like host objects to arrays
    > // IIRC, Array.prototype.slice didn't work with node lists
    > function toArray(o) {
    > var a = [];
    > var l = o.length;
    > while (l--) { a[l] = o[l]; }
    > return a;
    > }
    >
    > if (isRealObjectProperty(this, 'document')) {
    > doc = this.document;
    >
    > if (isFeaturedMethod(doc, 'all')) {
    > // Internal in my framework, called by:
    > // commonElementsByTagName and htmlElement
    > allElements = (function() {
    > var fnFilter = filter || filterLegacy;
    > return function(el, bFilter) {
    > return (bFilter) ?
    > fnFilter(toArray(el.all), elementFilter) :
    > el.all;
    > };
    > })();
    > }
    >
    > // Called by both gEBTN wrappers
    > commonElementsByTagName = (function() {
    > if (isFeaturedMethod(doc, 'all') && allElements) {
    > return function(el, t) {
    > return (t == '*' && el.all) ?
    > allElements(el, true) :
    > el.getElementsByTagName(t);
    > };
    > }
    > return function(el, t) {
    > return el.getElementsByTagName(t);
    > };
    > })();
    >
    > // Defined only if document nodes feature gEBTN or all.
    > // Returns an array or array-like host object.
    > documentElementsByTagName = (function() {
    > if (isFeaturedMethod(doc, 'getElementsByTagName')) {
    > return function(t, docNode) {
    > return commonElementsByTagName(docNode || doc, t);
    > };
    > }
    > if (isFeaturedMethod(doc, 'all') &&
    > isFeaturedMethod(doc.all, 'tags')) {
    > return function(t, docNode) {
    > return (docNode || doc).all.tags(t);
    > };
    > }
    > })();
    >
    > // Returns the HTML element by default or optionally
    > // the first element it finds.
    > htmlElement = function(docNode, bAnyElement) {
    > var html, all;
    > docNode = docNode || doc;
    > html = isRealObjectProperty(docNode, 'documentElement') ?
    > docNode.documentElement:((documentElementsByTagName)?
    > documentElementsByTagName('html', docNode)[0] : null);
    > if (!html && allElements) {
    > all = allElements(docNode);//Don't bother to filter for this
    > html = all[(all[0].tagName == '!')?1:0];
    > if (html &&
    > !bAnyElement &&
    > html.tagName.toLowerCase() != 'html') {
    > html = null;
    > }
    > }
    > return html;
    > };
    >
    > // Retrieve any element (what follows doesn't care which one)
    > html = htmlElement(doc, true);
    >
    > if (html) {
    > // Defined only if element nodes feature gEBTN or all.
    > // Returns an array or array-like host object.
    > elementElementsByTagName = (function() {
    > if (isFeaturedMethod(html, 'getElementsByTagName')) {
    > return commonElementsByTagName;
    > }
    > if (isFeaturedMethod(html, 'all') &&
    > isFeaturedMethod(html.all, 'tags')) {
    > return function(el, t) { return el.all.tags(t); };
    > }
    > })();
    >
    > }
    >
    > }


    Whoa, that is a lot of code!

    I didn't make a time test but I think the simulation of
    Array.prototype.filter will be very slow. I made a CSS selector
    function one time (something like jQuery has) and I found that any
    code that will operate on any medium sized DOM needs to be very fast.
    I also made a test between just simply looping over an array verses
    using a function that takes another function and iterates over all of
    an array. A simple loop wins by a long shot.

    For the repository, the above code could be substantially reduced to
    something like the following which will run much faster.

    // -----------------------------------------

    // from another file in the respository
    if (document.documentElement) {
    var getAnElement = function(d) {
    return (d || document).documentElement;
    };
    }

    // -----------------------------------------

    // One implementation for developers not concerned with IE5
    // problem of the browser thinking doctype and comments
    // are elements.

    if (document.getElementsByTagName &&
    typeof getAnElement != 'undefined' &&
    getAnElement().getElementsByTagName) {

    var getEBTN = function(tag, root) {
    var els = root.getElementsByTagName(tag);
    if (tag == '*' && !els.length && root.all) {
    els = root.all;
    }
    return els;
    };

    }

    // -----------------------

    // Another implementation for developers concerned with
    // the IE5 problem about doctype and comments

    if (document.getElementsByTagName &&
    typeof getAnElement != 'undefined' &&
    getAnElement().getElementsByTagName) {

    var getEBTN = function(tag, root) {
    var els = root.getElementsByTagName(tag);

    if (tag == '*' && !els.length && root.all) {
    var all = root.all;
    els = [];
    for (var i=0, ilen=all.length; i<ilen; i++) {
    var el = all;
    // The following conditional could be factored out
    // if necessary.
    if ((el.nodeType == 1 && el.tagName != '!') ||
    (!el.nodeType && el.tagName)) {
    els[els.length] = el;
    }
    }
    }

    return els;
    };
    }

    I haven't actually verified the IE5 bug yet and tested the above in a
    wide set of browsers. I'm just looking at the packaging of the
    concepts.

    I think there is no need for two getEBTN wrappers because, I think,
    all of the known browsers that have document.getElementByTagName also
    have element.getElementByTagName. It seems overly paranoid to think
    that sometime in a future such a browser will be created. The reason
    for two feature tests is because document.getElementsByTagName is
    defined in the DOM2 standard as part of Document and the other
    getElementsByTagName is defined for Element so a test only for one
    could be construed as object inference.

    Even though the two getEBTN wrappers use different fallbacks for IE4
    it looks like they could be combined into one just like I've done here
    with no real performance penalty. If some browsers had document.all
    and not element.all then that would be a problem but has that ever
    been the case?

    The API should be considered here. A getEBTN wrapper function like the
    above could be called "getByCssSelector" and the calls to getEBTN
    would become a subset of the possible calls to "getByCssSelector".
    There is a standard in the works for something like
    "document.getByCssSelector". So eventually there would be a very fast
    host object that could be wrapped.

    I think that having getEBTN as it's own function is good and both
    getEBID and getEBTN could be wrapped in a function getByCssSelector.
    This is something I'd like to add to the repository eventually. I did
    a couple weeks of work on these types of functions a while ago and it
    is very handy for enlivening pages for unobtrusive JavaScript. (Cue
    "PointedEars" for a "lemmings" comment.)

    --
    Peter
    Code Worth Recommending Project
    http://cljs.michaux.ca
     
    Peter Michaux, Dec 9, 2007
    #5
  6. Re: gEBI wrapper (Code Worth Recommending Project)

    On Dec 8, 5:32 pm, Peter Michaux <> wrote:
    > On Dec 8, 5:37 am, David Mark <> wrote:


    [snip about gEBI wrappers for the repository]

    > if (document.getElementById) {
    > var getEBI = function(id, d) {
    > var el = (d||document).getElementById(id);
    > if (el.id == id) {
    > return el;
    > }
    > };
    >
    > }


    // the simplest possible implementation

    if (document.getElementById) {
    var getEBI = function(id, d) {
    return (d||document).getElementById(id);
    };
    }

    // -------------------------------------------

    // an implementation that *fixes* the IE name/id bug
    // I don't know what happens in the "hunt" if there
    // are frames, iframes, or object elements in the DOM

    var getEBI = (function() {

    var el;

    if (document.getElementById &&
    typeof getAnElement != 'undefined' &&
    ((el=getAnElement()).firstChild || el.firstChild === null) &&
    (el.nextSibling || el.nextSibling === null)) {

    return function(id, d) {
    var el = (d || document).getElementById(id);
    if (el && el.id == id) {
    return el;
    }

    function hunt(node, id) {
    if (node.id == id) {
    return node;
    }
    node = node.firstChild;

    while (node) {
    var el = hunt(node, id);
    if (el) {
    return el;
    }
    node = node.nextSibling;
    }
    }

    return hunt(d, id);
    }

    }
    })();

    --
    Peter
    Code Worth Recommending Project
    http://cljs.michaux.ca
     
    Peter Michaux, Dec 9, 2007
    #6
  7. David Mark

    Evertjan. Guest

    Re: gEBI wrapper (Code Worth Recommending Project)

    Peter Michaux wrote on 09 dec 2007 in comp.lang.javascript:

    > // the simplest possible implementation
    >
    > if (document.getElementById) {
    > var getEBI = function(id, d) {
    > return (d||document).getElementById(id);
    > };
    >}
    >


    What is the use of testing for getElementById,
    as the js code will error anyway
    when getEBI() is subsequently called while not defined?

    // the simplest possible implementation

    var getEBI = function(id, d) {
    return (d||document).getElementById(id);
    };


    --
    Evertjan.
    The Netherlands.
    (Please change the x'es to dots in my emailaddress)
     
    Evertjan., Dec 9, 2007
    #7
  8. Re: gEBI wrapper (Code Worth Recommending Project)

    Peter Michaux wrote:
    > Based on the repository design guidelines, the above could be
    > substantially reduced to just
    > [...]
    > Notes:
    >
    > There is no need to check for the existence of "document" as no
    > browser, NN4+ and IE4+, missing "document".
    >
    > There is no need to check that document.getElementById is a callable
    > since there has never been a known implementation where
    > document.getElementById that is not callable.


    If these are the guidelines for your project, to produce code that is not
    interoperable and inherently unreliable, I don't want to contribute.


    PointedEars
     
    Thomas 'PointedEars' Lahn, Dec 9, 2007
    #8
  9. David Mark

    VK Guest

    Re: gEBI wrapper (Code Worth Recommending Project)

    On Dec 9, 4:32 am, Peter Michaux <> wrote:
    > Based on the repository design guidelines, the above could be
    > substantially reduced to just
    >
    > if (document.getElementById) {
    > var getEBI = function(id, d) {
    > var el = (d||document).getElementById(id);
    > if (el.id == id) {
    > return el;
    > }
    > };
    >
    > }


    That is an absolutely terrible way to implement a _runtime_ method.
    DOM interface calls are the most used in any script and they have the
    greatest impact to the performance. JS <=> DOM calls already much
    slower then JS <=> JS calls and cutting their performance even further
    for any bit is a crime to be punished :) :-|

    Features/environment sniffing has to be done only _once_ on the
    instantiation stage and _never_ in a loop on each invocation. So if
    anyone still really cares of document.getElementById support - nobody
    does in the world except some people in this NG but let's play
    academics - then it must be function reference conditionally set once
    for the identifier:
    if (condition_1) {
    $ = function(id){/* do this */};
    }
    else if (condition_2) {
    $ = function(id){/* do that */};
    }
    else {
    $ = function(id){/* do something */};
    }
    so on the further run $(id) will call the appropriate direct method.
     
    VK, Dec 9, 2007
    #9
  10. David Mark

    David Mark Guest

    On Dec 9, 12:00 am, AKS <> wrote:
    > On 8 ÄÅË, 23:46, David Mark <> wrote:
    >
    > > On Dec 8, 8:37 am, David Mark <> wrote:
    > > var l = a.length
    > > ...
    > > if (typeof(l) != 'undefined')

    >
    > What this checking is necessary for? The index of an array can be type
    > "undefined"?


    Typo. Should have been checking a[l].
     
    David Mark, Dec 9, 2007
    #10
  11. David Mark

    David Mark Guest

    Re: gEBI wrapper (Code Worth Recommending Project)

    On Dec 8, 8:32 pm, Peter Michaux <> wrote:
    > On Dec 8, 5:37 am, David Mark <> wrote:
    >
    > > After a some reworking to support the "*" parameter properly in IE5
    > > (my previous version failed to filter non-element nodes), here are my
    > > proposals for various low-level DOM-related functions and the feature
    > > tests that enable them to be created without issue. I welcome any
    > > comments.

    >
    > [nitpick mode on]
    >
    > Please use a 70 character line length to avoid wrapping. Usenet wraps
    > at 72 so a 70 character line allows one reply without wrapping.
    >
    > A 2 space "tab stop" conserves line width.
    >
    > Neither of these are my personal preferences but are appropriate to
    > code that will be posted to Usenet.
    >
    > <URL:http://cljs.michaux.ca/trac/wiki/DesignGuidelines>
    >
    > [nitpick mode off]
    >
    > [code quoted below reformatted and reduced to just that related to the
    > gEBI wrapper]
    >
    >
    >
    >
    >
    > > var element,
    > > doc,
    > > i;
    > > var reFeaturedMethod = new RegExp('^function|object$', 'i');
    > > var global = this;

    >
    > > // Test for host object properties that are typically callable
    > > // (e.g. all, getElementById),
    > > // which may be of type function, object (IE and possibly others)
    > > // or unknown (IE ActiveX methods)

    >
    > > var isFeaturedMethod = function(o, m) {
    > > var t = typeof(o[m]);
    > > return !!((reFeaturedMethod.test(t) && o[m]) || t == 'unknown');
    > > };

    >
    > > if (isRealObjectProperty(this, 'document')) {
    > > doc = this.document;

    >
    > > // gEBI wrapper
    > > element = (function() {

    >
    > > function idCheck(el, id) {
    > > return (el && el.id == id) ? el : null;
    > > }

    >
    > > if (isFeaturedMethod(doc, 'getElementById')) {
    > > return function(id, docNode) {
    > > return idCheck((docNode || doc).getElementById(id), id);
    > > };
    > > }

    >
    > > if (isFeaturedMethod(doc, 'all')) {
    > > return function(id, docNode) {
    > > return idCheck((docNode || doc).all[id], id);
    > > };
    > > }

    >
    > > })();

    >
    > > }

    >
    > Based on the repository design guidelines, the above could be
    > substantially reduced to just
    >
    > if (document.getElementById) {
    > var getEBI = function(id, d) {
    > var el = (d||document).getElementById(id);
    > if (el.id == id) {
    > return el;
    > }
    > };
    >
    > }
    >
    > Notes:
    >
    > There is no need to check for the existence of "document" as no
    > browser, NN4+ and IE4+, missing "document".
    >
    > There is no need to check that document.getElementById is a callable
    > since there has never been a known implementation where
    > document.getElementById that is not callable.
    >
    > Since this is a getter the name must start with "get". I'm tempted to
    > call it "getElementById" since that won't clash with
    > document.getElementById. "getEBI" is shorter.
    >
    > It is clear that supporting IE4 requires quite a bit more code which
    > must be downloaded by everyone. I don't mean this to start a big
    > discussion about supporting IE4. It is just quite obvious in this
    > example.


    A lot of the extra code is for the IE5 problem with the "*" parameter
    (gEBTN.)
     
    David Mark, Dec 9, 2007
    #11
  12. Re: gEBI wrapper (Code Worth Recommending Project)

    VK wrote:
    > On Dec 9, 4:32 am, Peter Michaux <> wrote:
    >> Based on the repository design guidelines, the above could be
    >> substantially reduced to just
    >>
    >> if (document.getElementById) {
    >> var getEBI = function(id, d) {
    >> var el = (d||document).getElementById(id);
    >> if (el.id == id) {
    >> return el;
    >> }
    >> };
    >>
    >> }

    >
    > That is an absolutely terrible way to implement a _runtime_ method.
    > DOM interface calls are the most used in any script and they have the
    > greatest impact to the performance. JS <=> DOM calls already much
    > slower then JS <=> JS calls and cutting their performance even further
    > for any bit is a crime to be punished :) :-|


    I have to agree with VK here. Lacking necessary feature testing (instead
    of mere property true-value testing) aside, it is not necessary and it is
    inefficient to do the test every time the feature is used.

    That is why I refactored my dhtml.js recently to use factory functions to
    assign suitable function references to the wrapper object's properties.


    PointedEars
    --
    "Use any version of Microsoft Frontpage to create your site. (This won't
    prevent people from viewing your source, but no one will want to steal it.)"
    -- from <http://www.vortex-webdesign.com/help/hidesource.htm>
     
    Thomas 'PointedEars' Lahn, Dec 9, 2007
    #12
  13. David Mark

    David Mark Guest

    On Dec 9, 12:02 am, Peter Michaux <> wrote:
    [snip]
    >
    > Whoa, that is a lot of code!


    I thought it best to start with every possibility and whittle it down
    from there.

    >
    > I didn't make a time test but I think the simulation of
    > Array.prototype.filter will be very slow. I made a CSS selector


    Slow is relative. I don't know of any way to simulate filter any
    faster. That's why I never use the "*" selector for gEBTN. I
    included it only to illustrate what is required to work around that
    problem.

    > function one time (something like jQuery has) and I found that any


    I have found that a combination of gEBI and gEBTN is all I ever need
    to home in on whatever elements I need to manipulate.

    > code that will operate on any medium sized DOM needs to be very fast.


    As long as you don't use "*" with gEBTN, then these functions perform
    well.

    > I also made a test between just simply looping over an array verses
    > using a function that takes another function and iterates over all of
    > an array. A simple loop wins by a long shot.


    Without question. It will create longer functions that duplicate
    code, but that may be the way to go in this case.

    >
    > For the repository, the above code could be substantially reduced to
    > something like the following which will run much faster.
    >
    > // -----------------------------------------
    >
    > // from another file in the respository
    > if (document.documentElement) {
    > var getAnElement = function(d) {
    > return (d || document).documentElement;
    > };
    >
    > }
    >
    > // -----------------------------------------
    >
    > // One implementation for developers not concerned with IE5
    > // problem of the browser thinking doctype and comments
    > // are elements.
    >
    > if (document.getElementsByTagName &&
    > typeof getAnElement != 'undefined' &&
    > getAnElement().getElementsByTagName) {
    >
    > var getEBTN = function(tag, root) {
    > var els = root.getElementsByTagName(tag);
    > if (tag == '*' && !els.length && root.all) {
    > els = root.all;
    > }
    > return els;
    > };
    >
    > }


    That works for that case.

    >
    > // -----------------------
    >
    > // Another implementation for developers concerned with
    > // the IE5 problem about doctype and comments
    >
    > if (document.getElementsByTagName &&
    > typeof getAnElement != 'undefined' &&
    > getAnElement().getElementsByTagName) {
    >
    > var getEBTN = function(tag, root) {
    > var els = root.getElementsByTagName(tag);
    >
    > if (tag == '*' && !els.length && root.all) {
    > var all = root.all;
    > els = [];
    > for (var i=0, ilen=all.length; i<ilen; i++) {
    > var el = all;


    I prefer not to declare variables inside of loops. And for long
    loops, a while that counts down to 0 will be faster. Granted, you
    have to reverse the results at the end.

    > // The following conditional could be factored out
    > // if necessary.
    > if ((el.nodeType == 1 && el.tagName != '!') ||
    > (!el.nodeType && el.tagName)) {
    > els[els.length] = el;
    > }
    > }
    > }
    >
    > return els;
    > };
    >
    > }
    >
    > I haven't actually verified the IE5 bug yet and tested the above in a
    > wide set of browsers. I'm just looking at the packaging of the
    > concepts.


    It isn't actually a bug, but a limitation of IE5's implementation of
    gEBTN. It just doens't handle "*".

    >
    > I think there is no need for two getEBTN wrappers because, I think,
    > all of the known browsers that have document.getElementByTagName also
    > have element.getElementByTagName. It seems overly paranoid to think
    > that sometime in a future such a browser will be created. The reason


    I was actually more concerned with past browsers that may have
    incomplete implementations of gEBTN.

    > for two feature tests is because document.getElementsByTagName is
    > defined in the DOM2 standard as part of Document and the other
    > getElementsByTagName is defined for Element so a test only for one
    > could be construed as object inference.


    Right. That's why I avoided it.

    >
    > Even though the two getEBTN wrappers use different fallbacks for IE4
    > it looks like they could be combined into one just like I've done here
    > with no real performance penalty. If some browsers had document.all
    > and not element.all then that would be a problem but has that ever
    > been the case?


    That one isn't likely. It was the gEBTN inference that I wanted to
    avoid. The previous discussion about this stemmed from the fact that
    most libraries make similar inferences about addEventListener (which
    requires three implementations: window, document and element.) It was
    noted that agents do exist that support that method for elements, but
    not for window.

    >
    > The API should be considered here. A getEBTN wrapper function like the
    > above could be called "getByCssSelector" and the calls to getEBTN
    > would become a subset of the possible calls to "getByCssSelector".


    If you feel we must have a getByCssSelector, then that makes sense. I
    can't imagine a scenario where I would use such a thing.

    > There is a standard in the works for something like
    > "document.getByCssSelector". So eventually there would be a very fast
    > host object that could be wrapped.


    Right.

    >
    > I think that having getEBTN as it's own function is good and both
    > getEBID and getEBTN could be wrapped in a function getByCssSelector.


    That is similar to what I do, except they are both wrapped in one
    branch of an XPath wrapper (for browsers that don't support XPath.)

    > This is something I'd like to add to the repository eventually. I did
    > a couple weeks of work on these types of functions a while ago and it
    > is very handy for enlivening pages for unobtrusive JavaScript. (Cue
    > "PointedEars" for a "lemmings" comment.)


    I haven't found a need for such queries and I agree with "PointedEars"
    in that event delegation is often the best way to go anyway.
     
    David Mark, Dec 9, 2007
    #13
  14. David Mark

    David Mark Guest

    Re: gEBI wrapper (Code Worth Recommending Project)

    On Dec 9, 12:58 am, Peter Michaux <> wrote:
    > On Dec 8, 5:32 pm, Peter Michaux <> wrote:
    >
    > > On Dec 8, 5:37 am, David Mark <> wrote:

    >
    > [snip about gEBI wrappers for the repository]
    >
    > > if (document.getElementById) {
    > > var getEBI = function(id, d) {
    > > var el = (d||document).getElementById(id);
    > > if (el.id == id) {
    > > return el;
    > > }
    > > };

    >
    > > }

    >
    > // the simplest possible implementation
    >
    > if (document.getElementById) {
    > var getEBI = function(id, d) {
    > return (d||document).getElementById(id);
    > };
    >
    > }
    >
    > // -------------------------------------------
    >
    > // an implementation that *fixes* the IE name/id bug
    > // I don't know what happens in the "hunt" if there
    > // are frames, iframes, or object elements in the DOM


    Frames and IFrames won't enter into it.

    >
    > var getEBI = (function() {
    >
    > var el;
    >
    > if (document.getElementById &&
    > typeof getAnElement != 'undefined' &&
    > ((el=getAnElement()).firstChild || el.firstChild === null) &&
    > (el.nextSibling || el.nextSibling === null)) {
    >
    > return function(id, d) {
    > var el = (d || document).getElementById(id);
    > if (el && el.id == id) {
    > return el;
    > }
    >
    > function hunt(node, id) {
    > if (node.id == id) {
    > return node;
    > }
    > node = node.firstChild;
    >
    > while (node) {
    > var el = hunt(node, id);
    > if (el) {
    > return el;
    > }
    > node = node.nextSibling;
    > }
    > }
    >
    > return hunt(d, id);
    > }
    >
    > }
    >
    > })();


    I thought about adding this, but decided it was too much extra code to
    enable authors to write poor markup. It might be better to have the
    function return null in such a case as it will clue the author that
    something is wrong.
     
    David Mark, Dec 9, 2007
    #14
  15. David Mark

    David Mark Guest

    Re: gEBI wrapper (Code Worth Recommending Project)

    On Dec 9, 4:14 am, "Evertjan." <> wrote:
    > Peter Michaux wrote on 09 dec 2007 in comp.lang.javascript:
    >
    > > // the simplest possible implementation

    >
    > > if (document.getElementById) {
    > > var getEBI = function(id, d) {
    > > return (d||document).getElementById(id);
    > > };
    > >}

    >
    > What is the use of testing for getElementById,
    > as the js code will error anyway
    > when getEBI() is subsequently called while not defined?


    The idea is that applications can more simply feature test API
    abstractions than the individual features abstracted. Granted, this
    particular example is too simple to illustrate this concept.

    >
    > // the simplest possible implementation
    >
    > var getEBI = function(id, d) {
    > return (d||document).getElementById(id);
    >
    > };


    In this particular case, that would be just as good (it isn't any more
    complex to test getElementById.) But as soon as additional features
    are abstracted by getEBI (e.g. document.all), it becomes simpler to
    test the wrapper.
     
    David Mark, Dec 9, 2007
    #15
  16. David Mark

    David Mark Guest

    Re: gEBI wrapper (Code Worth Recommending Project)

    On Dec 9, 4:39 am, Thomas 'PointedEars' Lahn <>
    wrote:
    > Peter Michaux wrote:
    > > Based on the repository design guidelines, the above could be
    > > substantially reduced to just
    > > [...]
    > > Notes:

    >
    > > There is no need to check for the existence of "document" as no
    > > browser, NN4+ and IE4+, missing "document".

    >
    > > There is no need to check that document.getElementById is a callable
    > > since there has never been a known implementation where
    > > document.getElementById that is not callable.

    >
    > If these are the guidelines for your project, to produce code that is not
    > interoperable and inherently unreliable, I don't want to contribute.


    I somewhat agree with that sentiment, which is why I presented both
    tests, though not contributing at all would seem an extreme position.
     
    David Mark, Dec 9, 2007
    #16
  17. David Mark

    David Mark Guest

    Re: gEBI wrapper (Code Worth Recommending Project)

    On Dec 9, 4:51 am, VK <> wrote:
    > On Dec 9, 4:32 am, Peter Michaux <> wrote:
    >
    > > Based on the repository design guidelines, the above could be
    > > substantially reduced to just

    >
    > > if (document.getElementById) {
    > > var getEBI = function(id, d) {
    > > var el = (d||document).getElementById(id);
    > > if (el.id == id) {
    > > return el;
    > > }
    > > };

    >
    > > }

    >
    > That is an absolutely terrible way to implement a _runtime_ method.
    > DOM interface calls are the most used in any script and they have the
    > greatest impact to the performance. JS <=> DOM calls already much
    > slower then JS <=> JS calls and cutting their performance even further
    > for any bit is a crime to be punished :) :-|
    >
    > Features/environment sniffing has to be done only _once_ on the
    > instantiation stage and _never_ in a loop on each invocation.


    Read it again. It is done only _once_.
     
    David Mark, Dec 9, 2007
    #17
  18. David Mark

    David Mark Guest

    On Dec 9, 12:02 am, Peter Michaux <> wrote:

    [snip]

    >
    > I didn't make a time test but I think the simulation of
    > Array.prototype.filter will be very slow. I made a CSS selector


    Considering that the use of the filter wrapper only helps IE5 and
    under, it does make sense to duplicate its functionality in the gEBTN
    wrapper. But that function and other JS1.6 array method wrappers are
    useful for other purposes where speed is less of an issue. I think
    that we should deal with those shortly.

    Looking at the rest of my "base" module, I think these topics should
    be considered next (in no particular order.)

    These extend what is currently being discussed:
    - Get child elements
    - Get first and last child element (with optionally specified tagName)
    - Get HTML and head elements

    These are needed to complete basic feature testing support:
    - Style support detection
    - createElement and XML parse mode detection
    - Element parent and owner document
    - Element "canHaveChildren" test

    Array and object testing, traversal and mutation:
    - isArray
    - push/pop
    - 1.6 Array method wrappers (filter, map, some, every, forEach, etc.)
    - For in filter.

    Events (needed to support more advanced feature testing):
    - addEventListener wrapper
    - DOMContentLoaded simulation

    Miscellaneous:
    - Get and set element text
    - Cookies (with optional support for ASP's keyed values.)
    - Plug-in detection

    I realize that the task of finalizing, adding and documenting each is
    a bottleneck, but perhaps we should start discussing some of these in
    parallel. Everybody has their own areas of interest, so this may
    increase overall participation in the project.
     
    David Mark, Dec 9, 2007
    #18
  19. Re: gEBI wrapper (Code Worth Recommending Project)

    On Dec 9, 1:14 am, "Evertjan." <> wrote:
    > Peter Michaux wrote on 09 dec 2007 in comp.lang.javascript:
    >
    > > // the simplest possible implementation

    >
    > > if (document.getElementById) {
    > > var getEBI = function(id, d) {
    > > return (d||document).getElementById(id);
    > > };
    > >}

    >
    > What is the use of testing for getElementById,
    > as the js code will error anyway
    > when getEBI() is subsequently called while not defined?


    The idea is to avoid errors.

    A function that calls getEBI should only test for the existance of
    gEBI and not document.getElementById. This is because the
    implementation of getEBI may be changed and may not use
    document.getElementById.

    --
    Peter
    Code Worth Recommending Project
    http://cljs.michaux.ca
     
    Peter Michaux, Dec 9, 2007
    #19
  20. Re: gEBI wrapper (Code Worth Recommending Project)

    On Dec 9, 1:39 am, Thomas 'PointedEars' Lahn <>
    wrote:
    > Peter Michaux wrote:
    > > Based on the repository design guidelines, the above could be
    > > substantially reduced to just
    > > [...]
    > > Notes:

    >
    > > There is no need to check for the existence of "document" as no
    > > browser, NN4+ and IE4+, missing "document".

    >
    > > There is no need to check that document.getElementById is a callable
    > > since there has never been a known implementation where
    > > document.getElementById that is not callable.

    >
    > If these are the guidelines for your project, to produce code that is not
    > interoperable and inherently unreliable,


    Do you feature test everything?

    Do you feature test that alert is callable?

    Do you feature test that string concatenation works?

    Do you feature test that float division works (e.g. 1/2 is 0.5)?

    Do you feature test for the bugs in the implementation of feature "x"
    in browser "y" that you have never seen? You could almost do this by
    an insanely laborious process of checking things like float division
    in 100 different combinations before doing float division.

    Hopefully you answer "no" to at least one of those question. If so
    then you see that the line has to be drawn somewhere.

    --
    Peter
    Code Worth Recommending Project
    http://cljs.michaux.ca
     
    Peter Michaux, Dec 9, 2007
    #20
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Peter Michaux
    Replies:
    45
    Views:
    400
    Peter Michaux
    Dec 7, 2007
  2. Peter Michaux
    Replies:
    16
    Views:
    257
    Peter Michaux
    Nov 26, 2007
  3. David Mark

    isFunction (Code Worth Recommending Project)

    David Mark, Dec 8, 2007, in forum: Javascript
    Replies:
    25
    Views:
    262
    Peter Michaux
    Dec 12, 2007
  4. Peter Michaux
    Replies:
    0
    Views:
    162
    Peter Michaux
    Dec 13, 2007
  5. Peter Michaux
    Replies:
    24
    Views:
    325
Loading...

Share This Page