Changing a elements className makes it unfindable using $(className).each() - javascript

I have some JQuery code that converts all HTML elements of a specific class to & from textarea elements.
My Problem: I use JQuery(.addClass()) to change an elements class from "updatable" to "updatable P". But when I go to search for all the elements that have the class "updatable" (using the function $(".updatable").each()) it does'nt find any elements when it should find 3.
What am I doing wrong to make this happen? It seems that after I change an elements classI am unable to identify/find that element by its class(using JQuery) again.
<html>
<head>
<script type="text/javascript" src="jquery-1.6.4.js"></script>
<script type="text/javascript">
<!--
var STATE = 1;
function Toggle()
{
if (STATE==1) { convertToUpdatable(); STATE = 0; }
else { convertToStatic(); STATE = 1; }
}
function getTitleName( ele )
{
try { return ele.className.split(" ")[1]; }
catch (ex) { return "undefined"; }
}
function convertToUpdatable()
{
// Post: Convert all HTML elements (with the class 'updatable') to textarea HTML elements
// and store their HTML element type in the class attribute
// EG: Before: <p class="updatable Paragraph1"/> Hello this is some text 1 </p>
// After : <p class='updatableElementTitle'>Paragraph1</p><textarea class="updatable Paragraph1 p"/> Hello this is some text 1 </textarea>
$(".updatable").each(function()
{
var title = getTitleName( this );
$(this).replaceWith("<p class='updatableElementTitle'>"+title+"</p><textarea>"+$(this).text() +"</textarea>");
$(this).addClass( this.nodeName );
alert( this.className );
});
}
function convertToStatic()
{
// Post: Find all HTML elements (with the class 'updatable'), check to see if they are part of another class aswell
// (which will be their original HTML element type) & convert the element back to that original HTML element type
// PROBLEM OCCURS HERE: after I have changed an elements className in the convertToUpdatable() I can no
// longer find any HTML elements that have the className updatable using $(".updatable").each()
$(".updatable").each(function()
{
alert("Loop");
// Determine elements original HTML(node) type
try
{
var type = this.className.split(" ");
type = (type[ type.length-1 ]).toLowerCase();
alert(type);
}
catch (ex) { alert("Updatable element had no type defined in class attribute"); return; }
// Convert element back to original HTML type
$(this).replaceWith( type +$(this).text() + type );
// Remove elements type from its className (remove " P" from "updatable Paragraph1 P")
$(this).removeClass( type );
alert( this.className );
});
// Delete all elements with the class 'updatableElementTitle'
$(".updatableElementTitle").remove();
}
-->
</script>
</head>
<body>
<p class="updatable Paragraph1"/> Hello this is some text 1 </p>
<b class="updatable Paragraph2"/> Hello this is some text 2 </b>
<i class="updatable Paragraph3"/> Hello this is some text 3 </i>
<input id="MyButton" type="button" value="Click me!" onclick="Toggle();" />
</body>
</html>

.replaceWith() destroys the existing element. Thus, you can no longer use this after you've done the replaceWith() in the loop because that DOM element is no longer the element that's in your document (it's the old one that has been removed and will be garbage collected as soon as your function exits).
Since you're specifying the HTML for the new tag, I would suggest you just put the new class name in the HTML you pass to replaceWith(). Further, the alert you have to check the className is checking the old DOM element, not the new DOM element. Remember, this points to the old DOM name, not the one you replaced it with.
You could do so by changing this:
$(".updatable").each(function()
{
var title = getTitleName( this );
$(this).replaceWith("<p class='updatableElementTitle'>"+title+"</p><textarea>"+$(this).text() +"</textarea>");
$(this).addClass( this.nodeName );
alert( this.className );
});
}
to this:
$(".updatable").each(function()
{
var title = getTitleName( this );
$(this).replaceWith("<p class='updatableElementTitle " + this.nodeName + "'>" + title + "</p><textarea>"+$(this).text() +"</textarea>");
});
}
Although, I don't understand why you're adding the nodeName as a class?

You are using replaceWith which (according to the spec) completely removes all content and replaces it with the new content. After you've replaced the content there are no more nodes with the updatable class, so you won't ever find them again.
Try replacing $(this).addClass( this.nodeName ); with $(this).addClass('updatable');.

Related

How to detect HTML hidden Attribute with pure javascript

I'm working in the analytics field and most of the time I'm using CSS selector to solve my issue. Right now I'm working on a website were I need to detect if a <p> element is hidden or not. so here is the two possible situation :
<p namehere hidden>This paragraph should be hidden.</p>
<p namehere This paragraph should be hidden.</p>
What's the most simple way to return a boolean regarding the hidden status of the <p> element ?
Thanks !
Probably the quickest and simplest way to achieve this is the following
var elements = document.querySelectorAll('p:not([hidden])');
Fabio
You could catch all the p elements with getElementsByTagName function, iterate over them and check if specified element has hidden attribute different than null.
Note: If hidden attribute is null - element has no hidden attribute.
var elems = document.getElementsByTagName('p');
Array.from(elems).forEach(function(v,i) {
if (v.getAttribute('hidden') == null) {
console.log(v);
}
});
<p namehere hidden>This paragraph should be hidden.</p>
<p namehere>This paragraph should be hidden.</p>
You're looking for getAttribute. In this case, getAttribute would return null if there's no attribute or '' if there was one.
You can iterate over tags like so:
var paragraphs = document.getElementsByTagName("p");
for(var i = 0; i < paragraphs.length; i++){
if(paragraphs[i].getAttribute("hidden") !== null){
// The paragraph is hidden and can be accessed with paragraphs[i]
console.log(paragraphs[i].innerHTML + ' is hidden');
} else {
// The paragraph not is hidden and can be accessed with paragraphs[i]
console.log(paragraphs[i].innerHTML + ' is not hidden');
}
}
<p>Paragraph 1</p>
<p hidden>Paragraph 2</p>
<p>Paragraph 3</p>
Quite an old question, but... Methods described here don't seem to be
"(...) the most simple way to return a boolean regarding the hidden
status of the element (...)"
While getting all <p> element is necessary, using getAttribute() method is NOT. Instead, simply test the hidden attribute from the element object:
Just returning a boolean related to the hidden attribute: (Array.from() syntax)
Array.from(
document.getElementsByTagName("p"),
element => console.log(element.hidden)
);
<p namehere hidden>This paragraph should be hidden.</p>
<p namehere>This paragraph should be hidden.</p>
If you want to do an action depending on the boolean return, you should prefer this syntax:
//Using forEach() method instead of map() for better loop handling
Array
.from(document.getElementsByTagName('p'))
.forEach( element => {
if(element.hidden){
console.log("This element is hidden.");
}else{
console.log("This element is not hidden");
}
}
);
<p namehere hidden>This paragraph should be hidden.</p>
<p namehere>This paragraph should be hidden.</p>
Eventually, no getAttribute() method, no comparison operator, only boolean property check. Sounds more straightforward IMHO.
Ref : HTMLElement.hidden property (MDN)
If you are working with Jquery you can directly use hasClass function.
Jquery hasClass
If you are looking for pure javascript you can use this function
function hasClass(element, className) {
return new RegExp(' ' + className + ' ').test(' ' + element.className + ' ');}

How to get a class name based on div hover in jquery

