Return DOM elements based on array - javascript

As a challenge, I am trying to create a JavaScript selection engine i.e. a JavaScript function that will return DOM elements given a CSS selector.
I cant use document.querySelector/document.querySelectorAll.
I am currently creating a object of the parameter, but am now stuck. I now need to loop through every element on the page, and if it matches my tag, or class/id, push that element to an array.
$("div") //Should return 2 DIVs
$("img.some_class") //Should return 1 IMG
$("#some_id") //Should return 1 DIV
$(".some_class") //Should return 1 DIV and 1 IMG
function $ (selector) {
var elements =[];
var pageTags =[];
var all = document.getElementsByTagName("*");
//splits selector
var arg = parse(selector);
function parse(subselector) {
var obj = {tags:[], classes:[], ids:[], attrs:[]};
subselector.split(/(?=\.)|(?=#)|(?=\[)/).forEach(function(token){
switch (token[0]) {
case '#':
obj.ids.push(token.slice(1));
break;
case '.':
obj.classes.push(token.slice(1));
break;
case '[':
obj.attrs.push(token.slice(1,-1).split('='));
break;
default :
obj.tags.push(token);
break;
}
});
return obj;
}
console.log(arg);
for (var item of all) {
//gets tagname of all page elements
var element = item.tagName.toLowerCase();
console.log(element);
//if argument contains DOM element
if (arg.indexOf(element) !== -1) {
var x = document.getElementsByTagName(element);
for (var test of x) {
elements.push(test);
}
}
}
return elements;
}
<html>
<head>
<script src="Answer.js"></script>
<script src="Test.js"></script>
</head>
<body onload="test$()">
<div></div>
<div id="some_id" class="some_class some_other_class"></div>
<img id="some_other_id" class="some_class some_other_class"></img>
<input type="text">
</body>
</html>
Please any help on how to do this will be appreciated.

check this jsfiddle.
There would be many many more combinations of course ...
I limited the test cases to the html example you provided.
function _select(attrValues, tagFilter, cssSel) {
var results = [];
//var value = selector.slice(1);
var all = document.getElementsByTagName(tagFilter);
//look for an id attribute
if (cssSel === '#') {
for (var i = 0; i < all.length; i++) {
if (all[i].id === attrValues) {
results.push(all[i]);
}
}
} else {
if (typeof attrValues === 'string') {
for (var i = 0; i < all.length; i++) {
if (all[i].classList.contains(attrValues)) {
results.push(all[i]);
}
}
} else {
//multiple selector classes
var found = 0;
for (var i = 0; i < all.length; i++) {
for (var j = 0; j < attrValues.length; j++) {
if (all[i].classList.contains(attrValues[j])) {
found += 1;
if (found === attrValues.length) {
results.push(all[i]);
}
}
}
}
}
}
return results;
}
function $(selector) {
var cssSel = selector.charAt(0);
var cssSelectors = ['.', '#'];
if (cssSel === cssSelectors[0] || cssSel === cssSelectors[1]) {
//direct selector
var attrValue = selector.slice(1),
tagFilter = '*';
return _select(attrValue, tagFilter, cssSel)
} else {
for (var i = 0; i < cssSelectors.length; i++) {
var tokens = selector.split(cssSelectors[i]);
if (tokens.length > 1 && tokens[0] !== "") {
//nested selector
var tagFilter = tokens[0], //the first of the array should be the tagname ,because the case of the cssSelector at charAt(0) should have been caught in the if at the beginning.
attrValue = tokens.slice(1); //the rest of the array are selector values
return _select(attrValue, tagFilter, cssSel)
}
}
}
return document.getElementsByTagName(selector);
}
//TEST cases
var results = $("div")
console.log('Should return 2 DIVs')
for ( var e of results){
console.log(e)
}
var results = $(".some_class")
console.log('Should return 1 DIV and 1 IMG')
for ( var e of results){
console.log(e)
}
var results = $("#some_id")
console.log('Should return 1 DIV ')
for ( var e of results){
console.log(e)
}
var results = $("img.some_class")
console.log('Should return 1 IMG')
for ( var e of results){
console.log(e)
}
var results = $("div.some_class.some_other_class")
console.log('Should return 1 div')
for ( var e of results){
console.log(e)
}

Related

how to prevent filter to make empty array?

function addSelected(id) {
var feedFound = false;
var selList = [] ;
for (var i = 0; i < vm.feeds.length; i++) {
if (vm.feeds[i].id == id) {
if (vm.rationList.length > 0) {
for (var j = 0; j < vm.rationList.length; j++) {
if (vm.feeds[i].id == vm.rationList[j].id) {
feedFound = true;
break;
}
}
}
if (!feedFound) {
selList.push(vm.feeds[i]);
vm.feeds[i] = vm.feeds.filter(function(item) {
return item.id === vm.feeds[i].id;
});
}
feedFound = false;
}
}
var li = [];
angular.copy(selList, li);
for (var i = 0; i < li.length; i++) {
vm.rationList.push(li[i]);
}
vm.rationListSafe = vm.rationList;
}
This is how i add elements from one list to another with filtering. The problem is, for each filtered element, I get back an empty array. Is there anyway I can solve this?
If you are looking for only one item in your array, use find() instead of filter()
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
PS: find may return null so you should always null-check
Example:
const foundItem = vm.feeds.find(function(item) {
return item.id === vm.feeds[i].id;
});
if (foundItem) {
vm.feeds[i] = foundItem
}

I want to remove redundant code and move in to a separate Jquery function

Here is the code I am trying to remove the redundant code and move the code to separate function.
//Adding Infotypes to filter and checks whether any infotype option is selected
if(this.$infoOptions.val() != null){
var infotypelength = this.$infoOptions.val().length;
var l=0,j;
//Condition to check empty input type
if( infotypelength > 0){
var infotypeList = this.$infoOptions.val();
for(j = 0; j < infotypelength; j++) {
//It checks whether already option is selected and prevents adding to filter if its duplicate.
if(($.inArray( $('#infoOptions').select2('data')[j].text, filterList)) == -1 ){
this.filter.push($('#infoOptions').select2('data')[j].text);
if(infotypeList[j].contains('_subgroup')){
var res = infotypeList[j].split("_");
this.aSubinfotype[l]=res[0];
l=l+1;
}
else
this.aInfotypes.push(infotypeList[j]);
}
}
}
}
//Adding Countries to filter
if(this.$countryOptions.val() != null){
var geoLength = this.$countryOptions.val().length;
//Condition to check empty input type
if( geoLength > 0){
var geoList = this.$countryOptions.val();
var l=0;
for(var j = 0; j < geoLength; j++) {
if(($.inArray( $('#countryOptions').select2('data')[j].text, filterList)) == -1 ){
this.filter.push($('#countryOptions').select2('data')[j].text);
if(geoList[j].contains('_subgroup')){
var res = geoList[j].split("_");
this.aSubgeotype[l]=res[0];
l=l+1;
}
else
this.aGeography.push(geoList[j]);
}
}
}
}
But I am facing problem in passing the variable and cached selectors in to other function. Can anyone help me with this?
I don't know how is done your implementation but I really think that you can improve it, by the way, you can reduce your code in two way bit different :
var myFunction = function(option, filter, array, selector, subType) {
if(option && option.val()){
var optList = option.val();
var optLength = optList.length;
//Condition to check empty input type
if( optLength > 0) {
var l = 0;
for(var j = 0; j < optLength; j++) {
if( ($.inArray( selector.select2('data')[j].text, filterList)) == -1 ){
filter.push(selector.select2('data')[j].text);
if(optList[j].contains('_subgroup')){
var res = optList[j].split("_");
subType[l]=res[0];
l=l+1;
} else {
array.push(optList[j]);
}
}
}
}
}
}
call : myFunction(this.$countryOptions, this.filter, this.aGeography, $('#countryOptions'), this.aSubgeotype)
// data = {option, filter, array, selector, subType}
var myFunction = function(data) {
if(data.option && data.option.val()){
var optList = data.option.val();
var optLength = optList.length;
//Condition to check empty input type
if( optLength > 0) {
var l = 0;
for(var j = 0; j < optLength; j++) {
if( ($.inArray( data.selector.select2('data')[j].text, filterList)) == -1 ){
data.filter.push(data.selector.select2('data')[j].text);
if(optList[j].contains('_subgroup')){
var res = optList[j].split("_");
data.subType[l]=res[0];
l=l+1;
} else {
data.array.push(optList[j]);
}
}
}
}
}
}
call :
myFunction({
option: this.$countryOptions,
filter: this.filter,
array: this.aGeography,
selector: $('#countryOptions'),
subType: this.aSubgeotype
});
or
var data = {
option: this.$countryOptions,
filter: this.filter,
array: this.aGeography,
selector: $('#countryOptions'),
subType: this.aSubgeotype
}
myFunction(data);
The first way is to pass your data one by one, the second you pass your data into an json object.

implementing a system like data-reactid

I am implementing a system similar to data-reactid (from scratch). Something like this: (not exactly for the same purpose as data-reactid is used):
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<div>
<p></p>
<p></p>
<p></p>
</div>
</body>
</html>
to
<!DOCTYPE html>
<html data-id="0">
<head data-id="0.0">
<title data-id="0.0.0"></title>
</head>
<body data-id="0.1">
<div data-id="0.1.1">
<p data-id="0.1.1.0"></p>
<p data-id="0.1.1.1"></p>
<p data-id="0.1.1.2"></p>
</div>
</body>
</html>
I am able to create a JSON object of the parsed HTML but unable to do what I want in a simpler manner, could you please help me, with same!
function mapDOM(element, json) {
var treeObject = {};
// If string convert to document Node
if (typeof element === "string") {
if (window.DOMParser) {
parser = new DOMParser();
docNode = parser.parseFromString(element,"text/xml");
} else { // Microsoft strikes again
docNode = new ActiveXObject("Microsoft.XMLDOM");
docNode.async = false;
docNode.loadXML(element);
}
element = docNode.firstChild;
}
//Recursively loop through DOM elements and assign properties to object
var li=lj=lk=-1;
function treeHTML(element, object) {
++li;
object["type"] = element.nodeName;
var nodeList = element.childNodes;
if (nodeList != null) {
if (nodeList.length) {
object["content"] = [];
for (var i = 0; i < nodeList.length; i++) {
++lj;
if (nodeList[i].nodeType == 3) {
object["content"].push(nodeList[i].nodeValue);
} else {
object["content"].push({});
treeHTML(nodeList[i], object["content"][object["content"].length -1]);
}
document.getElementsByTagName(nodeList[i])[i].setAttribute("data-reactid","0."+i+"."+li+"."+lj);
}
}
}
if (element.attributes != null) {
if (element.attributes.length) {
object["attributes"] = {};
for (var i = 0; i < element.attributes.length; i++) {
object["attributes"][element.attributes[i].nodeName] = element.attributes[i].nodeValue;
}
}
}
}
treeHTML(element, treeObject);
return (json) ? JSON.stringify(treeObject) : treeObject;
}
like that?
function processNode(node, id) {
id = !id ? "0" : String(id);
node.dataset.id = id;
return {
id: id,
type: node.nodeName.toLowerCase(),
content: processNodeList( node.childNodes, id + "." ),
attributes: processAttributes( node.attributes )
}
}
function processNodeList(nodes, prefix){
prefix = !prefix ? "" : String(prefix);
for(var out, i=0, j=0, len = (nodes && nodes.length)|0; i < len; ++i){
var node = nodes[i], nt = node.nodeType;
if(nt === 1){ //Element
value = processNode(node, prefix + j++);
}else if(nt === 3){ //Text-Node
//especially should be kept, and not replaced on stringify
var text = node.textContent.replace(/[\u00A0<>&\u00AD]/g, toHtmlEntity);
/*
//TODO: move that into a filter, applied when the Array is built
//remove all-whitespace-nodes between two block-nodes like p,div,section,h1-h6
if((i === 0 || i === nodes.length-1) && /^[\r\n\t ]*$/.test(text)){
//remove whitespace at the beginning or the end of the node
continue;
}
*/
//compact multiple spaces into a single one
value = text.replace(/([\r\n\t ])+/g, "$1");
}else{
continue;
}
out || (out = []);
out.push(value);
}
return out;
}
function processAttributes(attrs){
for(var out, i = 0, len = (attrs && attrs.length)|0; i < len; ++i){
var attr = attrs[i];
if(attr.nodeName === "data-id") continue;
out || (out = {});
out[attr.nodeName] = attr.nodeValue;
}
return out;
}
function toHtmlEntity(c){
switch(c.charCodeAt(0)){
case 160: return " ";
case 38: return "&";
case 60: return "<";
case 62: return ">";
case 173: return "­";
}
return c;
}
function mapDOM(element) {
// If string convert to document Node
if (typeof element === "string") {
if (window.DOMParser) {
parser = new DOMParser();
docNode = parser.parseFromString(element,"text/xml");
} else { // Microsoft strikes again
docNode = new ActiveXObject("Microsoft.XMLDOM");
docNode.async = false;
docNode.loadXML(element);
}
element = docNode.firstChild;
}
return processNode(element);
}
var tree = mapDOM(document.body);
console.log(JSON.stringify(tree, null, 4));

Removing elements from one to/until another

Let's have an example:
<table>
<tr class="need"></tr>
<tr class="no-need"></tr> // This is ourElement, needs to be removed
<tr></tr> // This element needs to be removed
<tr class="no-need"></tr> // This element needs to be removed
<tr class="no-need"></tr> // This element needs to be removed
<tr class="need"></tr> // Elements removed until this
</table>
I want to remove those four elements at once.
This is what I've done:
function remove(ourElement) {
var body = ourElement.parentNode,
bodyRows = body.getElementsByTagName('tr');
for (var i = 0; i < bodyRows.length; i++) {
if (bodyRows[i] == ourElement) {
if (!bodyRows[i+1].className) {
body.removeChild(bodyRows[i+1]);
}
}
if (bodyRows[i] > ourElement) {
if (bodyRows[i].className == 'no-need') {
body.removeChild(bodyRows[i]);
}
if (bodyRows[i].className == 'need') {
break;
}
}
}
body.removeChild(ourElement);
}
The function removes only the first empy row after ourElement and the ourElement itself.
As i wrote above, I need to remove those four elements at first run of our function.
Pure Javascript needed.
I just realised you may be looking for a function to delete items inside boundaries lets say:
items between class"need" and class"need" and delete all items inside them. if thats your question the answer is as follows:
function remove( tagElement, boundClass ) {
var tr = document.getElementsByTagName(tagElement),
re = new RegExp("(^|\\s)"+ boundClass +"(\\s|$)"),
bound = false,
r = [];
for( var i=0, len=tr.length; i<len; i++ ) {
if( re.test(tr[i].className) ) {
bound = ( bound === true ) ? false : true;
if(bound) continue;
}
if( bound ) r.push( tr[i] );
}
while( r.length )
r[ r.length - 1 ].parentNode.removeChild( r.pop() );
}
remove( "tr", "need" ); // use it like this
you need something like this:
function remove(ourElement) {
var body = ourElement.parentNode;
var childRows = body.childNodes;
var found = false;
for (var i = 0; i < childRows.length; i++) {
var row = childRows[i];
if(found) {
if(!row.className || row.className == "no-need") {
body.removeChild(row);
i--; // as the number of element is changed
} else if(row.className == "need") {
break;
}
}
if(row == ourElement) {
body.removeChild(ourElement);
found = true;
i--; // as the number of element is changed
}
}
}
You cannot use the < or > operators with DOM elements.
function remove(ourElement) {
var body = ourElement.parentNode,
bodyRows = body.getElementsByTagName('tr'),
lb = false;
for (var i = 0; i < bodyRows.length; i++) {
lb = (lb)?(bodyRows[i] == ourElement):lb;
if(lb){
if (!bodyRows[i].className) {
body.removeChild(bodyRows[i]);
}else if (bodyRows[i].className == 'no-need') {
body.removeChild(bodyRows[i]);
}else if (bodyRows[i].className == 'need') {
break;
}
}
}
}
Try this, every time it removes a child it decreases i to compensate:
function remove(ourElement) {
var body = ourElement.parentNode,
bodyRows = body.getElementsByTagName('tr'),
lb = false;
for (var i = 0; i < bodyRows.length; i++) {
if (!lb && bodyRows[i] != ourElement) {
continue;
} else if(bodyRows[i] == ourElement){
lb = true;
}
if (bodyRows[i].className == 'no-need' || !bodyRows[i].className) {
body.removeChild(bodyRows[i]);
i--;
}
}
}

