jQuery .text() length behavior - javascript

I have my HTML coded as follows;
<div class="welcomeText">
<ul>
<li><span<%=Some Java Code%></span>
</li>
</ul>
</div>
Also there is a Javascript code called after document is ready which has the following line;
var welcomeLen = $(".welcomeText span").text().length;
Now if I want to update my HTML code inside li as follows;
<li><span><span class="firstNameWithEllipses"><%=Some Java Code%></span></span>
i.e. I want to add a new span element with class="firstNameWithEllipses"
The issue that I am facing is that the JS calculation for welcomeLen changes if I add the above HTML code.
I am not quite sure how the text().length works as it returns the following values for the 2 separate cases;
When rendered as
<span>Hello, StudentFname87654 from Functional!</span>
it returns 41
&
When rendered as
<span>Hello, <span class="firstNameWithEllipses">StudentFname87654</span> from Functional!</span>
it returns 58
How do I ensure that the welcomeLen remains the same even if I add any HTML code inside the span ?

This is because your selector matches both span elements and returns the text from both. You could have added any element other than a child span and you would not have experienced this issue:
console.log($(".welcomeText span").text());
//Output: Hello, StudentFname87654 from Functional!StudentFname87654
Make your selector more specific. You could, for example, select only span elements that are direct children of an li that is a descendant of .welcomeText:
var welcomeLen = $(".welcomeText li > span").text().length;
Here's a working example.
Update
If you're interested in why this is the case, you can look at the jQuery source for the text method. Like most jQuery methods, it iterates over all of the elements in the matched set:
var ret = "";
jQuery.each( text || this, function(){ //Iterate over matched set
jQuery.each( this.childNodes, function(){ //Iterate over children of current element
if ( this.nodeType != 8 )
ret += this.nodeType != 1 ?
this.nodeValue : //Append text to overall string
jQuery.fn.text( [ this ] ); //Get text of descendants
});
});
return ret;

var welcomeLen = $(".welcomeText span").not(".firstNameWithEllipses").text().length;

Related

Select element by tag/classname length

I'd like to select an element using javascript/jquery in Tampermonkey.
The class name and the tag of the elements are changing each time the page loads.
So I'd have to use some form of regex, but cant figure out how to do it.
This is how the html looks like:
<ivodo class="ivodo" ... </ivodo>
<ivodo class="ivodo" ... </ivodo>
<ivodo class="ivodo" ... </ivodo>
The tag always is the same as the classname.
It's always a 4/5 letter random "code"
I'm guessing it would be something like this:
$('[/^[a-z]{4,5}/}')
Could anyone please help me to get the right regexp?
You can't use regexp in selectors. You can pick some container and select its all elements and then filter them based on their class names. This probably won't be super fast, though.
I made a demo for you:
https://codepen.io/anon/pen/RZXdrL?editors=1010
html:
<div class="container">
<abc class="abc">abc</abc>
<abdef class="abdef">abdef</abdef>
<hdusf class="hdusf">hdusf</hdusf>
<ueff class="ueff">ueff</ueff>
<asdas class="asdas">asdas</asdas>
<asfg class="asfg">asfg</asfg>
<aasdasdbc class="aasdasdbc">aasdasdbc</aasdasdbc>
</div>
js (with jQuery):
const $elements = $('.container *').filter((index, element) => {
return (element.className.length === 5);
});
$elements.css('color', 'red');
The simplest way to do this would be to select those dynamic elements based on a fixed parent, for example:
$('#parent > *').each(function() {
// your logic here...
})
If the rules by which these tags are constructed are reliably as you state in the question, then you could select all elements then filter out those which are not of interest, for example :
var $elements = $('*').filter(function() {
return this.className.length === 5 && this.className.toUpperCase() === this.tagName.toUpperCase();
});
DEMO
Of course, you may want initially to select only the elements in some container(s). If so then replace '*' with a more specific selector :
var $elements = $('someSelector *').filter(function() {
return this.className.length === 5 && this.className.toUpperCase() === this.tagName.toUpperCase();
});
You can do this in vanilla JS
DEMO
Check the demo dev tools console
<body>
<things class="things">things</things>
<div class="stuff">this is not the DOM element you're looking for</div>
</body>
JS
// Grab the body children
var bodyChildren = document.getElementsByTagName("body")[0].children;
// Convert children to an array and filter out everything but the targets
var targets = [].filter.call(bodyChildren, function(el) {
var tagName = el.tagName.toLowerCase();
var classlistVal = el.classList.value.toLowerCase();
if (tagName === classlistVal) { return el; }
});
targets.forEach(function(el) {
// Do stuff
console.log(el)
})

How to delete current element if previous element is empty in jquery?

