A simple CSS query selector

Discussion in 'Javascript' started by nick, Aug 15, 2011.

  1. nick

    nick Guest

    Hope this gets through, I know some of you guys ignore google groups
    posts due to all the spam.

    Anyway, I know everyone and their mother has done a query selector
    thing, but here's my take on it. I needed something simple and
    lightweight for in-house use. I'd like to get some feedback from the
    gurus here.

    Mostly, I'm wondering if I've made any glaring mistakes that will
    prevent this from working in certain browsers/situations. Advice on
    coding style and optimization hints are also welcome.

    The "select" function takes a CSS selector string. Currently selecting
    tag names, class names and IDs is suported. These should work in any
    combination supported by CSS. A selector like this should work: "body
    #nav a.offsite, #footer .inner a"

    ///

    var DM = DM || {};

    (function(){

    DM.contains = function(obj, value) {
    for (var i=-1, len=obj.length; i<len; i++) {
    if (obj===value) return true;
    }
    return false;
    };

    DM.unique = function(obj) {
    for (var i=0, len=obj.length, r=[]; i<len; i++) {
    if (!(DM.contains(r, obj))) r.push(obj);
    }
    return r;
    };

    DM.append = function(a, b) {
    for (var i=0, len=b.length; i<len; i++) a.push(b);
    return a;
    },

    DM.select = function(selector) {
    var groups=selector.split(','), i=-1, group,
    found=[], nodes=[document];
    while (group=groups[++i]) {
    var path=group.split(' '), j=-1, part;
    while (part=path[++j]) {
    nodes=selectPart(nodes, part)
    }
    found=DM.unique((DM.append(found, nodes)));
    }
    return found;
    };

    function selectPart(nodes, selector) {
    var i=-1, symbol, attr, val, buffer='', tokens=[];
    while (symbol=selector.charAt(++i)) {
    switch (symbol) {
    case '#':
    if (i) tokens.push(buffer);
    buffer='';
    tokens.push('id');
    break;
    case '.':
    if (i) tokens.push(buffer);
    buffer='';
    tokens.push('class');
    break;
    default :
    if (!i) tokens.push('tagName');
    buffer+=symbol;
    break;
    }
    }
    tokens.push(buffer);

    i=-1;
    while ((attr=tokens[++i]) && (val=tokens[++i])) {
    nodes = byAttr(nodes, attr, val, i-1);
    }

    return nodes;
    }

    function getDescendants(nodes, includeSiblings) {
    var node, results=[];
    for (var i=0, len=nodes.length; i<len; i++) {
    node=nodes;
    DM.append(results, node.getElementsByTagName('*'));
    if (includeSiblings) results.push(node);
    }
    return results;
    }

    function byAttr(nodes, attribute, value, includeSiblings) {
    var results=[], desc=getDescendants(nodes, includeSiblings);

    value=value.toLowerCase();

    for (var i=0, len=desc.length; i<len; i++) {
    if (((''+desc[attribute]).toLowerCase()==value) ||
    ((''+desc[attribute+'Name']).toLowerCase()==value)) {
    results.push(desc);
    }
    }
    return results;
    }

    })();

    ///

    P.S. - our company's initials are DM, so I'm "namespacing" things into
    an object named DM.
     
    nick, Aug 15, 2011
    #1
    1. Advertisements

  2. nick

    nick Guest

    Oops, that was supposed to be a semicolon, not a comma.
     
    nick, Aug 15, 2011
    #2
    1. Advertisements

  3. nick

    nick Guest

    ^
    Should have been zero. Just caught this.
     
    nick, Aug 16, 2011
    #3
  4. nick

    nick Guest

    Comma grouping didn't work. It does now.

    var DM = DM || {};

    (function(){

    // array stuff

    DM.contains = function(obj, value) {
    for (var i=0, len=obj.length; i<len; i++) {
    if (obj===value) return true;
    }
    return false;
    };

    DM.unique = function(obj) {
    for (var i=0, len=obj.length, r=[]; i<len; i++) {
    if (!(DM.contains(r, obj))) r.push(obj);
    }
    return r;
    };

    DM.append = function(a, b) {
    for (var i=0, len=b.length; i<len; i++) a.push(b);
    return a;
    };

    // string stuff

    DM.trim = function(str) {
    return (''+str).replace(/^\s\s*/, '').replace(/\s\s*$/, '');
    };

    // node query

    DM.select = function(selector) {
    var groups=selector.split(','), i=-1, group, found=[], nodes;
    while (group=groups[++i]) {
    nodes=[document];
    group=DM.trim(group);
    var path=group.split(' '), j=-1, part;
    console.log(path);
    while (part=path[++j]) {
    console.log(part);
    nodes=selectPart(nodes, part)
    console.log(nodes);
    }
    DM.append(found, nodes);
    }
    found=DM.unique(found);
    return found;
    };

    function selectPart(nodes, selector) {
    var i=-1, symbol, attr, val, buffer='', tokens=[];
    while (symbol=selector.charAt(++i)) {
    switch (symbol) {
    case '#':
    if (i) tokens.push(buffer);
    buffer='';
    tokens.push('id');
    break;
    case '.':
    if (i) tokens.push(buffer);
    buffer='';
    tokens.push('class');
    break;
    default :
    if (!i) tokens.push('tagName');
    buffer+=symbol;
    break;
    }
    }
    tokens.push(buffer);

    i=-1;
    while ((attr=tokens[++i]) && (val=tokens[++i])) {
    nodes = byAttr(nodes, attr, val, i-1);
    }

    return nodes;
    }

    function getDescendants(nodes, includeSiblings) {
    var node, results=[];
    for (var i=0, len=nodes.length; i<len; i++) {
    node=nodes;
    DM.append(results, node.getElementsByTagName('*'));
    if (includeSiblings) results.push(node);
    }
    return results;
    }

    function byAttr(nodes, attribute, value, includeSiblings) {
    var results=[], desc=getDescendants(nodes, includeSiblings);

    value=value.toLowerCase();

    for (var i=0, len=desc.length; i<len; i++) {
    if (((''+desc[attribute]).toLowerCase()==value) ||
    ((''+desc[attribute+'Name']).toLowerCase()==value)) {
    results.push(desc);
    }
    }
    return results;
    }

    })();
     
    nick, Aug 16, 2011
    #4
  5. 16.8.2011 4:59, nick wrote :
    Maybe you should have checked and tested your code before posting it,
    and preferably you should post a URL instead of a bulk of code. All too
    often, posted code gets munged one way or another - by the poster, by a
    newsreader, by Google Groups, or something else - and people find all
    too late that they have been discussing code different from the real code.

    Perhaps most importantly, what are you trying to achieve? Reinventing
    jQuery?
     
    Jukka K. Korpela, Aug 16, 2011
    #5
  6. nick

    nick Guest

    Probably jumped the gun a little. I kind of half-tested it; I finished
    writing it just before posting.
    It's less than 100 lines, didn't think it was too long to post here.
    Took care to shorten lines to around 70 characters. I thought that
    would be better received than posting a link to pastebin or similar.
    No offense but I'm sure someone would have complained either way ;)
    I'd think this would be just as much of a problem when posting a link
    to content that may change. At least this way there's a record of
    changes.

    Anyway, I don't expect it to change much more, just needed to work out
    a few last minute bugs.
    Not at all. All I really needed was a cross-browser
    getElementsByClassName, and it sort of grew into a mini query
    selector. I'm just adding on to an in-house utility class we use for
    other generic stuff. We don't use jQuery or other GP libraries for
    various reasons.

    Anyway, I'm more concerned with the content of the script than the
    mode of delivery or my particular intended use. It's just a query
    selector, not anything close to a full-blown GP library. If anyone
    has helpful comments regarding the soundness of this code as a query
    selector function, I'd like to hear it. Things like "this won't work
    on browser X because Y" or "doing it like this would be faster on most
    browsers" or "you have a memory leak here" are more what I'm looking
    for.
     
    nick, Aug 16, 2011
    #6
  7. This implies an interesting definition for "half-testing": something you
    do in infinitesimally small time between writing and posting. :)
    The proper way is to post a URL to a specific version and not change it
    during the discussion, and (if needed) another URL for the working version.
    It sounds like reinventing jQuery. It might be a good idea to rewrite
    parts of jQuery, but hardly this way - Usenet groups aren't really a
    good forum for collaborative software development, especially now that
    people can do such things on web-based forums with loads of nice tools.
    Well, you didn't make it particularly easy to others to test your code.
    A URL of a test page would let others immediately test it on their browsers.

    For one thing, your code doesn't handle class attributes containing
    multiple values, a very common case. DM.select('.bar') only finds
    elements with class="bar", not e.g. elements with class="foo bar".
     
    Jukka K. Korpela, Aug 16, 2011
    #7
  8. nick

    John Nagle Guest

    I want to get the position of a DIV element from inside a Greasemonkey
    script. (This is done to detect some strange situations involving
    off-page content.) So I write:

    Code:
    var computedstyle = document.defaultView.getComputedStyle(elt,
    null); // get style
    var zindex = computedstyle.getPropertyValue("z-index");
    var left = computedstyle.getPropertyValue("left");
    var top = computedstyle.getPropertyValue("top");
    ...


    This works, but I get back values of "auto" for "left" and "top".

    Consulting the Mozilla documentation at
    "https://developer.mozilla.org/en/DOM/window.getComputedStyle":

    "The returned object actually represents the CSS 2.1 used values, not
    the computed values. Originally, CSS 2.0 defined the computed values to
    be the "ready to be used" values of properties after cascading and
    inheritance, but CSS 2.1 redefined computed values as pre-layout, and
    used values as post-layout. The getComputedStyle function returns the
    old meaning of computed values, now called used values. There is no DOM
    API to get CSS 2.1 computed values."

    So I should get "used values", described at

    "https://developer.mozilla.org/en/CSS/used_value"

    "The used value of any CSS property is the final value of that property
    after all calculations have been performed. Used values can be retrieved
    by calling window.getComputedStyle. Dimensions (e.g. width, line-height)
    are all in pixels, shorthand properties (e.g. background) are consistent
    with their component properties (e.g. background-color), display is
    consistent with position and float, and every CSS property has a value."

    So, according to the documentation, I should get a number, not "auto",
    for "top", "left", "right", and "bottom". In practice, they're usually
    returning "auto".

    Code bug? Documentation bug? Wrong version of documentation?

    (Firefox 5.0.1, Windows 7, 64 bit).

    John Nagle
     
    John Nagle, Aug 16, 2011
    #8
  9. nick

    nick Guest

    That's actually a pretty accurate summation. ;)

    I didn't do any testing between writing and posting, but I tested
    things as I was writing.

    If "not tested" throws exceptions, crashes your browser, and gets
    stuck in infinite loops, and "fully tested" has unit tests and works
    flawlessly, then maybe "half-tested" is somewhere in between... the
    code runs without error in the most common use cases, but probably
    doesn't work as expected in all cases.
    Sounds reasonable enough, I'll do that next time.
    This seems a bit small of a snippet of code for github or google code,
    no? Or is that not what you had in mind?
    Guilty as charged.
    Ah, thank you very much! A glaring omission on my part. And, I just
    remembered that jsbin exists.

    http://jsbin.com/oqefaw/edit#javascript,html - This copy supports
    class attributes with multiple values.

    Get your own topic. =P
     
    nick, Aug 16, 2011
    #9
  10. It would be helpful if you told the URL of the Web site that you want to
    post-process with Greasemonkey, or posted the relevant parts of its source
    code if it was not publicly available.
    You should get a *pixel length* ("…px") – which is a _String_ value, not a
    Number value – if, and only if, you (or the author of that Web site) have
    set a length for that property, as its initial value is "auto", indeed:

    <http://www.w3.org/TR/CSS21/visuren.html#propdef-left>

    Have you/they?
    Uncertain. You need to be more precise about the stylesheet(s) and the
    element (object) you want to retrieve style information about.

    That said, for an element that does not have a `left' property declaration
    applying to it, the value ("auto") in Iceweasel 5.0 (Firefox 5.0 for Debian
    GNU/Linux) is the same as in Chromium 13.0.x and Opera 11.50, which along
    with the definition of the initial value in the Specification indicates that
    it is not a code bug.

    You should keep in mind that MDN is a wiki, and anyone can write any
    nonsense there, although it would hopefully not take long if someone
    corrected/reverted it. (I am not saying that what is written there on this
    subject is nonsense.)

    There is also a fair chance that you misunderstood the getComputedStyle()
    method, and wanted to retrieve the values of the proprietary, but – within
    the boundaries of different box models – well-supported `offsetLeft' and
    `offsetTop' DOM properties instead (which do yield Number values
    representing pixel lengths).


    PointedEars
     
    Thomas 'PointedEars' Lahn, Aug 16, 2011
    #10
  11. nick

    John Nagle Guest

    Thanks. That makes sense.

    I'm looking at Google search result pages, which have,
    as parts of some search results, a DIV with negative X values.

    To see this, use a DOM inspector on a Google search results
    page. Go down to an id of "search" to find the search results.
    The results are in an OL, and each LI item is one search result.
    Continue looking inward until you find an A tag with an "href"
    to a non-Google site. Now you're inside a search result DIV.

    Now, while in Mozilla's DOM Inspector, look at
    View->Object Viewer->Box Model. The A tag, according to
    Mozilla, has position values of x=0, y=-2. Looking outward
    in the DOM tree, the position values are bogus until the
    DIV (class "vsc") just below the LI, which has reasonable
    x and y values.

    It makes sense (sort of) that "inline" items might not
    have valid x and y positions in the box model. But there's
    a DIV with class "vspi", which shows a computed style of
    display="block", a "left" value of "-8px", and a box
    position of (-8,-5). Clicking on that item in DOM Inspector
    produces a red box bottom on the displayed page, rather than
    a full red outline box, so even DOM Inspector is confused.

    My goal here is to be able to find the next valid block
    item outward from an A tag, so I can enhance or gray it out
    depending on certain site legitimacy criteria. (Remember
    that I'm talking about a Greasemoney or Mozilla plug in,
    not an ordinary web page.) I want to
    do this without depending on details like ids or class names,
    so it doesn't have to be modified for each minor page format
    change.

    I don't need the absolute location of anything; I just need
    to find the first DIV that has a valid box model.
    That's covered above.
    That's useful to know.
    MDN does seem to say that a number should be expected for positions.
    That MDN page says this has changed from version to version, and
    may be wrong or not current.
    John Nagle
     
    John Nagle, Aug 16, 2011
    #11
  12. nick

    nick Guest

    Changing topic back.
     
    nick, Aug 16, 2011
    #12
  13. The CSS properties "left" and "top" have been defined so that their
    computed value may be "auto", and in browsers getComputedStyle really
    returns that value e.g. if you just test with any element without any
    CSS settings.

    This might be seen as conflicting with the DOM 2 specification, item
    http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-OverrideAndComputed'
    which refers to a definition of "computed value" at
    http://www.w3.org/TR/1998/REC-CSS2-19980512/cascade.html#computed-value
    which describes "auto" as a relative value that must be resolved. But
    the reference is outdated, as the current CSS specification, CSS 2.1,
    describes computed values differently:
    http://www.w3.org/TR/CSS21/cascade.html#value-stages

    It now by definition depends on the description of an individual CSS
    property what its computed value really means.
     
    Jukka K. Korpela, Aug 16, 2011
    #13
  14. nick

    Bart Lateur Guest

    That's not necessarily a bad thing.
     
    Bart Lateur, Aug 17, 2011
    #14
  15. It would have been easier if you had me select the button left to the URI in
    the DOM Inspector window (or Edit → Select Element By Click), and then click
    the first link in a search hit.
    You are mistaken. For some reason, that empty div.vspi is positioned
    absolute (`position: absolute') below the other elements on the z-axis
    (`z-index: -1'). Its parent, a div.vsc, is positioned relative (`position:
    relative') accordingly. As a result, div.vspi is positioned with regard to
    the box of div.vsc, while the positioning of the other, not explicitly
    positioned elements (i.e. `position: static') follow the normal document
    flow.
    So for each `a' element in the `ol' element, that meets your criteria,
    traverse the tree up to the div.vsc or a `li' element, and set e.g. its
    `opacity' style property to, e.g. "0.5". I do not see the problem. In
    fact, formatting might be possible with a simple user stylesheet and no
    scripting at all (just not on the `li' element, as CSS has no notion of
    child-based formatting).
    Did you know that there is a WOT (Web of Trust) extension for Firefox?

    Traversing up to the `li' element remains.
    Your notion that a box model could be invalid, and that would be invalid
    here, is bogus. And the box model has little to do with the computed style
    or used style. In fact, the DOM Inspector has another dropdown entry for
    "Computed Style", which probably refers to the CSS 2.0 definition (as the
    DOM Inspector existed before CSS 2.1, too.)
    Learn to think clearly.
    Learn to distinguish – what is, and what seems to be.
    -- Surak

    The word "number" does not occur in either MDN article to begin with. The
    article about the used value is referring to dimension properties, like
    `width' and `line-height', which CSS 2.0 computed style is given "in
    pixels", meaning a pixel length. A pixel length is a number followed by the
    unit of length, "px". In implementations that do not have a pixel length
    data type, such as ECMAScript implementations, this value is represented by
    a String value, not a Number value.

    `left' is no dimension property, and its initial value is `auto'. You would
    only get a pixel length for its CSS 2.0 computed value if there was a
    stylesheet applying to it to declare a property value different from `auto'.
    Only the meaning of "computed style" has changed from one CSS version to the
    next. What was called "computed style" in CSS 2.0 is called "used style" in
    CSS 2.1. The DOM Level 2 CSS API was created and implemented before CSS 2.1
    came to be, so the getComputedStyle() method returns the computed style as
    it was understood in CSS 2.0, which is the "used style" in CSS 2.1.
    Modifying the API to accomodate the change in CSS definitions would have
    broken existing programs, so it was not done. (As a side effect, there is
    no way to retrieve the CSS 2.1 computed style.)

    BTW: While the OP's reaction shows that he has not yet read the Usenet
    primer, you should create a new thread for an entirely new problem/question
    next time. And please trim your quotes to the relevant minimum, which
    includes not quoting signatures of any kind (unless you refer to them).


    HTH

    PointedEars
     
    Thomas 'PointedEars' Lahn, Aug 17, 2011
    #15
  16. nick

    John Nagle Guest

    Right. I hadn't noticed that the item was positioned absolute.
    The DIV seems to be redundant. Probably some artifact of the generator
    for Google search results pages.

    John Nagle
     
    John Nagle, Aug 17, 2011
    #16
  17. JFTR: There was no flame, intended or otherwise.


    PointedEars
     
    Thomas 'PointedEars' Lahn, Aug 17, 2011
    #17
  18. LOL
     
    Hans-Georg Michna, Aug 21, 2011
    #18
    1. Advertisements

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.