I am modifying an elements innerHTML property inside a $.each() loop. Now if my stack of elements contains childrens of the element where I update the innerHTML, the DOM reference of that children will get lost.
Example:
$(function(){
$stack = $(".myelement, .myelement *");
$stack.each(function(){
var $this = $(this);
var element = $(this)[0];
console.log(element.innerHTML = element.innerHTML + " modified");
});
});
Fiddle: https://jsfiddle.net/b0ux0v5e/
What happens is that I first modify the innerHTML of .myelement. This includes the children p. Therefore the DOM reference of this element is get lost and "modified" will not be appended.
How can such an scenario be solved without building a function that creates a unique selector for the element and re-catches it in each loop?
Note: I am not asking for specifically appending some text to nodes. This is just an example. In a real project I am replacing text in the innerHTML.
I guess you want to modify the text content. in this case you would like to know about .contents() method, which lets you loop through with text nodes as well:
$(function() {
$(".myelement").contents().each(function(i, el) {
el.textContent += " 'modified'";
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="myelement">
I am the myelement
<p>
Hello world
</p></div>
Don't modify innerHTML, it will destroy/recreate all the elements inside and force a re-render. You can use insertAdjacentHTML or jQuery's append to add to an element.
Also you do not need to do:
element = $(this)[0];
this will already be a reference to the element.
$(function(){
$stack = $(".myelement, .myelement *");
$stack.each(function(){
var $this = $(this);
//this.insertAdjacentHTML("beforeend"," modified");
//Or
$this.append(" modified");
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="myelement">
<span>A span</span>
<div>A div</div>
<div>
<p>a paragraph in a div</p>
</div>
</div>
EDIT
As it seems you want to replace word(s) and not append a word, you could do a replace on the html and then reset it, but again this will destroy/recreate the elements inside, so if any of them have something like event listeners attached they will be lost
var modified = $".myelement").html().replace(/yourword/g,"modified");
$(".myelement").html(modified);
Or you could just loop over all the Text nodes and replace the word from there, this will keep the elements, their event listeners, and so on intact.
$(".myelement, .myelement *").contents().filter(function() {
return this.nodeType == 3;
}).each(function(){
this.textContent = this.textContent.replace(/A/g,"modified");
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="myelement">
Inside A element <br>
<span>A span</span>
<div>A div</div>
<div>
<p>a paragraph in A div</p>
</div>
</div>
just change
$stack = $(".myelement, .myelement *");
to
$stack = $(".myelement");
And you will see in your console
I am the myelement
<p>
Hello world
</p> modified
Related
I am looking for a way to get the entire HTML document, excluding a few items (possibly tagged with a className called 'exclude') as a string.
I know I can grab the entire document with document.documentElement.innerHTML
or document.documentElement.outerHTML
and document.getElementsByTagName('html')[0].innerHTML
What i am still still struggling with is how do I exclude some of the nodes (such as buttons or divs or any other tags, that have a common className, before I get the innerHTML?
I'd probably clone the whole tree, then remove the elements you don't want:
var clone = document.body.cloneNode(true);
clone.querySelectorAll(".exclude").forEach(function(element) {
element.parentNode.removeChild(element);
});
var html = clone.outerHTML;
Note that this assumes body, itself, doesn't have the exclude class.
Example:
var clone = document.body.cloneNode(true);
// Snippet-specific: Also remove the script
clone.querySelectorAll(".exclude, script").forEach(function(element) {
element.parentNode.removeChild(element);
});
var html = clone.outerHTML;
console.log(html);
<div>
I want this
<div>And this</div>
</div>
<div class="exclude">
I don't want this
<div>Or this, since its parent is excluded</div>
</div>
I know I'm late to the party but here is my contribution, I have used chŝdk's idea to implement it.
let markup = document.querySelectorAll('*:not(.exclude)')[0].innerHTML;
console.log("Data Type: " + typeof(markup));
console.log(markup);
<center>
<div>Hello World</div>
<div class="exclude">Hello World [Exclude Me]</div>
<div>Hello World</div>
<div>Hello World</div>
<div>Hello World</div>
<div class="exclude">Hello World [Exclude Me]</div>
<div class="exclude">Hello World [Exclude Me]</div>
<div>Hello World</div>
<div>Hello World</div>
<div class="exclude">Hello World [Exclude Me]</div>
</center>
Well you can use querySelector() along with the :not() css selector upon your HTML block, to exclude unwanted elements from it.
var content = document.getElementsByTagName('html')[0]
var selection = content.querySelectorAll('*:not(.ignore)');
Then just use outerHTML to get the whole content from your selection:
var htmlString = selection[0].outerHTML;
Otherwise you can loop over the selection elements and for each one append its HTML to your result string:
var htmlString = "";
selection.forEach(function(el) {
htmlString += el.innerHTML;
});
Demo:
var content = document.getElementsByTagName('html')[0]
var selection = content.querySelectorAll('*:not(.ignore)');
//Then log the selection content
console.log(selection[0].outerHTML);
//Or maybe loop throught the elements and get their contents
var htmlString = "";
selection.forEach(function(el) {
htmlString += el.innerHTML;
});
console.log(htmlString);
Note:
In this demo there were no elements with ignore class, but you can always put it and test.
You can see that this will also keep all HTML elements including
scripts and styles tags.
Why can't you append to an element that you use to wrap another - see example below?
var $test = $('.test'),
$test1 = $('.test1'),
$move = $('.move'),
$testWrapper = $('<div class="test-wrapper"></div>'),
$test1Wrapper = $('<div class="test1-wrapper"></div>');
$test.wrap($testWrapper);
// move item and return to wrapper
$move.append($test);
$testWrapper.append($test); // this is the line that does not work?
console.log($testWrapper); // still seems to be a jquery object?
$test1.after($test1Wrapper); // if you place the element after instead of using it to wrap, then it works?
$move.append($test1);
$test1Wrapper.append($test1);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="test">test</div>
<div class="test1">test 1</div>
<div class="move"></div>
wrap() seems to clone the markup of the provided element for wrapping, not the actual element itself. You can see it in the developer tools when you use console.log($testWrapper) and hover over that line in your browser console: normally, the DOM element should be highlighted, but it's not. So what is referenced by the variable $testWrapper after wrapping is still (a jQuery collection of) a node that is not attatched to the DOM.
var $test = $('.test'),
$test1 = $('.test1'),
$move = $('.move'),
$testWrapper = $('<div class="test-wrapper"></div>');
$test.wrap($testWrapper);
// move item and return to wrapper
$move.append($test);
console.log($testWrapper); // <= if you hover this element in the browser console, it doesn't highlight the actual DOM element either; that's how you can visully detect that it's not the same element!
$testWrapper = $('.test-wrapper'); // select the actual DOM element that has been created for wrapping
$testWrapper.append($test); // now it will work!
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="test">test</div>
<div class="move"></div>
I am using MediumEditor which a WYSIWYG editor using contenteditables. I need to assign different IDs to each element inside of the contentEditable, but (when you press Enter) the editor will clone a paragraph from an esiting one with all it's attributes. I am wondering if there is a way to identify the new <p> element from the one it was cloned from? The new element can be placed either before or after the existing one.
UPDATE:
Here's an example:
https://jsfiddle.net/wwoh7e62/
<div id="container">
<p id="myid" class="myclass" data-id="myid">some text</p>
</div>
<button onclick="doClone(); myFunc();">clone</button>
<script>
doClone = function() {
var container = document.getElementById('container');
var node = container.getElementsByTagName('p')[0].cloneNode(false);
node.innerHTML = 'cloned node';
container.appendChild(node);
}
myFunc = function () {
console.log('my func');
}
</script>
The code in doClone I don't have access to. My code should reside in the myFunc function.
While I was typing the fiddle I realized that the solution will probably be in attaching an event listener (which is not cloned) and the new node will be the one that does not have the event listener.
UPDATE:
ids that were assigned previously need to stay the same as thay are used to identify particular nodes.
You can try this :
Remove the id from "p"
<div id="container">
<p class="myclass" data-id="myid">some text</p>
</div>
<button onclick="doClone(); myFunc();">clone</button>
Then update them into your Func function :
var length = document.getElementById("container").getElementsByTagName("p").length;
for(var i=0; i < length; i++) {
document.getElementById("container").getElementsByTagName("p")[i].id = i;
}
Does this help ?
I am dynamically generating <p> tags inside of a <div contenteditable=true> but the event handler I have setup to catch the keyup events coming from the <p> tags is not catching them.
HTML
<!-- Non-dynamically generated paragraph -->
<div class="note">
<p contenteditable="true"></p>
</div>
<!-- Contains dynamically generated paragraphs -->
<div class="note" contenteditable="true"></div>
<!-- Debug output -->
<div id="output"></div>
JS
$(document).ready(function() {
$(".note").keyup(function(evt) {
$("#output").append("<br/>note tag keyup");
// Surround paragraphs with paragraph tag
$(this).contents().filter(function() {
return this.nodeType === 3;
}).wrap('<p class="test"></p>');
});
$(".note").on("keyup", "p", function(evt) {
$("#output").append("<br/>p tag keyup");
});
});
Fiddle: https://jsfiddle.net/mibacode/axgccwoq/3/
The first div element demonstrates that I can successfully catch the keyup event from a paragraph tag generated on load. The second div shows that my dynamically generated paragraphs are not firing (or JQuery just can't catch) the keyup event.
Edit:
The issue/bug appears to be with how the paragraph tags are being generated and added to the DOM in this portion of the code:
$(this).contents().filter(function() {
return this.nodeType === 3;
}).wrap('<p class="test"></p>');
I believe for some the <p> tag isn't being added properly so the DOM isn't recognizing it or doesn't know it exists.
Edit 2:
I replaced the jQuery functionality which inserts the new paragraph tags with vanilla JS in hopes that might solve my issue, however it did not.
New code:
var textNode = null;
var nodes = this.childNodes;
for (var i = 0; i < nodes.length; i++)
{
if (nodes[i].nodeType === 3)
{
textNode = nodes[i];
break;
}
}
if (textNode)
{
var p = document.createElement("P");
var attr = document.createAttribute("contenteditable");
attr.value = "true";
p.setAttributeNode(attr);
p.addEventListener("keyup", function() {
$("#output").append("<br/>Special paragraph click!");
});
p.innerHTML = textNode.nodeValue;
var parent = $(this)[0];
parent.insertBefore(p, textNode);
parent.removeChild(textNode);
}
It appears it has to do with the way that JS handles events fired from elements within contenteditable elements.
The only problem is that you don't have the contenteditable attribute on the dynamic <p> tags. Just because they are contained within a contenteditable element doesn't make them editable also - they need the attribute explicitly.
In my updated fiddle, I have simplified it just to show that the dynamic tags work.
Building this basic to-do list from scratch to try and teach myself Javascript. I found out through the API that there is a firstChild function that will target the first child of a parent node.
If I have..
<div class = "parentNode">
<div id = "i0">
TEXT HERE
</div>
<div id = "i1">
</div>
</div>
Then I have some button that is designated to the function:
document.getElementById('myButton').onclick = function () {
var parentNode = document.getElementById('parentNode');
var childNode = parentNode.firstChild.innerHTML;
alert('childNode');
}
Why would this not return TEXT HERE in the alert box?
There are a few things going on here. First, you are looking for an element that does not exist
var parentNode = document.getElementById('parentNode');
is looking for an id. This can be remedied by using an id="parentNode on the element, or you can query by class name instead using querySelectorMDN
var parentNode = document.querySelector('.parentNode');
Next, alert('childNode'); will always alert the string "childNode" and not the variable childNode so that needs to be alert(childNode).
Lastly, and perhaps most interesting, is that .firstChild will get the first childNode of the set of childNodes. This can be a #text node (which it is), becuase of the whitespace used between the end of the <div class = "parentNode"> and the beginning of <div id = "i0">.
As opposed to using .firstChild, you can use children[0] which will only look at elements. Here is a snippet that shows this behavior.
document.getElementById('myButton').onclick = function () {
var parentNode = document.querySelector('.parentNode');
var childNode = parentNode.children[0].innerHTML;
alert(childNode);
}
<button id="myButton" type="button">Click To Check Node</button>
<div class = "parentNode">
<div id = "i0">
TEXT HERE
</div>
<div id = "i1">
</div>
</div>