querySelectorAll for grouped data-attributes - javascript

Suppose I have the following markup...
<div data-namespace-title="foo"></div>
<div data-namespace-description="bar"></div>
<div data-namespace-button="foo"></div>
Is there anyway in which I can select of them with a querySelectorAll?
I've tried document.querySelectorAll([data-namespace-*]), but that doesn't work of course

There is no easy way to do it, simply because the browser does not implement wildcard selectors on the attribute name/key (only on its value). What you can do is to simply iterate through your element set (in this case, their common denominator is div), and then filter them out.
You can access the list of attributes of each DOM node by calling <Node>.attributes, and then convert that into an array, and check if one or more of each attribute's name matches the regex pattern /^data-namespace-.*/gi:
var els = document.querySelectorAll("div");
var filteredEls = Array.prototype.slice.call(els).filter(function(el) {
var attributes = Array.prototype.slice.call(el.attributes);
// Determine if attributes matches 'data-namespace-*'
// We can break the loop once we encounter the first attribute that matches
for (var i = 0; i < attributes.length; i++) {
var attribute = attributes[i];
// Return the element if it contains a match, and break the loop
if (attribute.name.match(/^data-namespace-.*/gi))
return el;
}
});
console.log(filteredEls);
<div data-namespace-title="foo">title</div>
<div data-namespace-description="bar">description</div>
<div data-namespace-button="foobar">button</div>
<div data-dummy>dummy</div>
Update: if you're familiar with ES6, it gets a lot cleaner, because:
We can use Array.from in place of the cumbersome Array.prototype.slice.call(...). Pro-tip: you can also use the spread operator, i.e. const els = [...document.querySelectorAll("div")].
We can use Array.some in place of manually creating a for loop with return logic
const els = Array.from(document.querySelectorAll("div"));
const filteredEls = els.filter(el => {
const attributes = Array.from(el.attributes);
return attributes.some(attribute => attribute.name.match(/^data-namespace-.*/gi));
});
console.log(filteredEls);
<div data-namespace-title="foo">title</div>
<div data-namespace-description="bar">description</div>
<div data-namespace-button="foobar">button</div>
<div data-dummy>dummy</div>

Not sure if you would be up for changing the format of you attributes, but making them all the same and adding an extra attribute could be of use if you want to using querySelectorAll
Array.from(document.querySelectorAll('[data-namespace]')).forEach(el => {
console.log(el.getAttribute('data-value'))
})
<div data-namespace="title" data-value="foo"></div>
<div data-namespace="description" data-value="bar"></div>
<div data-ns="button" data-value="foo"></div>
<div data-namespace="button" data-value="foo"></div>
Your other option is to use xpath.
Note: When using iterateNext() it will break if you modify the document before calling it.
var divs = document.evaluate('//#*[starts-with(name(.), "data-namespace")]', document, null, XPathResult.ANY_TYPE, null);
var div = divs.iterateNext()
while (div) {
alert(div.ownerElement.textContent)
div = divs.iterateNext()
}
<div data-namespace-title="foo">Foo</div>
<div data-namespace-description="bar">Bar</div>
<div data-ns-button="foo">NS Foo</div>
<div data-namespace-button="foo">Foo</div>

There's no built-in selector for such a thing, but you can still accomplish it easily enough, by selecting all elements and then filtering for those which have an attribute that starts with data-namespace:
console.log(
[...document.querySelectorAll('*')]
.filter(({ attributes }) =>
[...attributes].some(({ name }) => name.startsWith('data-namespace'))
)
);
<div data-baz="baz"></div>
<div data-namespace-title="foo"></div>
<div data-namespace-description="bar"></div>
<div data-namespace-button="foo"></div>

Related

JavaScript possible to select all elements with classname that starts with (...)? [duplicate]

