/* .hasData() jQuery plugin This plugin adds a .hasData() jQuery function to filter elements by their .data() dataset. It takes a single option which represents the key or set of {key:value,key:value,..} pairs to search for. It returns the set of matched elements. If more than one key:value pair is specified, the entire set must match. Values can of course be of any data type (strings, functions, arrays, objects, etc.). If any values are objects, the objects are searched recursively. Three ways to use. 1. See if key is present in .data(): $elements.hasData('myKey'); 2. See if a set of key value pairs are present in .data(): var obj = { 'key1' : 'value', 'key2' : 'value', 'key3' : { 'a' : 'value', 'b' : 'value' } } $elements.hasData(obj); 3. Alternative notation - allows you to search for a deep key, and can accomplish both 1 and 2 above. Pass true as the first argument. For the second argument, define the object key chain in an array. For data.a.b.c.d, do ['a','b','c','d']. Third argument is optional are represents a value that the key in argument two must equal. // to check if key data.a.b.c.d exists: var keyChain = ['a','b','c','d']; $elements.hasData(true, keyChain); // to check if key data.a.b.c.d exists and equals 'text': $elements.hasData(true, keyChain, 'text'); // to accomplish 1 above: var keyChain = ['myKey']; $elements.hasData(true, keyChain); // to accomplish 2 above: var keyChain1 = ['key1']; var keyValue1 = 'value'; var keyChain2 = ['key2']; var keyValue2 = 'value'; var keyChain3 = ['key3']; var keyValue3 = { 'a' : 'value', 'b' : 'value' } $elements.hasData(true, keyChain1, keyValue1) .hasData(true, keyChain2, keyValue2) .hasData(true, keyChain3, keyValue3) Version 01.12.12 By E. C. Ellingsworth */ // plugin definition // function is not assigned to a variable, therefore it is executed immediately // notice that the jQuery object is passed to the function as $ (function( $ ){ // main function call passes to other methods in namespace $.fn.hasData = function(arg1, arg2, arg3) { // args can take on different meanings depending on type var searchObjectForObject = function(needle, haystack){ // this search function can call itself, allowing to search // objects recursively, both needle and haystack are objects // and we're matching all key:value's of needle to haystack var isNeedleInHaystack = true; for (var key in needle){ var value = needle[key]; if (value !== null && typeof(value) == 'object'){ if (haystack[key] !== null && typeof(haystack[key]) == 'object'){ // both needle[key] and haystack[key] are objects, they need to be compared isNeedleInHaystack = isNeedleInHaystack && searchObjectForObject(needle[key], haystack[key]); } else{ // needle[key] is an object, but haystack[key] is not! isNeedleInHaystack = false; } } else if (typeof(value) == 'function'){ if (typeof(haystack[key]) == 'function'){ // both needle[key] and haystack[key] are functions, they need to be compared isNeedleInHaystack = isNeedleInHaystack && (needle[key].toString() === haystack[key].toString()); } else{ // needle[key] is a function, but haystack[key] is not! isNeedleInHaystack = false; } } else{ // neele[key] is not an object or function, just compare to value in haystack[key] isNeedleInHaystack = isNeedleInHaystack && (value === haystack[key]); } } return isNeedleInHaystack; } var compareObjects = function(obj1, obj2){ // function calls itself recursively if any keys are objects themselves var objectsAreEqual = true; var matchObjects = function (obj1, obj2){ // this function does a partial comparison, returns true if all // of obj1 is found in obj2, but obj2 may have extra keys // to get a full comparison, you have to call this in both // directions: matchObjects(o1,o2) && matchObjects(o2,o1) var isOneInTwo = true; for (var key in obj1){ var value1 = obj1[key]; if ((typeof(obj1[key]) == 'object' && typeof(obj2[key]) == 'object') && (obj1[key] !== null && obj2[key] !== null)){ isOneInTwo = isOneInTwo && compareObjects(obj1[key], obj2[key]); } else if (typeof(obj1[key]) == 'function' && typeof(obj2[key]) == 'function'){ isOneInTwo = isOneInTwo && (obj1[key].toString() == obj2[key].toString()); } else if (typeof(obj1[key]) !== typeof(obj2[key])){ isOneInTwo = false; } else if (typeof(obj2[key]) == 'undefined'){ isOneInTwo = false; } else{ isOneInTwo = isOneInTwo && (obj1[key] === obj2[key]); } } return isOneInTwo; } // loop through all keys in obj1 and see if they're the same in obj2 // then loop through all keys in obj2 and see if they're the same in obj1 objectsAreEqual = matchObjects(obj1, obj2) && matchObjects(obj2, obj1); return objectsAreEqual; } $matchedElements = $(); this.each( function(index){ var $element = $(this); var haystack = $element.data(); if (typeof(arg1) == 'string'){ var needle = arg1; // match on key needle if (needle in haystack){ // check for key existence $matchedElements = $matchedElements.add($element); } } else if (typeof(arg1) == 'object' && arg1 !== null){ var needle = arg1; if (searchObjectForObject(needle, haystack)){ // check that all key value pairs of needle exist in haystack $matchedElements = $matchedElements.add($element); } } else if (typeof(arg1) == 'boolean'){ // arg1 is true, so arg2 should be an array representing // an oject key chain (arg2 = ['key1','key2','key3'] for // object.key1.key2.key3) // and the optional arg3 represents the value of the final // key in the object key chain if (typeof(arg2) != 'undefined' && Object.prototype.toString.call(arg2) === '[object Array]'){ // arg2 is an array var needlePiece = arg2; var i = 0; var nextKey = needlePiece[i]; var pieceOfHaystack = haystack; while ((i < needlePiece.length) && (typeof(pieceOfHaystack) == 'object') && (pieceOfHaystack !== null) && (nextKey in pieceOfHaystack)){ pieceOfHaystack = pieceOfHaystack[nextKey]; i++; nextKey = needlePiece[i]; } // if we get here and i=needlePiece.length, then the key chain // specified in needlePiece is present in haystack, arg3 is optional if (i == needlePiece.length && typeof(arg3) == 'undefined'){ $matchedElements = $matchedElements.add($element); } else if (i == needlePiece.length && typeof(arg3) != 'undefined'){ // in this case, match the value in arg3 var needle = arg3; if (typeof(pieceOfHaystack) !== typeof(needle)){ // do nothing } else if (typeof(pieceOfHaystack) == 'object' && pieceOfHaystack !== null){ if (compareObjects(pieceOfHaystack, needle)){ $matchedElements = $matchedElements.add($element); } } else if (typeof(pieceOfHaystack) == 'function'){ if (pieceOfHaystack.toString() == needle.toString()){ $matchedElements = $matchedElements.add($element); } } else if (pieceOfHaystack === needle){ $matchedElements = $matchedElements.add($element); } } } } }); return $matchedElements; }; })( jQuery ); // here is where the jQuery object is passed into the function