简体   繁体   中英

Custom jQuery Selectors returning a match for all elements

I'm, trying to knock together a custom jquery selector for matching asp client ids in in Asp.net. I'm working on info from these two blog entries here and here

The problem is I am returning a match on every element on the page now. I kinda know why (it's cos I'm querying all nodes each time I think) but my brain is absolutely frazzled and I've coded myself into submission.

If anyone could figure out what's gone wrong and give me some pointers I'd be very appreciative.

Usage for the selector would be the following:

$("input:clientID(TextBox1)")

Cheers!

EDIT: I've got this code so far but this will return all nodes ending with the given id.

 (function ($) {
    $.expr[":"].clientID = function (objNode, intStackIndex, arrProperties, arrNodeStack) {

        var keyValue, tName, $node, len, id;

        if (arrProperties[3]) {

            // Split into array (name,value):
            keyValue = arrProperties[3];

            // Check to see whether a tag node has been supplied.
            if (objNode) {

                // Get the name.
                tName = objNode.tagName;
                $node = $(objNode);
                id = $node.attr("id");

                if ((id.lastIndexOf(keyValue) === id.length - keyValue.length) && ($node.is(":first"))) {
                    return true;
                }
                else if (id === keyValue) {
                    return true;
                }
                else {

                    return false;
                }

            } else {

                // No node supplied so will use the ends with attribute selector.
                return $("[id$=\"_" + keyValue + "\"]").first().length > 0;
            }

        } else {

            // If we have made it this far, then we found no
            // match. Return false to indicate that this node
            // did not match our selector.
            return false;
        }
    };

} (jQuery));

I think you're misunderstanding how jQuery selectors are supposed to work. Here is how they work. Assuming that you query with the following:

$("input:clientID(TextBox1)");

jQuery first finds all input elements, then, for each one asks your function if it should be included or not. So, if you page has 10 input elements, then your function will be called 10 times.

You do not have to query the DOM yourself .

With this understanding, it becomes much easier for you to decide whether a node's id contains _TextBox1 or not.

(function ($) {
    $.expr[":"].clientID = function(objNode, intStackIndex, arrProperties, arrNodeStack) {
        // we need to something to query
        if( ! arrProperties[3] ) return false; 
        return objNode.id.indexOf( "_" + arrProperties[3] ) !== -1;
    };
} (jQuery));

I'll leave the specifics of ASP.net to you, since I'm not familiar with how ASP.net clientID's are generated. However, in my understanding, you would also simply be able to achieve identical functionality with built in jQuery selectors (as others have mentioned).

Edit - Finding ASP.net formatted id's

To select ids which may be either "xxx_xxx_clientID" or "clientID" the code can be modified to:

var testID = arrProperties[3];
if( objNode.id == testID ) return true;

// ends with code
testID = "_" + testID;
var lastIndex = objNode.id.lastIndexOf(testID);
return lastIndex !== -1 && lastIndex == (objNode.id.length - testID.length);

Edit 2 - finding only the first matched element

In order to only match the first result, you can set a flag variable when you've found something. This should be much faster, but less clean than using James's solution.

(function ($) {
    // assign the clientID filter with a self-executing anonymous function
    $.expr[":"].clientID = (function(){
        // create a "private" flag variable
        var alreadyFound = false;

        return function(objNode, intStackIndex, arrProperties, arrNodeStack) {
            if( intStackIndex === 0 ) alreadyFound = false;
            if( alreadyFound ) return false;

            var testID = arrProperties[3];
            if( objNode.id == testID){
                alreadyFound = true;
            } else {
                // ends with code
                testID = "_" + testID;
                var lastIndex = objNode.id.lastIndexOf(testID);
                alreadyFound = lastIndex !== -1 && lastIndex == (objNode.id.length - testID.length);
            }

            return alreadyFound;
        };

    })();
} (jQuery));

The alreadyFound variable is accessible to our clientID function (and no one else) via a closure.

A little out of the box, but how about changing the ClientIDMode to Static and not worrying about it?

<asp:Label ID="Label1" runat="server" ClientIDMode="[Mode Type]" />

http://weblogs.asp.net/asptest/archive/2009/01/06/asp-net-4-0-clientid-overview.aspx

I'm only adding an answer so that the final code can be shown. I couldn't have got this far without Skabbes so he'll get the points.

The selector returns the first instance of an asp control with the given clientid. It works without a tagname (though it is much slower). I've optimized it to the best of my abilities but if someone can speed it up that'd be nice.

Usage: $("input:clientID(TextBox1)");

enjoy......

(function ($) {
    // A cache for holding our grepped array.
    var cache = [];

    $.expr[":"].clientID = function (node, stackIndex, properties, nodeStack) {
        // <summary>
        //     Selects a single Asp.Net server control whose id has been prefixed.
        // </summary>
        //  <param name="node" type="Object">
        //      This is a reference to the current DOM element being evaluated. 
        //      This is not a jQuery version of the element but the actual DOM node.
        //  </param>
        //  <param name="stackIndex" type="Integer">
        //      This is the zero-based index of the given node within the stack of 
        //      nodes that will be evaluated with this selector call.
        //  </param>
        //  <param name="properties" type="Array">
        //      This is an array of meta data about the custom jQuery selector execution. 
        //      Of this, only the fourth argument (index 3) is of any real value - it contains 
        //      the string data that was passed to the jQuery selector.
        //  </param>
        //  <param name="nodeStack" type="Array">
        //      This is an array of the DOM elements that are being evaluated with this selector.
        //      The inStackIndex integer above is the current DOM element's index within this array.
        //  </param>
        //  <returns type="jQuery"/>
        var testID = properties[3],
        endsWithID = "_" + testID,
        nodeID,
        id = node.id,
        lastIndex,

        grep = function (elems, callback, inv) {
            /// <summary>
            /// Tweaked for speed version of the native jQuery grep.
            /// </summary>

            var ret = [], retVal, len = elems.length;
            inv = !!inv;

            // Go through the array, only saving the items
            // that pass the validator function
            while (len--) {
                retVal = !!callback(elems[len], len);

                if (inv !== retVal) {
                    ret.push(elems[len]);
                }
            }

            // reverse since we are descending.
            return ret.reverse();
        };

        // Check if there is anything in the cache and grep if not.
        if (cache.length === 0) {
            var trimmed = grep(nodeStack, function (val, key) {
                return val.id.lastIndexOf(testID) !== -1;
            });

            cache = trimmed;
        }

        // Get the first node id only.
        nodeID = cache[0].id;
        lastIndex = nodeID.lastIndexOf(endsWithID);

        // Clear the cache on the last element.
        if (stackIndex === nodeStack.length - 1) {
            cache.length = 0;
        }

        // Check the id.
        if ((nodeID === id) || (lastIndex !== -1 && nodeID.substring(lastIndex + 1) === id)) {
            return true;
        }

        // Strict.
        return false;
    };
} (jQuery));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM