Listen for changes in DOM - javascript

I am working on a reflection of part of my website. This is the relevant HTML:
<div id = "original">
<img src = "picture.png" alt = "A picture" id = "picture">
<p id = "text">Some text</p>
</div>
<div id = "reflection"></div>
My Idea is copying the content of div#original to div#reflection like this:
<script>
function reflect()
{
var original = document.getElementById("original");
var reflection = document.getElementById("reflection");
reflection.innerHTML = original.innerHTML;
}
</script>
I am aware, that this will make the HTML invalid, in the whole project I iterate through the elements copied and set the IDs to not have that side effect. I just thought that this would be unnecessary to include here.
The problem I have is that the HTML I want to reflect is dynamic, so it may change. Then the reflection is wrong. I have searched for an event handler for this, but haven't found one. The only one near I found was onchange, but apparently this listens to changes in the attributes, not the child elements. Is there another one for this, or did I just use onchange wrong?
Thank you for your help!
GeF
I am aware that I could add onchange to every element, but this seemed not good style to me.

The simplest thing you can do is add a callback to reflect() every time you change the contents of the #original.
If this is not an option, you can look into a MutationObserver (documetation), as suggested here: Is there a JavaScript/jQuery DOM change listener?

Related

How to get variable, array, nodelist from different function?

I am trying to write a ToDoList with JavaScript.
I have an input-element. Whenever I type something and press enter, it creates a new fieldset(in my example its a fieldset but it can also be a Div) with the class name ".fieldListClass" and a P-Tag as a child of fieldset. the P-tag innerHTML is the the value of input. I used Click-EventListener for that.
After each click, I assigned the query selector of all .fieldListClass to a nodeList "fieldListQuery". I even converted this nodeList into an Array but no result.
Now I want to create an addEventListner but outside the previous one. it should be a new one. And It should be a click-EventListener for all fieldListQuery which where created inside the previous function.(this part is at the bottom of my code)
When I click on it something should happen like removing the current target etc. But it wont work because outside the function it always says that this variable is undefined. I don't get it because I declared it global outside of the function.
I don't want to use DOMNodeInserted or MutationObserver yet for detecting changes inside the DOM. Simple because the first one is not recommended anymore it and the last one I have no idea how to use it. Many people saying that this is not a safe way.
Any Help please?
let addDiv = document.createElement("div"); addDiv.id = "addDivId";
let listDiv = document.createElement("div"); listDiv.id = "listDivId";
let inputText = document.createElement("input"); inputText.id = "inputTextId";
let fieldList; // = document.createElement("fieldset");
let fieldDiv; // = document.createElement("div");
let fieldDivP; // = document.createElement("P");
let fieldListArr;
let fieldListQuery;
document.body.appendChild(addDiv);
addDiv.appendChild(inputText);
document.body.appendChild(listDiv);
inputText.addEventListener("keypress", event => {
if (event.key === "Enter") {
fieldList = document.createElement("fieldset");
fieldDiv = document.createElement("div");
fieldDivP = document.createElement("P");
listDiv.appendChild(fieldList);
fieldList.className = "fieldListClass";
fieldList.appendChild(fieldDiv);
fieldDiv.appendChild(fieldDivP);
fieldDivP.innerHTML = inputText.value;
fieldListQuery = document.querySelectorAll(".fieldListClass") ;
}
})
fieldListQuery.forEach(element => { // <- it say fieldListQuery is undefined.
fieldListQuery.addEventListener("click", e => {
e.currentTarget.innerHTML="test";
})
});
ยดยดยด
Since I offered critique of your approach, I thought it is only fair I at least try to offer you some code that accomplishes (on the overall level, in light of absence of much detail about your solution) something along of what you have.
First off, I think creating trees of elements through a script when other solutions are more viable, tends to show an anti-pattern. Your script is invariably loaded in the context of an HTML document, which may already contain a lot of useful markup -- including an input field (that you were creating with createElement). If the input field is a "constant" there is no need to waste code on creating it -- just put it in your markup.
Second, even for elements or hierarchies of elements that are created "on demand" -- as a reaction to an event or however else -- it typically is much more readable and manageable to use templates. As a fallback -- if template cannot be used for some reason -- using innerHTML to create entire element trees is actually an appealing and more readable option than a lot of "boilerplate" containing createElement, appendChild, etc.
Third, you should always try to see if you can have your interactive controls be part of a form. I won't go into all reasons to do so, but suffice to say it helps user agents that screen-read content and for other accessibility systems, to name one. There are exceptions to this rule, but I don't recall looking at code where a control should not be part of a form -- so the rule is a good one.
Here is a proof-of-concept bare-bones to-do application:
<html>
<head>
<script>
function submit_create_todo_item_form() {
const new_todo_fragment = document.getElementById("todo-item-template").content.cloneNode(true);
new_todo_fragment.querySelector(".body").textContent = document.forms[0].elements[0].value;
document.body.appendChild(new_todo_fragment);
}
</script>
<template id="todo-item-template">
<div class="todo-item">
<p class="body"></p>
</div>
</template>
</head>
<body>
<form action="javascript: submit_create_todo_item_form()">
<input>
</form>
</body>
<html>
Take note that I use textContent instead of innerHTML to create content for a to-do item's body. innerHTML invokes the HTML parser and unless you plan to be typing hypertext into that single line of input field, innerHTML only costs you extra for no clear benefit. If you need to interpret the value verbatim, textContent is instead exactly what's needed. So, approach your solution with that in mind.
I hope this is useful, I worked with what I thought I had.

How to add onClick on the component

I am using https://github.com/winhtaikaung/react-tiny-link for displaying some posts from other blogs. I am able to get the previews correctly. I want to capture the views count through onClick() but this module(react-tiny-link) doesn't seems to support the same, please help.
<ReactTinyLink
cardSize="large"
showGraphic={true}
maxLine={0}
minLine={0}
header={""}
description={""}
url={url}
onClick={() => this.handleViewCount(id)} />
I tried adding div around the component but it affects the css.
You can wrap your link with a div and attach your onClick callback on that div instead.
<div onClick={() => this.handleViewCount(id)}>
<ReactTinyLink
cardSize="large"
showGraphic={true}
maxLine={0}
minLine={0}
header={""}
description={""}
url={url}
/>
</div>
Your library - ReactTinyLink does not support onClick attribute.
Since you've tagged javascript - I can give you a small JS hack for the same.
Run the following code at the end of your React Rendering
var cards = document.getElementsByClassName('react_tinylink_card');
linksClicked = [];
for(var i = 0; i<cards.length; i++){
cards[i].onclick = function(){
linksClicked.push(this.href);
}
}
The above code will go through each and every cards and will attach onClick handlers on them, once clicked - the 'this' object will be your anchor tag's element, so I am storing it's href. (you're free to store anything you want)
In the following example - https://winhtaikaung.github.io/react-tiny-link/
I tried the same snippet - and got the following result
Hope this would be a good starting point for what you're trying to achieve.

State of element before and after editing (using contentEditable)

I would like to capture how the elements within a div change after a user edits it (as the content is contentEditable), and so have a page like the following:
before_html = $("#example_div").children();
$("#differences_button").on("click",function(){
after_html = $("#example_div").children();
console.dir(before_html[0].innerText);
console.dir(after_html[0].innerText);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="example_div" contentEditable><span id='example_span'>How it starts</span></div>
<button type="button" id="differences_button">Show differences</button>
However, as the console.dirs show, the "before_html" doesn't store the original structure of the element, but will show you the latest structure of it when running it again.
Is there a way to store the structure before the sort of changes shown in this example?
I've tried JSON.parse(JSON.stringify(before_html)) to store something that won't update, which often works when trying to store a javascript variable you don't want later update, but this fails to store the content when applied here.
The problem is that you are accessing before_html[0].innerText and after_html[0].innerText after the click. So both of them are evaluated after all changes are made.
Instead, you can save before_html (prior to attaching the event handler), and have it contain the innerHtml or innerText, and then compare with the new value during the click handler.
before_text = $("#example_div").text();
$("#differences_button").on("click",function(){
after_text = $("#example_div").text();
console.dir(before_text);
console.dir(after_text);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="example_div" contentEditable><span id='example_span'>How it starts</span></div>
<button type="button" id="differences_button">Show differences</button>
Note that I have changed the variable name from before_html to before_text as it doesn't contain HTML. You can have it contain HTML by calling before_text = $("#example_div").html(); if you prefer that.

Duplicate div with script/style tags inside using JS

I have the following html code:
<table><tbody><tr><td>
<div id="div_1">
<style>...</style>
<div><label> 1 </label></div>
<div><input type="text" name="text_1"/></div>
<script>$("#text_1").mask("99/99/9999");</script>
<div><label><a onclick="javascript:insert_div()"> </a></label></div>
...
</div>
...
<div id="div_20">
<style>...</style>
<div><label> 1 </label></div>
<div><input type="text" name="text_20"/></div>
<script>$("#text_20").mask("99/99/9999");</script>
<div><label><a onclick="javascript:insert_div()"> </a></label></div>
...
</div>
</td></tr></tbody></table>
That generates this (from 1 to 20, actually):
What I need is to insert a whole new div when the user presses the arrow button. It should copy the div with scripts and styles and insert after them with a new number (e.g. 21, then 22, etc.).
This is purely an instructional example of an alternate way of doing this task. It is intentionally wordy to provide ideas.
Suggestion: Avoid attribute-based event handlers when using jQuery:
To clarify my first comment. If you use onclick=javascript handlers, you are placing the registration of the event in the HTML, separate to the actual handler in the script. The "jQuery way" is to apply the handler function to a selection of elements, using methods like .click() and the rather useful .on() which I use below. This makes maintaining pages easier as you are not hunting through the HTML for JavaScript snippets. jQuery event handlers also support having more than one handler, for the same event, attached to an element which you simply cannot do with onclick=.
Concepts shown:
Use a global counter for the next id number and simply increment it after each use
Use a delegated event handler to process the "add" clicks as the elements are added dynamically (so do not exist until later).
Use a template stored in a dummy <script> block to hold your template HTML (this text/template type is unknown so is ignored by all browsers. It also makes maintenance a breeze.
Replace placeholder markers in the template with the new id information
Convert the HTML to DOM elements using $(html)
Find descendants in the new row to add things like mask.
Append the new row
JSFiddle: http://jsfiddle.net/TrueBlueAussie/Lu0q0na2/2/
// Declare a global counter for our new IDs
id = 2;
// Listen for click events at a non-changing ancestor element (delegated event handler)
$(document).on('click', '.addnew', function(e){
// get the HTML of the template from the dummy script block
var template = $('#template').html();
// Change the template names etc based on the new id
template = template.replace('{name}', 'name' + id).replace('{id}', id);
// Increase next id to use
id++;
// Convert the HTML into a DOM tree (so we can search it easily)
var $template= $(template);
// Apply the mask to the newly added input - alter this to suit
$template.find('input').mask("99/99/9999");
// Append the new row
$('table').append($template);
// stop the link from moving to page top
return false;
});
I will be happy to explain any part of this if you have questions. I realise it may be a bit of a shock compared to the existing way of doing it you have :)
I give you the basic idea: the rest if left as an exercise as teachers say:
<script type="text/javascript">
var last_inserted = 0;
function insert_div(){
$d = $("#div_" + last_inserted).clone();
$d.attr('id', 'div_' + last_inserted++);
$("table").append($d);
}
</script>
And something else: <a onclick="javascript:insert_div()"> is probably not correct (untested).
Either: <a onclick="insert_div()"> or <a href="javascript:insert_div()">

How to change the source of a child element with JavaScript?

I'm looking to change the source attribute of an image element when my parent element is clicked. The child/image element is actually nested within two div's which complicates things slightly and I have a suspicion this is where i'm going wrong in my code.
The JavaScript is as follows:
<script src="js/jquery-1.7.2.min.js"></script>
<script>
$(document).ready(function () {
$('.slideContent').hide();
$('.slideHeader').toggle(
function () {
$(this).next('.slideContent').slideDown();
$(this).children('.dropDownIcon').children('img').src = "./images/dropDownIconUp.png";
},
function () {
$(this).next('.slideContent').fadeOut();
$(this).children('.dropDownIcon').children('img').src = "./images/dropDownIconDown.png";
}
); // end toggle
}); // end ready
And the HTML is:
<div class="slideHeader">
<div class="dropDownIcon">
<img class="dropDownClass" src="./images/dropDownIconDown.png"/>
</div>
<div>
<p>2014</p>
</div>
</div>
<div class="slideContent">
<div>
<p>2014</p>
</div>
</div>
The slideDown and fadeOut functions from jQuery work fine. However the image change does not happen. So i'm confident my eror is within the following code:
$(this).children('.dropDownIcon').children('img').src = "./images/dropDownIconUp.png";
But as far as I can see, I select the correct elements in my chain. If anyone can shine any light on this it would be much appreciated. As everything else on the internet verifys the code above should work (Whilst a be little messy).
Thanks in advance.
src is an attribute of the img tag, so use .attr()
Try,
$(this).children('.dropDownIcon').children('img').attr('src', "./images/dropDownIconUp.png");
instead of using children selector twice use $(this).find('img').attr('src','./images/dropDownIconUp.png')
I know you have accepted the answer but let me tell you some points in brief
.src = "blah" would not work for jquery objects.$() always returns a jquery object and you must use .attr() or .prop() to work with jquery objects
However jquery provides a simple way to convert to javascript object
if you still want to use as a javascript object you could it this way
$(".slideHeader").children('.dropDownIcon').children('img')[0].src = "./images/dropDownIconDown.png";

Categories

Resources