I wonder how I can write the jQuery is method using only javascript?
You can't use any third party libraries. Only the javascript provided from the browser.
FYI the method is in jQuery:
Check the current matched set of elements against a selector, element,
or jQuery object and return true if at least one of these elements
matches the given arguments.
If you need support for older browsers, you should use jQuery. This should work in IE9, Firefox and Chrome.
//returns true if element matches selector or if element is equal to the node passed as selector
function is( elem, selector ) {
var div = document.createElement("div");
var matchesSelector = div.webkitMatchesSelector || div.mozMatchesSelector || div.msMatchesSelector;
return typeof selector == "string" ? matchesSelector.call( elem, selector ) : selector === elem;
}
//returns true if any elements in the array/nodelist "is" selector
function anyIs( elems, selector ) {
var l = elems.length, i;
for( i = 0; i < l; ++i ) {
if( is( elems[i], selector ) ) {
return true;
}
}
return false;
}
is(document.createElement("div"), "div")
//true
is(document.createElement("div"), "li")
//false
anyIs(document.getElementsByTagName("div"), document.getElementsByTagName("div")[3])
//true
anyIs(document.getElementsByTagName("div"), "div")
//true
function is( elem, selector ) {
var sel = elem.ownerDocument.querySelectorAll( selector );
return [].some.call( sel, function( el ) {
return elem === el;
} );
}
Will return true if any element matches.
Related
Like the title says. If it's not then what would be the same as .innerHTML = "" ?
It's nearly the same. If you look at the source for the method, you'll see that it's:
empty: function() {
var elem,
i = 0;
for ( ; ( elem = this[ i ] ) != null; i++ ) {
if ( elem.nodeType === 1 ) {
// Prevent memory leaks
jQuery.cleanData( getAll( elem, false ) );
// Remove any remaining nodes
elem.textContent = "";
}
}
return this;
},
And assigning the empty string to the .textContent of an element is the same as assigning the empty string to the .innerHTML of an element.
The only difference is that .empty calls .cleanData, which removes a number of jQuery-specific data/events associated with the element, if there happen to be any.
Let's say I have the following element TEXT in HTML:
<div id="TEXT">
<p>First <strong>Line</strong></p>
<p>Seond <em>Line</em></p>
</div>
How should one extract the raw text from this element, without HTML tags, but preserving the line breaks?
I know about the following two options but neither of them seems to be perfect:
document.getElementById("TEXT").textContent
returns
First LineSecond Line
problem: ignores the line break that should be included between paragraphs
document.getElementById("TEXT").innerText
returns
First Line
Second Line
problem: is not part of W3C standard and is not guaranteed to work in all browsers
Here's a handy function for getting text contents of any element and it works well on all platforms, and yes, it preserves line breaks.
function text(e){
var t = "";
e = e.childNodes || e;
for(var i = 0;i<e.length;i++){
t+= e[i].nodeType !=1 ? e[i].nodeValue : text(e[i].childNodes);
}
return t;
}
You can check how jQuery does it. It uses sizzle js. Here is the function that you can use.
<div id="TEXT">
<p>First <strong>Line</strong></p>
<p>Seond <em>Line</em></p>
</div>
<script>
var getText = function( elem ) {
var node,
ret = "",
i = 0,
nodeType = elem.nodeType;
if ( !nodeType ) {
// If no nodeType, this is expected to be an array
while ( (node = elem[i++]) ) {
// Do not traverse comment nodes
ret += getText( node );
}
} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
// Use textContent for elements
// innerText usage removed for consistency of new lines (jQuery #11153)
if ( typeof elem.textContent === "string" ) {
return elem.textContent;
} else {
// Traverse its children
for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
ret += getText( elem );
}
}
} else if ( nodeType === 3 || nodeType === 4 ) {
return elem.nodeValue;
}
// Do not include comment or processing instruction nodes
return ret;
};
console.log(getText(document.getElementById('TEXT')));
<script>
I am writing a UserScript that will remove elements from a page that contain a certain string.
If I understand jQuery's contains() function correctly, it seems like the correct tool for the job.
Unfortunately, since the page I'll be running the UserScript on does not use jQuery, I can't use :contains(). Any of you lovely people know what the native way to do this is?
http://codepen.io/coulbourne/pen/olerh
This should do in modern browsers:
function contains(selector, text) {
var elements = document.querySelectorAll(selector);
return [].filter.call(elements, function(element){
return RegExp(text).test(element.textContent);
});
}
Then use it like so:
contains('p', 'world'); // find "p" that contain "world"
contains('p', /^world/); // find "p" that start with "world"
contains('p', /world$/i); // find "p" that end with "world", case-insensitive
...
Super modern one-line approach with optional chaining operator
[...document.querySelectorAll('*')].filter(element => element.childNodes?.[0]?.nodeValue?.match('❤'));
And better way is to search in all child nodes
[...document.querySelectorAll("*")].filter(e => e.childNodes && [...e.childNodes].find(n => n.nodeValue?.match("❤")))
If you want to implement contains method exaclty as jQuery does, this is what you need to have
function contains(elem, text) {
return (elem.textContent || elem.innerText || getText(elem)).indexOf(text) > -1;
}
function getText(elem) {
var node,
ret = "",
i = 0,
nodeType = elem.nodeType;
if ( !nodeType ) {
// If no nodeType, this is expected to be an array
for ( ; (node = elem[i]); i++ ) {
// Do not traverse comment nodes
ret += getText( node );
}
} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
// Use textContent for elements
// innerText usage removed for consistency of new lines (see #11153)
if ( typeof elem.textContent === "string" ) {
return elem.textContent;
} else {
// Traverse its children
for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
ret += getText( elem );
}
}
} else if ( nodeType === 3 || nodeType === 4 ) {
return elem.nodeValue;
}
// Do not include comment or processing instruction nodes
return ret;
};
SOURCE: Sizzle.js
The original question is from 2013
Here is an even older solution, and the fastest solution because the main workload is done by the Browser Engine NOT the JavaScript Engine
The TreeWalker API has been around for ages, IE9 was the last browser to implement it... in 2011
All those 'modern' and 'super-modern' querySelectorAll("*") need to process all nodes and do string comparisons on every node.
The TreeWalker API gives you only the #text Nodes, and then you do what you want with them.
You could also use the NodeIterator API, but TreeWalker is faster
function textNodesContaining(txt, root = document.body) {
let nodes = [],
node,
tree = document.createTreeWalker(
root,
4, // NodeFilter.SHOW_TEXT
{
node: node => RegExp(txt).test(node.data)
});
while (node = tree.nextNode()) { // only return accepted nodes
nodes.push(node);
}
return nodes;
}
Usage
textNodesContaining(/Overflow/);
textNodesContaining("Overflow").map(x=>console.log(x.parentNode.nodeName,x));
// get "Overflow" IN A parent
textNodesContaining("Overflow")
.filter(x=>x.parentNode.nodeName == 'A')
.map(x=>console.log(x));
// get "Overflow" IN A ancestor
textNodesContaining("Overflow")
.filter(x=>x.parentNode.closest('A'))
.map(x=>console.log(x.parentNode.closest('A')));
This is the modern approach
function get_nodes_containing_text(selector, text) {
const elements = [...document.querySelectorAll(selector)];
return elements.filter(
(element) =>
element.childNodes[0]
&& element.childNodes[0].nodeValue
&& RegExp(text, "u").test(element.childNodes[0].nodeValue.trim())
);
}
Well, jQuery comes equipped with a DOM traversing engine that operates a lot better than the one i'm about to show you, but it will do the trick.
var items = document.getElementsByTagName("*");
for (var i = 0; i < items.length; i++) {
if (items[i].innerHTML.indexOf("word") != -1) {
// Do your magic
}
}
Wrap it in a function if you will, but i would strongly recommend to use jQuery's implementation.
I have this html structure :
<body>
<a>
<b>
<c>
<d>
</d>
</c>
</b>
</a>
</body>
I use the <d> element as the first node to start with.
Question :
var s= $("d").parentsUntil("body").andSelf().map(function(){
return this.tagName;
}).get();
It should start from the bottom and to top meaning the s array should look like d,c,b,a.
But it apparently look like : ["A", "B", "C", "D"]
Why is that ?
Jsbin
.andSelf() causes jQuery to re-order the array.
You can try :
var s= $("d").parentsUntil("body").map(function(){
return this.tagName;
}).get();
The output of this code looks like:["C", "B","A" ].
If you look at addBack's code (to which andSelf is an alias), you see this :
add: function( selector, context ) {
var set = typeof selector === "string" ?
jQuery( selector, context ) :
jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
all = jQuery.merge( this.get(), set );
return this.pushStack( jQuery.unique(all) );
},
addBack: function( selector ) {
return this.add( selector == null ?
this.prevObject : this.prevObject.filter(selector)
);
}
So you see it calls unique.
By looking further, you see
jQuery.unique = Sizzle.uniqueSort;
and
Sizzle.uniqueSort = function( results ) {
var elem,
duplicates = [],
i = 1,
j = 0;
// Unless we *know* we can detect duplicates, assume their presence
hasDuplicate = !support.detectDuplicates;
results.sort( sortOrder );
if ( hasDuplicate ) {
for ( ; (elem = results[i]); i++ ) {
if ( elem === results[ i - 1 ] ) {
j = duplicates.push( i );
}
}
while ( j-- ) {
results.splice( duplicates[ j ], 1 );
}
}
return results;
};
So, addBack sorts the set as it ensures the added element isn't yet inside.
In this simple script i get the error "obj.parentNode.getElementById is not a function", and I have no idea, what is wrong.
<script type="text/javascript">
function dosomething (obj) {
sibling=obj.parentNode.getElementById("2");
alert(sibling.getAttribute("attr"));
}
</script>
<body>
<div>
<a id="1" onclick="dosomething(this)">1</a>
<a id="2" attr="some attribute">2</a>
</div>
</body>
.getElementById() is on document, like this:
document.getElementById("2");
Since IDs are supposed to be unique, there's no need for a method that finds an element by ID relative to any other element (in this case, inside that parent). Also, they shouldn't start with a number if using HTML4, a numberic ID is valid in HTML5.
replace .getElementById(id) with .querySelector('#'+id);
document.getElementById() won't work if the node was created on the fly and not yet attached into the main document dom.
For example with Ajax, not all nodes are attached at any given point. In this case, you'd either need to explicitly track a handle to each node (generally best for performance), or use something like this to look the objects back up:
function domGet( id , rootNode ) {
if ( !id ) return null;
if ( rootNode === undefined ) {
// rel to doc base
var o = document.getElementById( id );
return o;
} else {
// rel to current node
var nodes = [];
nodes.push(rootNode);
while ( nodes && nodes.length > 0 ) {
var children = [];
for ( var i = 0; i<nodes.length; i++ ) {
var node = nodes[i];
if ( node && node['id'] !== undefined ) {
if ( node.id == id ) {
return node; // found!
}
}
// else keep searching
var childNodes = node.childNodes;
if ( childNodes && childNodes.length > 0 ) {
for ( var j = 0 ; j < childNodes.length; j++ ) {
children.push( childNodes[j] );
}
}
}
nodes = children;
}
// nothing found
return null;
}
}