I'm trying to only show certain divs. The way I have decided to do this is to first hide all elements that start with "page" and then only show the correct divs. Here's my (simplified) code:
<form>
<input type="text" onfocus="showfields(1);">
<input type="text" onfocus="showfields(2);">
</form>
<div class="page1 row">Some content</div>
<div class="page1 row">Some content</div>
<div class="page2 row">Some content</div>
<div class="page2 row">Some content</div>
<script>
function showfields(page){
//hide all items that have a class starting with page*
var patt1 = /^page/;
var items = document.getElementsByClassName(patt1);
console.log(items);
for(var i = 0; i < items.length; i++){
items[i].style.display = "none";
}
//now show all items that have class 'page'+page
var item = document.getElementsByClassName('page' + page);
item.style.display = '';
}
</script>
When I console.log(items); I get a blank array. I'm pretty sure the regexp is right (get all items starting with 'page').
The code I'm using is old school JS, but I'm not adverse to using jQuery. Also if there is a solution that doesn't use regexp, that's fine too as I'm new to using regexp's.
getElementsByClassName only matches on classes, not bits of classes. You can't pass a regular expression to it (well, you can, but it will be type converted to a string, which is unhelpful).
The best approach is to use multiple classes…
<div class="page page1">
i.e. This div is a page, it is also a page1.
Then you can simply document.getElementsByClassName('page').
Failing that, you can look to querySelector and a substring matching attribute selector:
document.querySelectorAll("[class^=page]")
… but that will only work if pageSomething is the first listed class name in the class attribute.
document.querySelectorAll("[class*=page]")
… but that will match class attributes which mention "page" and not just those with classes which start with "page" (i.e. it will match class="not-page".
That said, you could use the last approach and then loop over .classList to confirm if the element should match.
var potentials = document.querySelectorAll("[class*=page]");
console.log(potentials.length);
elementLoop:
for (var i = 0; i < potentials.length; i++) {
var potential = potentials[i];
console.log(potential);
classLoop:
for (var j = 0; j < potential.classList.length; j++) {
if (potential.classList[j].match(/^page/)) {
console.log("yes");
potential.style.background = "green";
continue elementLoop;
}
}
console.log("no");
potential.style.background = "red";
}
<div class="page">Yes</div>
<div class="notpage">No</div>
<div class="some page">Yes</div>
<div class="pageXXX">Yes</div>
<div class="page1">Yes</div>
<div class="some">Unmatched entirely</div>
Previous answers contain parts of the correct one, but none really gives it.
To do this, you need to combine two selectors in a single query, using the comma , separator.
The first part would be [class^="page"], which will find all the elements whose class attribute begins with page, this selector is thus not viable for elements with multiple classes, but this can be fixed by [class*=" page"] which will find all the elements whose class attribute have somewhere the string " page" (note the space at the beginning).
By combining both selectors, we have our classStartsWith selector:
document.querySelectorAll('[class^="page"],[class*=" page"]')
.forEach(el => el.style.backgroundColor = "green");
<div class="page">Yes</div>
<div class="notpage">No</div>
<div class="some page">Yes</div>
<div class="pageXXX">Yes</div>
<div class="page1">Yes</div>
<div class="some">Unmatched entirely</div>
You can use jQuery solution..
var $divs = $('div[class^="page"]');
This will get all the divs which start with classname page
$(document).ready(function () {
$("[class^=page]").show();
$("[class^=page]").hide();
});
Use this to show hide div's with specific css class it will show/hide all div's with css class mention.

Select the elements with at least one value in their data attribute, which is included in a certain array

I am writing a filtering function, in which I need to select the elements that have a certain value in their data attribute, and those values are included in an array, allow me to explain it in an example:
For example, I have three elements and a button as follows:
<div data-foo="aa,cc,ff" ></div>
<div data-foo="bb,cc,ff" ></div>
<div data-foo="bb,dd,ee" ></div>
<div class="button" data-boo="aa,ff" ></div>
The data-foo in each element contains comma-separated values. When I click on the button, I create an array (myArray in the code below) from its data attribute, then I need to select those elements that at least one of the values in that myArray is in their data-foo, for a clear explanation please see the code below:
$( ".button" ).click(function() {
// First I make an array from the button's data attribute
var myArray = $(this).data('boo').split(',');
// Select should be elements that their da-foo has at least one
// — of values in the array above
var Select = "?"
});
How the Select variable can target the first two elements, since the first one has both "aa" and "ff", and the second element has "ff".
I really tried to put it the way that makes sense, if it is not clear enough, please let me know and I will be happy to explain more, thank you.
You can use Attribute Contains Selector:
$( ".button" ).click(function() {
// First I make an array from the button's data attribute
var myArray = $(this).data('boo').split(',');
// produces array of strings like '[data-foo*="aa"]'
var selectors = myArray.map(function(value) {
return '[data-foo*="' + value + '"]';
});
// searches by selectors joined by comma, returns all elements
// that satisfy at least one selector
var selectedElements = $(selectors.join(','));
});
Lets use Array.prototype.some for this:
$(".button").click(function() {
// First I make an array from the button's data attribute
var myArray = $(this).data('boo').split(',');
// Select should be elements that their da-foo has at least one
// — of values in the array above
var Select = $("div[data-foo]"); //select all elements with data-foo
Select.each(function(index, element) {
var isInMyArray = $(element).data("foo").split(",").some(function(element) {
if ( myArray.indexOf(element) != -1)
{return true;}//if true then element is in myArray
}); //using some here - if one value is found in array return true.
console.log(isInMyArray);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div data-foo="aa,cc,ff"></div>
<div data-foo="bb,cc,ff"></div>
<div data-foo="bb,dd,ee"></div>
<div class="button" data-boo="aa,ff">test</div>

Intern: loop on Promise.<Array.<leadfoot/Element>>

Let's say I have the following DOM structure, for simplicity:
<div class='myparent'>
<div class='child'>
<div class="label">A</div>
<div class="ico"/>
</div>
<div class='child'>
<div class="label">B</div>
<div class="ico"/>
</div>
<div class='child'>
<div class="label">C</div>
<div class="ico"/>
</div>
</div>
I would like to loop within all child Element returned by the function findAllByCssSelector('.child'). In particular, I would click on the ico div subelement ONLY if the label of the div is B.
I would remember, that findAllByCssSelector() returns Promise.<Array.<leadfoot/Element>>.
Typically I should do something like:
var my_label = null;
this.remote
.findAllByCssSelector('.my-selector').then(function (elementArray) {
for(.....) {
elementArray[i]
.getVisibileText()
.then(function (text) {
if(text == my_label)
elementArray[i].findByCssSelector('.ico').click().end()
}
}
})
I tried this code but did not work, because the elementArray[i] within the getVisibleText().then() function does not exist - it's like I lose its reference. Furthermore, I need also that if the label is not found at the end of the loop, an exception should be thrown.
How can I achieve that? Could anyone help, please?
The simplest way to do this would be to use an Xpath expression to select the item directly, like:
.findByXpath('//div[#class="child" and div[#class="label" and text()="B"]]/div[#class="ico"]')
The expression above will find the first div with class "ico" that's the child of a div with class "child" that has a child div with class "label" and text content "B".
Update
Using an Xpath expression is almost always preferable to looping through elements using Leadfoot commands because it's significantly more efficient, but if looping is desired for some reason, you can do something like:
var my_label = null;
this.remote
.findAllByCssSelector('.my-selector')
.then(function (elementArray) {
return Promise.all(elementArray.map(function (element) {
return element.getVisibleText()
.then(function (text) {
if (text === my_label) {
return element.findByCssSelector('.ico')
.then(function (ico) {
return ico.click();
});
}
});
});
});
A couple of key points to note:
You need to return Promises/Commands from then callbacks when you're performing async operations in the then callbacks
Element methods (like element.findByCssSelector) return Promises, not Commands, so you can't call click on the result.

Need a way to filter divs based on user input and at the same time keep event binding on them

At the moment I'm filtering an array with array.filter, passing in a regex.
The array I am filtering is based on the contents of a div.
Each child div of that div has a text which I extract with $.text() and put into its own array so I can filter it. Then I output the contents of the filtered array.
The problem is that the filtering needs an array of strings, and thus I make separate array which I filter and then print to the html. But, then when a user clicks one of the items in the list, nothing happens, because the user clicks on div which isn't bound to an event. I do the binding on document ready, and I use the bound event to get the value of the data-item-pageid attribute.
I need a way to this which gives me access to the data-item-pageid of the clicked element and still is fast. I would prefer not to bind the events all over again each time the user types, so I think the logic needs to be changed. I need to hide the divs that do not match the regex.
this is the html:
<div class="content">
<div class="categories">
<div class="item" data-item-pageid="1">text1</div>
<div class="item" data-item-pageid="2">text2</div>
<div class="item" data-item-pageid="3">text3</div>
</div>
<div class="categories">
<div class="item" data-item-pageid="4">text4</div>
<div class="item"data-item-pageid="5">text5</div>
<div class="item"data-item-pageid="6">text6</div>
</div>
</div>
<div class="filter_result"></div>
This is the JavaScript code:
// Binds the click event for each category item
$('.category_item').click(function() {
console.log($(this));
});
...
// Array for filter
filterArray = [];
for (var i = 0; i < $categoryItems.length; ++i) {
filterArray[i] = $($categoryItems[i]).text();
}
...
function filterList(key) {
var result = filterArray.filter(/./.test.bind(new RegExp(key, 'i')));
if (key != '' || key) {
var markup = []; //ugh
for (var i = 0; i < result.length; ++i) {
markup += '<div class="category_item">' + result[i] + '</div>';
}
if ($categoryItems.is(':visible'))
$categoriesDiv.toggle();
$filteredResult.html(markup);
} else {
$filteredResult.html('');
if (!$categoryItems.is(':visible'))
$categoriesDiv.toggle();
}
}
You could do delegated event handling
$(CONTAINER_SELECTOR).on('click', CHILD_SELECTOR, function(){
/* Your click handler*/
});
Where CONTAINER_SELECTOR is an element that is not added/removed so it retains its listeners
CHILD_SELECTOR is the selector for the children which are added/removed, of whose clicks we wish to listen to
In your case $(CONTAINER_SELECTOR) could be $filteredResult and CHILD_SELECTOR .category_item.
For better understanding of delegated events check out the official docs or have a look at one of my other answers on delegated events.
UPDATE
You could easily retrieve the filtered list using:
$elements = $('.item');
$filteredList = $elements.filter(
function(index){
return conditionIsMetFor($(this).text()); // this refers to the element under iteration
}
);
Then you cant take $filteredList and:
$.clone() it
iterate over it and create new elements based on the $.data() they contain
etc.

jQuery select if multiple elements has the same class

Example HTML:
<div class="elem-1"></div>
<div class="elem-2"></div>
<div class="elem-2"></div>
<div class="elem-3"></div>
<div class="elem-4"></div>
<div class="elem-4"></div>
<div class="elem-4"></div>
Needed:
How can I select the div's which has "elem-2"s and "elem-4"s with jQuery selectors? (multiple elements has same class)
You can use , to separate different selectors:
$(".elem-2, .elem-4")
selector will be given the value that you desire
http://jsfiddle.net/mMEN5/1
var selector = $();
var list = $('[class]').toArray().reduce(function(p, e){
p = p || {};
var classes = $(e).prop('class').split(/\s/);
$.each(classes, function(i,c){
p[c] = p[c] || [];
p[c].push(e);
});
return p;
}, {})
for (el in list) if (list[el].length > 1) selector=selector.add(list[el]);
​
$('.elem-2, .elem-4').hide() // or any other jquery method
By the way, it's not a jQuery selector, it's just a CSS selector, see this article for an explanation
Even though you have asked for css selector, we can also use the attributeStartsWith selector
I will just mention the selector usage. you can use to for any internal DOM chnage
alert($('div[class^="elem-2"], div[class^="elem-4"]').length);
here i have used .length just to give a correct count. Use can use above with id attributes also.

Categories

Resources