I want to delete element with class "tehnicneinfo" but only if the element I'm checking ( with class "h2size") has no child. I have a bunch of those elements, generated by a plugin and I want to delete only the ones that have the next element without child. I wrote jquery code, but it delets all of my elements, not only the ones that have the next element without child. Here is my jquery code:
$('.news .h2size > div').each(function() {
var ul = $(this).find('ul');
if(!ul.length) $(this).remove();
var h1 = $('.news').find('.tehnicneinfo');
var h2size = $('.news').find('.h2size');
if(h2size.prev().is(':empty'))
{
h1.remove();
}
});
this code is inside $(document).ready(function(). Can you tell me what I'm doing wrong? The code is for something else also, so I'm having truble only from var h1 = $('.news').find('.tehnicneinfo'); this line on. Thanks in advance!
Html:
<div class="news">
<h1 class="tehnicneinfo">xxx</h1>
<div class="h2size">
<div id="xyxyxy">
.......
</div>
</div>
<h1 class="tehnicneinfo">yyy</h1>
<div class="h2size"></div>
....
</div>
That's the html, only that there is like 20 more lines that are the same, but with different values (not yyy and xxx). I would need to delete all 'yyy' (they are not all with same value).
You can use filter to filter the ones you want to remove then remove them
"I want to delete only the ones that have the next element without child"
$('.tehnicneinfo').filter(function(){
return !$(this).next().children().length;
// only ones with next sibling with no children
}).remove();
JSFIDDLE

Removing the text before an element with jQuery

Imagine this scenario:
<span>Item 1</span> | <span>Item 2</span>
How can I target the | and remove it? Also, assume I always need to remove the | before the span with "Item 2" in it, and the list can grow with items being added before OR after "Item 2." All new items will be enclosed within span and they'll be separated by |.
$('span').each(function() {
if ($(this).text() == 'Item 2') {
$(this.previousSibling).remove();
}
});
http://jsfiddle.net/spFUG/2/
manipulating text nodes is one of the things jquery doesn't exactly excel in. Incidentally, the native browser API does this very well. Even if you don't want to use it regularly, this time you probably should. previousSibling selects the previous node, be it a text node, a comment node or an element node. It's probably safe to assume it's always the text node that you want to remove :
var $elem = $(":contains('Item 2')");
$elem.map(function(){ //select the preceding node to any element we want to remove
return this.previousSibling
}).addBack() // select the original element node as well
.remove(); // remove both
note that addBack was added in jQuery 1.8. If you are using an older version, use andSelf instead. If you want to remove only the text node, drop addBack entirely.
.contents finds text nodes.
$("#container").contents().each(function () {
//Text node
if (this.nodeType === 3) {
$(this).remove();
}
});
http://jsfiddle.net/QQsk5/
Find the parent of the spans and use .html() along with .replace().
JAVASCRIPT:
$('span:first-child').parent().html($('span:first-child').parent().html().replace(/\|/g, ''))
DEMO:
http://jsfiddle.net/dirtyd77/5F3kf/2/
UPDATED
Here's some direct javascript that would remove all " | " from the code
var parent = $('span').parent().get(0), children = parent.childNodes, node;
for (node = 0;node < children.length;node++){
if (children[node].data === " | "){
parent.removeChild(children[node]);
}
}
Fiddle : http://jsfiddle.net/vg3pc/
Grab spans. Remove everything. Put spans back (.joined as you choose).
<div class='container'>
<span>Item 1</span> | <span>Item 2</span>
</div>
.
var $c = $(".container");
var $spans = $c.find("span");
var htmlSpans = [];
$spans.each(function(){
htmlSpans.push($(this).prop("outerHTML"));
});
$c.empty();
$c.append(htmlSpans.join(" "));
http://jsfiddle.net/u59Uz/
EDIT:
Dirty hack using same logic to handle this very specific case. Regex replace handles 0 or more spaces before and after the pipe.
var $c = $(".container");
var html = $c.prop("outerHTML");
$c.empty();
$c.append(html.replace(/\s*\|\s*<span>Item 2/,"<span>Item 2" ));
http://jsfiddle.net/TnXB7/1/

Add class to first child using javascript

is there any reason this chain does not work? It does not add the class:
document.getElementsByTagName('nav')[0].firstChild.className = "current"
It should return the first child of the nav element which is an <a> which does not happen.
Thanks for your help!
That's because you have text nodes between nav and a. You can filter them by nodeType:
var childNodes = document.getElementsByTagName('nav')[0].childNodes;
for (var i = 0; i < childNodes.length; i++) {
if (childNodes[i].nodeType !== 3) { // nodeType 3 is a text node
childNodes[i].className = "current"; // <a>
break;
}
}
It may seem strange but, for example, if you have the following markup:
<nav>
<a>afsa</a>
</nav>
Here's a DEMO.
Why does this happen? Because some browsers may interpret the space between <nav> and <a> as an extra text node. Thus, firstChild will no longer work since it'll return the text node instead.
If you had the following markup, it'd work:
<nav><a>afsa</a></nav>
You can simply document.querySelectorAll to select the list.
use "firstElementChild" to get first child node and add class.
const firstChild = document.querySelectorAll('nav').firstElementChild;
firstChild.classList.add('current');
The statement:
document.getElementsByTagName('nav')[0].firstChild.className = "current"
is somewhat fragile as any change in the assumed document structure breaks your code. So more robust do do something like:
var links,
navs = document.getElementsByTagName('nav');
if (navs) links = nav[0].getElementsByTagName('a');
if (links) links[0].className = links[0].className + ' ' + 'current';
You should also have robust addClassName and removeClassName functions.
Jquery can make this very easy:
$("#nav:first-child").addClass("current");

How to change text inside span with jQuery, leaving other span contained nodes intact?

I have the following HTML snippet:
<span class="target">Change me <a class="changeme" href="#">now</a></span>
I'd like to change the text node (i.e. "Change me ") inside the span from jQuery, while leaving the nested <a> tag with all attributes etc. intact. My initial huch was to use .text(...) on the span node, but as it turns out this will replace the whole inner part with the passed textual content.
I solved this with first cloning the <a> tag, then setting the new text content of <span> (which will remove the original <a> tag), and finally appending the cloned <a> tag to my <span>. This works, but feels such an overkill for a simple task like this. Btw. I can't guarantee that there will be an initial text node inside the span - it might be empty, just like:
<span class="target"><a class="changeme" href="#">now</a></span>
I did a jsfiddle too. So, what would be the neat way to do this?
Try something like:
$('a.changeme').on('click', function() {
$(this).closest('.target').contents().not(this).eq(0).replaceWith('Do it again ');
});
demo: http://jsfiddle.net/eEMGz/
ref: http://api.jquery.com/contents/
Update:
I guess I read your question wrong, and you're trying to replace the text if it's already there and inject it otherwise. For this, try:
$('a.changeme').on('click', function() {
var
$tmp = $(this).closest('.target').contents().not(this).eq(0),
dia = document.createTextNode('Do it again ');
$tmp.length > 0 ? $tmp.replaceWith(dia) : $(dia).insertBefore(this);
});
​Demo: http://jsfiddle.net/eEMGz/3/
You can use .contents():
//set the new text to replace the old text
var newText = 'New Text';
//bind `click` event handler to the `.changeme` elements
$('.changeme').on('click', function () {
//iterate over the nodes in this `<span>` element
$.each($(this).parent().contents(), function () {
//if the type of this node is undefined then it's a text node and we want to replace it
if (typeof this.tagName == 'undefined') {
//to replace the node we can use `.replaceWith()`
$(this).replaceWith(newText);
}
});
});​
Here is a demo: http://jsfiddle.net/jasper/PURHA/1/
Some docs for ya:
.contents(): http://api.jquery.com/contents
.replaceWith(): http://api.jquery.com/replacewith
typeof: https://developer.mozilla.org/en/JavaScript/Reference/Operators/typeof
Update
var newText = 'New Text';
$('a').on('click', function () {
$.each($(this).parent().contents(), function () {
if (typeof this.tagName == 'undefined') {
//instead of replacing this node with the replacement string, just replace it with a blank string
$(this).replaceWith('');
}
});
//then add the replacement string to the `<span>` element regardless of it's initial state
$(this).parent().prepend(newText);
});​
Demo: http://jsfiddle.net/jasper/PURHA/2/
You can try this.
var $textNode, $parent;
$('.changeme').on('click', function(){
$parent = $(this).parent();
$textNode= $parent.contents().filter(function() {
return this.nodeType == 3;
});
if($textNode.length){
$textNode.replaceWith('Content changed')
}
else{
$parent.prepend('New content');
}
});
Working demo - http://jsfiddle.net/ShankarSangoli/yx5Ju/8/
You step out of jQuery because it doesn't help you to deal with text nodes. The following will remove the first child of every <span> element with class "target" if and only if it exists and is a text node.
Demo: http://jsfiddle.net/yx5Ju/11/
Code:
$('span.target').each(function() {
var firstChild = this.firstChild;
if (firstChild && firstChild.nodeType == 3) {
firstChild.data = "Do it again";
}
});
This is not a perfect example I guess, but you could use contents function.
console.log($("span.target").contents()[0].data);
You could wrap the text into a span ... but ...
try this.
http://jsfiddle.net/Y8tMk/
$(function(){
var txt = '';
$('.target').contents().each(function(){
if(this.nodeType==3){
this.textContent = 'done ';
}
});
});
You can change the native (non-jquery) data property of the object. Updated jsfiddle here: http://jsfiddle.net/elgreg/yx5Ju/2/
Something like:
$('a.changeme3').click(function(){
$('span.target3').contents().get(0).data = 'Do it again';
});
The contents() gets the innards and the get(0) gets us back to the original element and the .data is now a reference to the native js textnode. (I haven't tested this cross browser.)
This jsfiddle and answer are really just an expanded explanation of the answer to this question:
Change text-nodes text
$('a.changeme').click(function() {
var firstNode= $(this).parent().contents()[0];
if( firstNode.nodeType==3){
firstNode.nodeValue='New text';
}
})
EDIT: not sure what layout rules you need, update to test only first node, otherwise adapt as needed

Categories