Search Engine with jQuery :contains() [duplicate] - javascript

Is there a case insensitive version of the :contains jQuery selector or should I do the work manually by looping over all elements and comparing their .text() to my string?

What I ended up doing for jQuery 1.2 is :
jQuery.extend(
jQuery.expr[':'], {
Contains : "jQuery(a).text().toUpperCase().indexOf(m[3].toUpperCase())>=0"
});
This will extend jquery to have a :Contains selector that is case insensitive, the :contains selector remains unchanged.
Edit: For jQuery 1.3 (thanks #user95227) and later you need
jQuery.expr[':'].Contains = function(a,i,m){
return jQuery(a).text().toUpperCase().indexOf(m[3].toUpperCase())>=0;
};
Edit:
Apparently accessing the DOM directly by using
(a.textContent || a.innerText || "")
instead of
jQuery(a).text()
In the previous expression speeds it up considerably so try at your own risk if speed is an issue. (see #John 's question)
Latest edit: For jQuery 1.8 it should be:
jQuery.expr[":"].Contains = jQuery.expr.createPseudo(function(arg) {
return function( elem ) {
return jQuery(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
};
});

To make it optionally case insensitive:
http://bugs.jquery.com/ticket/278
$.extend($.expr[':'], {
'containsi': function(elem, i, match, array)
{
return (elem.textContent || elem.innerText || '').toLowerCase()
.indexOf((match[3] || "").toLowerCase()) >= 0;
}
});
then use :containsi instead of :contains

As of jQuery 1.3, this method is deprecated. To get this to work it needs to be defined as a function:
jQuery.expr[':'].Contains = function(a,i,m){
return jQuery(a).text().toUpperCase().indexOf(m[3].toUpperCase())>=0;
};

If someone (like me) is interested what do a and m[3] mean in Contains definition.
KEY/LEGEND: Params made available by jQuery for use in the selector definitions:
r = jQuery array of elements being scrutinised. (eg: r.length = Number of elements)
i = index of element currently under scrutiny, within array r.
a = element currently under scrutiny. Selector statement must return true to include it in its matched results.
m[2] = nodeName or * that we a looking for (left of colon).
m[3] = param passed into the :selector(param). Typically an index number, as in :nth-of-type(5), or a string, as in :color(blue).

In jQuery 1.8 you will need to use
jQuery.expr[":"].icontains = jQuery.expr.createPseudo(function (arg) {
return function (elem) {
return jQuery(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
};
});

A variation that seems to perform slightly faster and that also allows regular expressions is:
jQuery.extend (
jQuery.expr[':'].containsCI = function (a, i, m) {
//-- faster than jQuery(a).text()
var sText = (a.textContent || a.innerText || "");
var zRegExp = new RegExp (m[3], 'i');
return zRegExp.test (sText);
}
);
Not only is this case-insensitive, but it allows powerful searches like:
$("p:containsCI('\\bup\\b')") (Matches "Up" or "up", but not "upper", "wakeup", etc.)
$("p:containsCI('(?:Red|Blue) state')") (Matches "red state" or "blue state", but not "up state", etc.)
$("p:containsCI('^\\s*Stocks?')") (Matches "stock" or "stocks", but only at the start of the paragraph (ignoring any leading whitespace).)

May be late.... but,
I'd prefer to go this way..
$.extend($.expr[":"], {
"MyCaseInsensitiveContains": function(elem, i, match, array) {
return (elem.textContent || elem.innerText || "").toLowerCase().indexOf((match[3] || "").toLowerCase()) >= 0;
}
});
This way, you DO NOT tamper with jQuery's NATIVE '.contains'... You may need the default one later...if tampered with, you might find yourself back to stackOverFlow...

jQuery.expr[':'].contains = function(a,i,m){
return jQuery(a).text().toUpperCase().indexOf(m[3].toUpperCase())>=0;
};
The update code works great in 1.3, but "contains" should be lower case on the first letter unlike the previous example.

Refer below to use ":contains" to find text ignoring its case sensitivity from an HTML code,
$.expr[":"].contains = $.expr.createPseudo(function(arg) {
return function( elem ) {
return $(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
};
});
$("#searchTextBox").keypress(function() {
if($("#searchTextBox").val().length > 0){
$(".rows").css("display","none");
var userSerarchField = $("#searchTextBox").val();
$(".rows:contains('"+ userSerarchField +"')").css("display","block");
} else {
$(".rows").css("display","block");
}
});
You can also use this link to find case ignoring code based on your jquery version,
Make jQuery :contains Case-Insensitive

A faster version using regular expressions.
$.expr[':'].icontains = function(el, i, m) { // checks for substring (case insensitive)
var search = m[3];
if (!search) return false;
var pattern = new RegExp(search, 'i');
return pattern.test($(el).text());
};

I had a similar problem with the following not working...
// This doesn't catch flac or Flac
$('div.story span.Quality:not(:contains("FLAC"))').css("background-color", 'yellow');
This works and without the need for an extension
$('div.story span.Quality:not([data*="flac"])').css("background-color", 'yellow');
This works too, but probably falls into the "manually looping" category....
$('div.story span.Quality').contents().filter(function()
{
return !/flac/i.test(this.nodeValue);
}).parent().css("background-color", 'yellow');

New a variable I give it name subString and put string you want to search in some elements text. Then using Jquery selector select elements you need like my example $("elementsYouNeed") and filter by .filter(). In the .filter() it will compare each elements in $("elementsYouNeed") with the function.
In the function i using .toLowerCase() for element text also subString that can avoid case sensitive condition and check if there is a subString in it. After that the .filter() method constructs a new jQuery object from a subset of the matching elements.
Now you can get the match elements in matchObjects and do whatever you want.
var subString ="string you want to match".toLowerCase();
var matchObjects = $("elementsYouNeed").filter(function () {return $(this).text().toLowerCase().indexOf(subString) > -1;});

Related

How to check if element contains string after converting everything to lowercase

I'm using a simple input field to search through a list on my website using this code:
$('#f-search').keyup(function() {
var q = $('#f-search').val().toLowerCase();
$("#f-list .f // toLowerCase // :contains('q')").css('border-color', '#900');
});
My problem is that the list elements (.f) contain unpredictable capital letters, so in order to accurately check it against the input I need to convert it to lower case, but I don't know how to do that and then use :contains. For example, if one .f contains "WoWoWoWoWzzzziees" but the user typed "wow", it wouldn't be a match with my current code, but I'd like it to be.
What you want is:
$("#f-list .f").filter(function() {
return $(this)
.text() // or .html() or .val(), depends on the element type
.toLowerCase()
.indexOf(q) != -1;
}).css('border-color', '#900');
which compares the text inside your elements selected by "#f-list .f" and, if they contain what is in the q variable, they get the css modification applied.
EDIT:
If you also want the list to be reset each time, you can do this:
$("#f-list .f").css('border-color', 'WHATEVER IT WAS').filter(function() {
return $(this)
.text() // or .html() or .val(), depends on the element type
.toLowerCase()
.indexOf(q) != -1;
}).css('border-color', '#900');
For better performance you could cache your list like this:
var f_list = $("#f-list .f"),
f_search = $('#f-search');
f_search.keyup(function() {
var q = f_search.val().toLowerCase();
f_list.css('border-color', 'WHATEVER IT WAS').filter(function() {
return $(this)
.text() // or .html() or .val(), depends on the element type
.toLowerCase()
.indexOf(q) != -1;
}).css('border-color', '#900');
});
You can go as far as creating your own custom :contains selector in jQuery:
From here and here and here:
jQuery.expr[":"].Contains = jQuery.expr.createPseudo(function(arg) {
return function( elem ) {
return jQuery(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
};
});
And use it like this (please note the new selector is :Contains with uppercase C):
$("#f-list .f:Contains('q')").css('border-color', '#900');

Is there an existing or upcoming CSS3 selector for matching substrings of attribute names?

You can do [foo^="bar"] to match nodes which have the attribute foo with value starting with bar.
Is there a way to match nodes with an attribute name starting with a particular string? The use case of this is to match all nodes with a data-* attribute.
Edit: the reason I'm trying this is to avoid iterating over all the nodes looking for these attributes (for performance reasons). I'd be using querySelectorAll and its Sizzle polyfill for older browsers.
One way is using .filter() method:
$('element').filter(function() {
return $.grep(this.attributes, function(value) {
return value.nodeName.indexOf('data') === 0;
}).length;
});
http://jsfiddle.net/QACsw/
It may be a little overkill, but you could write a custom selector:
$.expr[':'].attr = function (elem, index, regex_str) {
var regex = new RegExp(regex_str[3]);
for (var i = 0; i < elem.attributes.length; i++) {
if (elem.attributes[i].name.match(regex)) {
return true;
}
}
return false;
};
Demo: http://jsfiddle.net/LBHwr/
So in your case, you'd do:
$('div:attr(^data)') // starts with
$('div:attr(foo$)') // ends with
It's somewhat similar to the regular attribute selector syntax.

Make an array case-insensitive [duplicate]

I'm trying to use "contains" case insensitively. I tried using the solution at the following stackoverflow question, but it didn't work:
Is there a case insensitive jQuery :contains selector?
For convenience, the solution is copied here:
jQuery.extend(
jQuery.expr[':'], {
Contains : "jQuery(a).text().toUpperCase().indexOf(m[3].toUpperCase())>=0"
});
Here is the error:
Error: q is not a function
Source File: /js/jquery-1.4.js?ver=1.4
Line: 81
Here's where I'm using it:
$('input.preset').keyup(function() {
$(this).next().find("li").removeClass("bold");
var theMatch = $(this).val();
if (theMatch.length > 1){
theMatch = "li:Contains('" + theMatch + "')";
$(this).next().find(theMatch).addClass("bold");
}
});
My use of the original case sensitive "contains" in the same scenario works without any errors. Does anyone have any ideas? I'd appreciate it.
This is what i'm using in a current project, haven't had any problems. See if you have better luck with this format:
jQuery.expr[':'].Contains = function(a, i, m) {
return jQuery(a).text().toUpperCase().indexOf(m[3].toUpperCase()) >= 0;
};
In jQuery 1.8 the API for this changed, the jQuery 1.8+ version of this would be:
jQuery.expr[":"].Contains = jQuery.expr.createPseudo(function(arg) {
return function( elem ) {
return jQuery(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
};
});
You can test it out here. For more detail on 1.8+ custom selectors, check out the Sizzle wiki here.
It's worth noting that the answer is correct but only covers :Contains, and not the alias :contains which could lead to unexpected behavior (or could be used by design for advanced applications that require both sensitive and insensitive search).
This could be resolved by duplicating the extention for the alias:
jQuery.expr[':'].Contains = function(a, i, m) {
return jQuery(a).text().toUpperCase().indexOf(m[3].toUpperCase()) >= 0;
};
jQuery.expr[':'].contains = function(a, i, m) {
return jQuery(a).text().toUpperCase().indexOf(m[3].toUpperCase()) >= 0;
};
Took me a while to work out why it wasn't working for me.
I would do something like this
$.expr[':'].containsIgnoreCase = function (n, i, m) {
return jQuery(n).text().toUpperCase().indexOf(m[3].toUpperCase()) >= 0;
};
And Leave :contains Alone...
DEMO
So why jQuery doesn't support it in it's library?! if it is that easy...
because Does your code pass the turkey code?
May be late.... but,
I'd prefer to go this way..
$.extend($.expr[":"], {
"MyCaseInsensitiveContains": function(elem, i, match, array) {
return (elem.textContent || elem.innerText || "").toLowerCase().indexOf((match[3] || "").toLowerCase()) >= 0;
}
});
This way, you DO NOT tamper with jQuery's NATIVE '.contains'... You may need the default one later...if tampered with, you might find yourself back to stackOverFlow...
i'll allow myself to add my friends:
$.expr[":"].containsNoCase = function (el, i, m) {
var search = m[3];
if (!search) return false;
return eval("/" + search + "/i").test($(el).text());
};
I was just able to ignore jQuery's case sensitivity altogether to achieve what I want using below code:
$.expr[":"].contains = $.expr.createPseudo(function(arg) {
return function( elem ) {
return $(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
};
});
You can use this link to find code based on your jQuery versions to ignore case sensitivity,
https://css-tricks.com/snippets/jquery/make-jquery-contains-case-insensitive/
Also if you want to use :contains and make some search you may want to take a look at this: http://technarco.com/jquery/using-jquery-search-html-text-and-show-or-hide-accordingly

How to filter elements returned by QuerySelectorAll

I'm working on a javascript library, and I use this function to match elements:
$ = function (a)
{
var x;
if (typeof a !== "string" || typeof a === "undefined"){ return a;}
//Pick the quickest method for each kind of selector
if(a.match(/^#([\w\-]+$)/))
{
return document.getElementById(a.split('#')[1]);
}
else if(a.match(/^([\w\-]+)$/))
{
x = document.getElementsByTagName(a);
}
else
{
x = document.querySelectorAll(a);
}
//Return the single object if applicable
return (x.length === 1) ? x[0] : x;
};
There are occasions where I would want to filter the result of this function, like pick out a div span, or a #id div or some other fairly simple selector.
How can I filter these results? Can I create a document fragment, and use the querySelectorAll method on that fragment, or do I have to resort to manual string manipulation?
I only care about modern browsers and IE8+.
If you want to look at the rest of my library, it's here: https://github.com/timw4mail/kis-js
Edit:
To clarify, I want to be able to do something like $_(selector).children(other_selector) and return the children elements matching that selector.
Edit:
So here's my potential solution to the simplest selectors:
tag_reg = /^([\w\-]+)$/;
id_reg = /#([\w\-]+$)/;
class_reg = /\.([\w\-]+)$/;
function _sel_filter(filter, curr_sel)
{
var i,
len = curr_sel.length,
matches = [];
if(typeof filter !== "string")
{
return filter;
}
//Filter by tag
if(filter.match(tag_reg))
{
for(i=0;i<len;i++)
{
if(curr_sell[i].tagName.toLowerCase() == filter.toLowerCase())
{
matches.push(curr_sel[i]);
}
}
}
else if(filter.match(class_reg))
{
for(i=0;i<len;i++)
{
if(curr_sel[i].classList.contains(filter))
{
matches.push(curr_sel[i]);
}
}
}
else if(filter.match(id_reg))
{
return document.getElementById(filter);
}
else
{
console.log(filter+" is not a valid filter");
}
return (matches.length === 1) ? matches[0] : matches;
}
It takes a tag like div, an id, or a class selector, and returns the matching elements with the curr_sel argument.
I don't want to have to resort to a full selector engine, so is there a better way?
I don't think I get the question right. Why would you want to "filter" the result of querySelectorAll() which infact, is some kind of a filter itself. If you query for div span or even better #id div, those results are already filtered, no ?
However, you can apply Array.prototype.filter to the static result of querySelectorAll like follows:
var filter = Array.prototype.filter,
result = document.querySelectorAll('div'),
filtered = filter.call( result, function( node ) {
return !!node.querySelectorAll('span').length;
});
That code would first use querySelectorAll() to query for all <div> nodes within the document. Afterwards it'll filter for <div> nodes which contain at least one <span>. That code doesn't make much sense and is just for demonstrative purposes (just in case some SO member wants to create a donk comment)
update
You can also filter with Element.compareDocumentPosition. I'll also tell if Elements are disconnected, following, preceding, or contained. See MDC .compareDocumentPosition()
Note: NodeList is not a genuine array, that is to say it doesn't have
the array methods like slice, some, map etc. To convert it into an
array, try Array.from(nodeList).
ref: https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelectorAll
for example:
let highlightedItems = Array.from(userList.querySelectorAll(".highlighted"));
highlightedItems.filter((item) => {
//...
})
Most concise way in 2019 is with spread syntax ... plus an array literal [...], which work great with iterable objects like the NodeList returned by querySelectorAll:
[...document.querySelectorAll(".myClass")].filter(el=>{/*your code here*/})
Some browsers that support qsa also support a non-standard matchesSelector method, like:
element.webkitMatchesSelector('.someSelector')
...that will return a boolean representing whether element matched the selector provided. So you could iterate the collection, and apply that method, retaining positive results.
In browsers that don't have a matchesSelector, you'd probably need to build your own selector based method similar to the selector engine you're building.

ID Ends With in pure Javascript

I am working in a Javascript library that brings in jQuery for one thing: an "ends with" selector. It looks like this:
$('[id$=foo]')
It will find the elements in which the id ends with "foo".
I am looking to do this without jQuery (straight JavaScript). How might you go about this? I'd also like it to be as efficient as reasonably possible.
Use querySelectorAll, not available in all browsers (like IE 5/6/7/8) though. It basically works like jQuery:
http://jsfiddle.net/BBaFa/2/
console.log(document.querySelectorAll("[id$=foo]"));
You will need to iterate over all elements on the page and then use string functions to test it. The only optimizations I can think of is changing the starting point - i.e. not document.body but some other element where you know your element will be a child of - or you could use document.getElementsByTagName() to get an element list if you know the tag name of the elements.
However, your task would be much easier if you could use some 3rd-party-javascript, e.g. Sizzle (4k minified, the same selector engine jQuery uses).
So, using everything that was said, I put together this code. Assuming my elements are all inputs, then the following code is probably the best I am going to get?
String.prototype.endsWith = function(suffix) {
return this.indexOf(suffix, this.length - suffix.length) !== -1;
};
function getInputsThatEndWith(text) {
var result = new Array();
var inputs = document.getElementsByTagName("input");
for(var i=0; i < inputs.length; i++) {
if(inputs[i].id.endsWith(text))
result.push(inputs[i]);
}
return result;
}
I put it on JsFiddle: http://jsfiddle.net/MF29n/1/
#ThiefMaster touched on how you can do the check, but here's the actual code:
function idEndsWith(str)
{
if (document.querySelectorAll)
{
return document.querySelectorAll('[id$="'+str+'"]');
}
else
{
var all,
elements = [],
i,
len,
regex;
all = document.getElementsByTagName('*');
len = all.length;
regex = new RegExp(str+'$');
for (i = 0; i < len; i++)
{
if (regex.test(all[i].id))
{
elements.push(all[i]);
}
}
return elements;
}
}
This can be enhanced in a number of ways. It currently iterates through the entire dom, but would be more efficient if it had a context:
function idEndsWith(str, context)
{
if (!context)
{
context = document;
}
...CODE... //replace all occurrences of "document" with "context"
}
There is no validation/escaping on the str variable in this function, the assumption is that it'll only receive a string of chars.
Suggested changes to your answer:
RegExp.quote = function(str) {
return str.replace(/([.?*+^$[\]\\(){}-])/g, "\\$1");
}; // from https://stackoverflow.com/questions/494035/#494122
String.prototype.endsWith = function(suffix) {
return !!this.match(new RegExp(RegExp.quote(suffix) + '$'));
};
function getInputsThatEndWith(text) {
var results = [],
inputs = document.getElementsByTagName("input"),
numInputs = inputs.length,
input;
for(var i=0; i < numInputs; i++) {
var input = inputs[i];
if(input.id.endsWith(text)) results.push(input);
}
return results;
}
http://jsfiddle.net/mattball/yJjDV/
Implementing String.endsWith using a regex instead of indexOf() is mostly a matter of preference, but I figured it was worth including for variety. If you aren't concerned about escaping special characters in the suffix, you can remove the RegExp.quote() bit, and just use
new RegExp(suffix + '$').
If you know the type of DOM elements you are targeting,
then get a list of references to them using getElementsByTagName , and then iterate over them.
You can use this optimization to fasten the iterations:
ignore the elements not having id.
target the nearest known parent of elements you want to seek, lets say your element is inside a div with id='myContainer', then you can get a restricted subset using
document.getElementById('myContainer').getElementsByTagName('*') , and then iterate over them.

Categories