I'm looking for a way to fetch the class name based on hovering on a div. Both div has same id but slightly different class name. take a look at the below example:
<div id="1-someid" class="1-example-class border cz">
...more element goes here....
</div>
and
<div id="2-someid" class="2-example-class border cz">
...more element goes here....
</div>
Update: I've made the id name unique based on expert's opinion posted below. :) Thanks for all the helps.
Now what I want is, when user hover on the div with 1-example-class it will return me the class name 1-example-class. and when people will hover on the div with 2-example-class it will return me 2-example-class name.
So that I can use parseInt() on that name to fetch the number, 1, 2, 3 and so on.
Also please note that writing a static script for just 1-example-class or 2-example-class will not help as there are many more divs like this with 3, 4, 5 and so on attached to it.
Can anyone help? I have tried the following but it didn't helped.
$('#someid').hover(function() {
var class_names = $(this).attr('class');
var class_name = class_names.split( ' ' );
var c = parseInt( class_name[0] );
console.log( c );
});
If anyone can help it will be really helpful.
Attribute selector and Regex is the way to go:
$("[class*=example-class]").hover(function() {
var c = this.className.match(/(\d+)-example-class/)[1];
console.log(c);
});
$("[class*=example-class]") matches all elements that their class
attribute includes 'example-class' string.
this.className.match(/(\d+)-example-class/)[1] gives the related
number.
Here is a way to do it based on your current configuration:
$('div').hover(function() {
// grab class attribute, split on space character (like you're doing already)
var class_names = $(this).attr('class').split( ' ' );
// loop through each class
$.each( class_names, function( k,v ){
// see if this 1 class matches your example
if ( v.indexOf("example-class") > 0 ){
// if it does, remove text part of class name
var this_num = v.replace( '-example-class', '' );
// output numeric value only to console
console.log( parseInt( this_num ) );
}
});
});
This method does not expect the same class configuration (meaning the classes in your class attribute can be in any order, so long as it does contain the example string). In your question, the code expects first class listed to be the example string class.
See this example: https://jsfiddle.net/31505tw1/
In the example, I have replaced your duplicate IDs into classes. As others have pointed out, HTML spec requires each ID to be unique.
There are several ways to do this- but other users are correct that your issue is in using the same ID multiple times, that's the only reason the code you already have doesn't work. If you use one of the other shared classes as your selector your original script will work:
$('.border').hover(function() {
var class_names = $(this).attr('class');
var class_name = class_names.split( ' ' );
var c = parseInt( class_name[0] );
console.log( c );
});
Firstly, you should use unique ID's on your div's: https://stackoverflow.com/a/9454716/984323
Not only for the HTML to be valid, but your jQuery script wouldn't be able to differentiate between the two. Then you can target each div and the rest of your code seems to work.
<div id="someid" class="1-example-class border cz">
...more element goes here....
</div>
<div id="someid2" class="2-example-class border cz">
...more element goes here....
</div>
https://jsfiddle.net/05r8f013/1
ID's must be unique all over html. Since you are using classname to get the data, you can add name field to the div:
<div name="someid" class="1-example-class border cz">
...more element goes here....
</div>
<div name="someid" class="2-example-class border cz">
...more element goes here....
</div>
$('[name="someid"]').hover(function() {
var class_names = $(this).attr('class');
var class_name = class_names.split( ' ' );
var c = parseInt( class_name[0] );
console.log( c );
});
Maybe you could for example:
$('div').hover(function(){
if ( $(this).attr('class') == 'some class' ) {
//Do something
} else {
//Do something else...
}
You could do a Regex on the hovered items that match on the pattern of the class name:
<div name="someid" class="1-example-class border cz">
...more element goes here....
</div>
<script>
$('#someid').hover(function() {
var className = this.className.match(/(\d)-example-class/);
if(className){
console.log(className[0]) // 1-example-classname
console.log(className[1]) // 1
}
});

Iterate over every element and get only the content that is directly in the node

let's assume I have this code
<p>FirstLevelP
<span>SecondLevelSpan</span>
</p>
<p>FirstLevelP
<span>SecondLevelSpan
<p>ThirdLevelP</p>
</span>
</p>
Is it possible to iterate through every element that I have right now, but only get the content, that's in the direct node of it, modify the text and then have it in the original content?
Example, If I go through every $('p').each and would extract the text I would also get the text inside the span.
Basically this:
FirstelElement: FirstLevelPSecondLevelSpan
SecondElement: SecondLevelSpanSecondLevelSpanThirdLevelP
But I want to have it like this
FirstelElement: FirstLevelP
SecondElement: SecondLevelSpan
ThirdElement: FirstLevelP
FourthElement: SecondLevelSpan
FifthElement: ThirdLevelP
Is this possible?
In my research I already found this answer here
$("#foo")
.clone() //clone the element
.children() //select all the children
.remove() //remove all the children
.end() //again go back to selected element
.text();
But this would only solve half of my problems. I would still need to modify the text in the original content! Thanks in advance guys.
EDIT FOR CLARIFICATION
So basically, want I want to achieve is something like this:
For every element, I want to check if there is a dot at the end. If not I want to add one. I already managed to do this for headlines, like this:
foreach (pq($content)->filter(':header') as $headline) {
if (substr(pq($headline)->text(), 0, -1) != '.') {
$content = preg_replace('#(' . pq($headline) . ')#', pq($headline) . '.', pq($content));
}
}
The problem, as I stated, is, that when I have nested elements it would add the dot after the whole element, and not after each sub element if neccessary.
To work with my "assumed" code, it should look like this
<p>FirstLevelP.
<span>SecondLevelSpan.</span>
</p>
<p>FirstLevelP.
<span>SecondLevelSpan.
<p>ThirdLevelP.</p>
</span>
</p>
But unfortunatley, it currently looks like this
<p>FirstLevelP
<span>SecondLevelSpan</span>.
</p>
<p>FirstLevelP
<span>SecondLevelSpan
<p>ThirdLevelP</p>
</span>.
</p>
Note the dots.
finding and changing text without child elements works this ways:
// search every element
$("body *").each(function(index, el) {
// find first text node
var node = $(el).contents().filter(function() {
return this.nodeType === 3;
})[0];
// change text
node.textContent = "new text";
});
Edit, Updated
Try
$("body *").each(function (i, el) {
if ($(el).is("p, span")) {
$(el).text(function (idx, text) {
var t = text.split("\n")[0];
// if `text` string's last character is not `.`
// concat `.` to `text` string ,
// return `text` original string's with `.` added
return t.slice(-1) !== "." ? t + "." : t
})
}
})
$("body *").each(function (i, el) {
if ($(el).is("p, span")) {
$(el).text(function (idx, text) {
var t = text.split("\n")[0];
return t.slice(-1) !== "." ? t + "." : t
})
}
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<p>FirstLevelP
<span>SecondLevelSpan</span>
</p>
<p>FirstLevelP
<span>SecondLevelSpan
<p>ThirdLevelP</p>
</span>
</p>

JS Change class of element

All I'm trying to do is toggle the class of an elements by ID when they are clicked on.
<script>
function selectLoad(id)
{
if(document.getElementById(id).className == "load") {
document.getElementById(id).className = "loadSelected";
} else {
document.getElementById(id).className = "load";
}
}
</script>
<div class="load" id=" ... set by PHP per element ... " onclick="selectLoad(this)">
This returns an error...
Uncaught TypeError: Cannot read property 'className' of null
So I guess it doesn't like the conditional.
I'm new to JS and need help, thanks in advance.
You are passing the dom element itself (using this), not the id.
You should change the code to
function selectLoad(element)
{
if (element.className == "load") {
element.className = "loadSelected";
} else {
element.className = "load";
}
}
I think you are passing an id that does not exist in the dom. Has the dom loaded before your javascript is executed? Move the script to the bottom of the page just before the closing html tag
EDIT: Following discussion in comments below the error is this line:
<div class="load" id=" ... set by PHP per element ... " onclick="selectLoad(this)">
it should read
<div class="load" id=" ... set by PHP per element ... " onclick="selectLoad(this.id)">
Your code is not safe because you don't check if you have the DOM element with the given id
That is why you get the error: Cannot read property 'className' of null
if(document.getElementById(id)) {
// ... do something and do not go further.
return;
}
And the problem is passing this when you call selectLoad. At this point, this is the DOM element, not the string that yo expect: ... set by PHP per element .... So you have to change the code accordingly.

CKEditor 4 (replace by class name) get text area value

I am trying to create a preview function in a CKEditor instance.
I'm using CKEditor 4 and am replacing textareas by class name, the only issue I am facing is that I cannot get the value of the textarea using JavaScript, this is what I've tried so far:
function prev()
{
html=document.forms["1_form"]["editor1"].value;
document.getElementById("prev").innerHTML = html;
}
and the CKEditor textarea:
<textarea class="ckeditor" onkeydown="prev()"></textarea>
Why doesn't this work? If I disable CKEditor, the script functions as expected, but not with ckeditor enabled. What am I supposed to do?
Thanks!
Edit:
I am now trying to do it with replace and this is the code I'm using:
CKEDITOR.replace('editor1'); //new ckeditor instance
var editor = CKEDITOR.instances.editor1; //reference to instance
//on `key` event
editor.on('key', function(){
var data = editor.getData(); //reference to ckeditor data
$('#prev').html(data); //update `div` html
});
it works but the only this is, it updates only after the next key is pressed so say the value of the editor is hello world it will show the preview as hello worl what can I do to this code to fix it, please ignore the first set of code.
This is because CKEDITOR replace your <textarea> into <div> with contenteditable="true" attribute and there is no value property in DOM for <div> element.
You need to add event listener to CKEDITOR instance, not to DOM element, for example:
// check if editor is ready
CKEDITOR.on( 'instanceCreated', function( e ) {
// check if DOM is ready
e.editor.on( 'contentDom', function() {
// bind "keydown" event to CKEDITOR
e.editor.document.on('keydown', function( event ) {
// write editor content into "prev" element
document.getElementById( "prev" ).innerHTML = e.editor.getData();
}
);
});
});
For anyone who needs it, here's one way to get the TEXT value of an HTML field (used in this case for a dynamic character counter):
JS ...
CKEDITOR.replace( 'myDoc', {
width: 605,
height: 500
});
setInterval("updateCount()", 500);
function updateCount() {
var str = CKEDITOR.instances.myDoc.getData();
if (str) {
var element = CKEDITOR.dom.element.createFromHtml( str );
var no_html = element.getText();
$('#myDoc_length').html( no_html.length );
}
}
... with HTML ...
<textarea id="myDoc"></textarea>
<span id="myDoc_length">0</span> characters

Categories

Resources