How can you determine if a css class exists with Javascript?

Is there a way to determine whether or not a css class exists using JavaScript?
This should be possible to do using the document.styleSheets[].rules[].selectorText and document.styleSheets[].imports[].rules[].selectorText properties. Refer to MDN documentation.
function getAllSelectors() {
var ret = [];
for(var i = 0; i < document.styleSheets.length; i++) {
var rules = document.styleSheets[i].rules || document.styleSheets[i].cssRules;
for(var x in rules) {
if(typeof rules[x].selectorText == 'string') ret.push(rules[x].selectorText);
}
}
return ret;
}
function selectorExists(selector) {
var selectors = getAllSelectors();
for(var i = 0; i < selectors.length; i++) {
if(selectors[i] == selector) return true;
}
return false;
}
Based on the answer, I created a javascript function for searching for a CSS class in the browser's memory -
var searchForCss = function (searchClassName) {
for (let i = 0; i < document.styleSheets.length; i++) {
let styleSheet = document.styleSheets[i];
try {
for (let j = 0; j < styleSheet.cssRules.length; j++) {
let rule = styleSheet.cssRules[j];
// console.log(rule.selectorText)
if (rule.selectorText && rule.selectorText.includes(searchClassName)) {
console.log('found - ', rule.selectorText, ' ', i, '-', j);
}
}
if (styleSheet.imports) {
for (let k = 0; k < styleSheet.imports.length; k++) {
let imp = styleSheet.imports[k];
for (let l = 0; l < imp.cssRules.length; l++) {
let rule = imp.cssRules[l];
if (
rule.selectorText &&
rule.selectorText.includes(searchClassName)
) {
console.log('found - ',rule.selectorText,' ',i,'-',k,'-',l);
}
}
}
}
} catch (err) {}
}
};
searchForCss('my-class-name');
This will print a line for each occurrence of the class name in any of the rules in any of the stylesheets.
Ref - Search for a CSS class in the browser memory
Here is my solution to this. I'm essentially just looping through document.styleSheets[].rules[].selectorText as #helen suggested.
/**
* This function searches for the existence of a specified CSS selector in a given stylesheet.
*
* #param (string) styleSheetName - This is the name of the stylesheet you'd like to search
* #param (string) selector - This is the name of the selector you'd like to find
* #return (bool) - Returns true if the selector is found, false if it's not found
* #example - console.log(selectorInStyleSheet ('myStyleSheet.css', '.myClass'));
*/
function selectorInStyleSheet(styleSheetName, selector) {
/*
* Get the index of 'styleSheetName' from the document.styleSheets object
*/
for (var i = 0; i < document.styleSheets.length; i++) {
var thisStyleSheet = document.styleSheets[i].href ? document.styleSheets[i].href.replace(/^.*[\\\/]/, '') : '';
if (thisStyleSheet == styleSheetName) { var idx = i; break; }
}
if (!idx) return false; // We can't find the specified stylesheet
/*
* Check the stylesheet for the specified selector
*/
var styleSheet = document.styleSheets[idx];
var cssRules = styleSheet.rules ? styleSheet.rules : styleSheet.cssRules;
for (var i = 0; i < cssRules.length; ++i) {
if(cssRules[i].selectorText == selector) return true;
}
return false;
}
This function offers a speed improvement over other solutions in that we are only searching the stylesheet passed to the function. The other solutions loop through all the stylesheets which is in many cases unnecessary.
/*
You can loop through every stylesheet currently loaded and return an array of all the defined rules for any selector text you specify, from tag names to class names or identifiers.
Don't include the '#' or '.' prefix for an id or class name.
Safari used to skip disabled stylesheets, and there may be other gotchas out there, but reading the rules generally works better across browsers than writing new ones.
*/
function getDefinedCss(s){
if(!document.styleSheets) return '';
if(typeof s== 'string') s= RegExp('\\b'+s+'\\b','i'); // IE capitalizes html selectors
var A, S, DS= document.styleSheets, n= DS.length, SA= [];
while(n){
S= DS[--n];
A= (S.rules)? S.rules: S.cssRules;
for(var i= 0, L= A.length; i<L; i++){
tem= A[i].selectorText? [A[i].selectorText, A[i].style.cssText]: [A[i]+''];
if(s.test(tem[0])) SA[SA.length]= tem;
}
}
return SA.join('\n\n');
}
getDefinedCss('p')//substitute a classname or id if you like
the latest item in the cascade is listed first.
Add this Condition Above
if (!document.getElementsByClassName('className').length){
//class not there
}
else{
//class there
}
If want to check on a element Just use
element.hasClassName( className );
also you can use on a ID
document.getElementById("myDIV").classList.contains('className');
Good Luck !!!
Building on Helen's answer, I came up with this:
//**************************************************************************
//** hasStyleRule
//**************************************************************************
/** Returns true if there is a style rule defined for a given selector.
* #param selector CSS selector (e.g. ".deleteIcon", "h2", "#mid")
*/
var hasStyleRule = function(selector) {
var hasRule = function(selector, rules){
if (!rules) return false;
for (var i=0; i<rules.length; i++) {
var rule = rules[i];
if (rule.selectorText){
var arr = rule.selectorText.split(',');
for (var j=0; j<arr.length; j++){
if (arr[j].indexOf(selector) !== -1){
var txt = trim(arr[j]);
if (txt===selector){
return true;
}
else{
var colIdx = txt.indexOf(":");
if (colIdx !== -1){
txt = trim(txt.substring(0, colIdx));
if (txt===selector){
return true;
}
}
}
}
}
}
}
return false;
};
var trim = function(str){
return str.replace(/^\s*/, "").replace(/\s*$/, "");
};
for (var i=0; i<document.styleSheets.length; i++){
var rules = document.styleSheets[i].rules || document.styleSheets[i].cssRules;
if (hasRule(selector, rules)){
return true;
}
var imports = document.styleSheets[i].imports;
if (imports){
for (var j=0; j<imports.length; j++){
rules = imports[j].rules || imports[j].cssRules;
if (hasRule(selector, rules)) return true;
}
}
}
return false;
};
You could check and see if an object of the style your are looking for already exists. If it does then the css class must exist because an object is using it. For example if you wanted to make sure that distinctly named svg objects each have their own style:
function getClassName(name) {
//Are there any elements which use a style named 'name' ?
if (document.getElementsByClassName(name).length === 0){
//There are none yest, let's make a new style and add it
var style = document.createElement('style');
style.type = 'text/css';
//Where you might provide your own hash function or rnd color
style.innerHTML = '.'+name+' { fill: #' + getHashColor(name) + '; background: #F495A3; }';
//Add the style to the document
document.getElementsByTagName('head')[0].appendChild(style);
}
return name;
}
Note that this is NOT a good approach if you are looking for a style which isn't necessarily used in your document.
if ($(".class-name").length > 0) {
}
That is a nice way to check the class in HTML by using javascript
Oneliner:
[].slice.call(document.styleSheets)
.reduce( (prev, styleSheet) => [].slice.call(styleSheet.cssRules))
.reduce( (prev, cssRule) => prev + cssRule.cssText)
.includes(".someClass")
function getAllSelectors() {
var ret = {};
for(var i=0;i<document.styleSheets.length;i++){
try {
var rules = document.styleSheets[i].rules || document.styleSheets[i].cssRules;
for(var x in rules) {
if(typeof rules[x].selectorText === 'string'){
if(ret[rules[x].selectorText] === undefined){
ret[rules[x].selectorText] = rules[x].style.cssText;
}
else {
ret[rules[x].selectorText] = ret[rules[x].selectorText] + ' ' + rules[x].style.cssText;
}
}
}
}
catch(error){
console.log(document.styleSheets[i]);
}
}
return ret;
}
function selectorExists(selector) {
var selectors = getAllSelectors();
if(selectors[selector] !== undefined){
return true;
}
return false;
}
// var allSelectors = getAllSelectors();

Categories