onclick attribute vs onclick in script - javascript

Why the difference?
document.getElementById('click').onclick = a.replaceChild(c,b) --- replaces the element without me clicking the button
but onclick="a.replaceChild(c,b)" --- replaces the element only after the button is clicked
<div id="container">
<p id="welcome">no greetings yet</p>
<p id="products">oreo ice-cream</p>
</div>
<button id="click" **onclick="a.replaceChild(c,b)"**>CHANGE</button>
<script>
var a = document.getElementById('container');
var b = document.getElementById('welcome');
var c = document.createElement('h2');
c.id = 'new';
c.innerHTML = "WELCOME";
**document.getElementById('click').onclick = a.replaceChild(c,b);**
</script>

"document.getElementById('click').onclick" is just returning the state onClick whenever that line runs. Probably at page load time.
What you want to do is add an event listener which will listen for a change in the .onclick state. Then call your function with that.
This is what the onClick attribute is doing. Its generally advisable to use an event listener so you cna clearly separate your content from your JS logic.

You don't include the parentheses: you'd assign a parameterless function like document.getElementById('foo')=bar;, not like bar().
The way you're assigning it now actually runs it, which means you're assigning the return value from that function, not its name.
The parameters make it slightly trickier than just naming the function without parentheses.
document.getElementById("click").onclick = function(c, b){return a.replacechild(c,b);}
This'll do what you're looking for. JSFiddle
jQuery's .click() is a good alternative if you're interested in solutions that aren't pure basic javascript. Another may be adding an event listener instead in some cases.

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.

passing values to a function does not work when inside for loop

I'm mapping currencies from a json file and i render the mapped currencies to a component. I have a .php file like this
<div class="currency-switch-container" id="currency_container">
<span style="font-size:12px;font-weight:bold">All currencies</span>
<div id="currency-map" style="margin-top:15px"></div>
</div>
I refer the div in the above component in my js file as follows
let currencyMap = jQuery("#currency-map");
And when my jQuery document is ready i'm doing the following
jQuery(document).ready(function($) {
$.getJSON('wp-content/themes/mundana/currency/currency.json', function(data) {
for(let c in data){
currencyMap.append(`<span onclick="onCurrencyClick(${data[c].abbreviation})"
class="currency-item">
<span>
${data[c].symbol}
</span>
<span>
${data[c].currency}
</span>
</span>`)
}
});
}
and my function is like this
function onCurrencyClick(val){
console.log("val",val);
setCookie("booking_currency", val, 14);
}
Here the function does not work. But if i do not pass anything to the function it seems to work as i can see the log in the terminal.
Hi your expression ${data[c].abbreviation} will put the value into function string without string quotes i.e. the resultant would be onCurrencyClick(abbreviation) while it should be onCurrencyClick('abbreviation').
please use onclick="onCurrencyClick('${data[c].abbreviation}')" instead.
Instead of using the inline onclick, use event delegation. This means that you have a single event listener that handles all the events from the children and grandchildren. The modification is a very minor one seeing the example here below.
A reason for doing this is that you keep your JavaScript inside your JS file. Like now, you encounter a JS error and have to look for it in your HTML. That can get very confusing. Also however inline onclick listeners are valid, they are outdated and should be avoided unless there is absolutely no other way. Compare it with using !important in CSS, same goes for that.
function onCurrencyClick(event){
var val = $(this).val();
setCookie("booking_currency", val, 14);
}
currencyMap.on('click', '.currency-item', onCurrencyClick);
This example takes the val that you try to insert from the value attribute from the clicked .current-item. <span> elements don't have such an attribute, but a <button> does and is a much more suitable element for it expects to be interacted with. It is generally a good practice to use clickable elements for purposes such as clicking.
In the example below you see the button being used and the abbreviation value being output in the value attribute of the <button> element and can be read from the onCurrencyClick function.
jQuery(document).ready(function($) {
$.getJSON('wp-content/themes/mundana/currency/currency.json', function(data) {
for(let c in data){
currencyMap.append(`
<button value="${data[c].abbreviation}" class="currency-item">
<span>
${data[c].symbol}
</span>
<span>
${data[c].currency}
</span>
</button>
`)
}
});
onclick will not work for a dynamically added div tag
Yo should follow jQuery on event
Refer: jQuery on
Stackoverflow Refer: Dynamic HTML Elements

JavaScript: Instead of passing "this" as an argument, how can I make function which will be called on it as an object?

I'm JavaScript beginner and am still grasping some concepts around it, so sorry if the question is dumb or it's already answered, but I didn't know what to search for, as the language terminology is still kinda foreign to me (both JavaScript and English). Currently I'm trying to master "this" keyword and trying to minimize JS code inside HTML file and move as much as possible to external files.
This is my question:
Let's say I want to change paragraph's value from Hello World! to foo bar by clicking on paragraph itself, just by using "this" keyword and some JavaScript.
I can do it in external file like:
<!--index.html-->
<p onclick="setParagraphText(this, 'foo bar')">Hello World!</p>
----------------------------------------------
//script.js
function setParagraphText(paragraph, value) {
return paragraph.innerHTML = value;
}
Or inline, inside tag:
<p onclick="this.innerHTML='foo bar'">Hello World!</p>
My question is: is it possible to do a combination of these 2 ways, so that the p value is not passed as an argument, but instead the function is invoked on it as an object (as a similar to 2nd example), but still keep the method of doing it in external file (like in 1st example)?
Something along the lines of this.function(value) instead of function(this, value)
<!--index.html-->
<p onclick="this.setParagraphText('foo bar')">Hello World!</p>
----------------------------------------------
//script.js
function setParagraphText(value) {
//something with innerHTML = value; or whatever will work
}
Thanks in advance!
You can add a data- attribute to your p tag which would store the data you want your text to change to (ie: "foo bar"), and then use addEventListener to add a click event-listener to paragraph tags which have this particular data- attribute. By doing this, you're handing over the javascript logic to the javascript file, and thus limiting the JS written within your HTML file.
See example below:
const clickableElements = document.querySelectorAll('[data-click]');
clickableElements.forEach(elem => {
elem.addEventListener('click', function() {
const value = this.dataset.click;
this.innerHTML = value;
});
});
<p data-click="foo bar">Hello World!</p>
<p data-click="baz">Hello Moon!</p>
First of all, if you want to be able to call your function on this, you have to know what the keyword this here represents. One simple way to do it is to console.log it.
So, this is the DOM element you have your inline javascript on ! Confirmed here: this in an inline event handler. Okay, if you want to have more information, console.log paragraph.constructor.
So, it's a HTMLParagraphElement. That's what you would get if you call this:
document.createElement("p");
So, if you want to be able to call this.setParagraphText, it comes down to calling setParagraphText on an HTMLParagraphElement object. But in order to do that, HTMLParagraphElement has to implement it and one way to do this, as subarachnid suggested, is to add the function to its prototype so that it is shared by all instances of it. If you think this would be useful, take a look at Web Components.
Here is a link: Extending native HTML elements.
Basically, you do it like this (and the cool thing here is the functionality, that is changing its content when clicking on it, will be encapsulated within the class):
<!DOCTYPE html>
<html>
<body>
<script>
// See https://html.spec.whatwg.org/multipage/indices.html#element-interfaces
// for the list of other DOM interfaces.
class CoolParagraph extends HTMLParagraphElement {
constructor() {
super();
this.addEventListener('click', e => this.setParagraphText('new value'));
}
setParagraphText(v) {
this.innerHTML = v;
}
}
customElements.define('cool-paragraph', CoolParagraph, {extends: 'p'});
</script>
<!-- This <p> is a cool paragraph. -->
<p is="cool-paragraph">Cool paragraph! Click on me and the content will change!</p>
</body>
</html>
So you don't even have to write inline Javascript anymore!
But if you want to have it your way and add your inline javascript, it's fine.
<!-- Note the this.setParagraphText(), now it works! -->
<p is="cool-paragraph" onclick="this.setParagraphText('foo bar')">Cool paragraph! Click on me and the content will change!</p>
<!DOCTYPE html>
<html>
<body>
<script>
// See https://html.spec.whatwg.org/multipage/indices.html#element-interfaces
// for the list of other DOM interfaces.
class CoolParagraph extends HTMLParagraphElement {
constructor() {
super();
//this.addEventListener('click', e => this.setParagraphText('new value'));
}
setParagraphText(v) {
this.innerHTML = v;
}
}
customElements.define('cool-paragraph', CoolParagraph, {extends: 'p'});
</script>
<!-- This <p> is a cool paragraph. -->
<p is="cool-paragraph" onclick="this.setParagraphText('foo bar')">Cool paragraph! Click on me and the content will change!</p>
</body>
</html>
I don't know if that answers your question, but that should hopefully points you in the right direction.
For your code to work you would have to enhance the HTMLElement prototype with your setParagraphText method (which is basically just a wrapper for this.innerHTML = value):
HTMLElement.prototype.setParagraphText = function(value) {
this.innerHTML = value;
};
Now something like this should work:
<p onclick="this.setParagraphText('foo bar')">Hello World!</p>
But I would strongly advise against modifiying native prototypes (older browsers like IE 9 don't even allow such a thing afaik).
Currently I'm trying to [...] minimize JS code inside HTML file and move as much as possible to external files.
Then how about having no JS code inside your HTML? This lets you kill two birds with one stone:
document.getElementById("clickme").addEventListener("click", function() {
this.innerHTML = "foo bar"
})
<p id="clickme">Hello world!</p>
The listener you add with addEventListener will be invoked with this being the element that the listener was added on.
Dunno if this is what you're after, but this in the function is referring to the function itself, but you can send in a scope with .call(this, arguments) which means that referring to this in the function is actually the scope of the HTML element.
I'm showing two ways of how you can handle this, with either a data attribute or sending in a new value as a parameter.
function setParagraphText(newValue) {
this.innerText = this.dataset.text + " + " + newValue;
}
<p data-text="foo bar" onclick="setParagraphText.call(this, 'foo yah')">Hello World!</p>
Also read: Javascript call() & apply() vs bind()?

Ondragstart Attribute doesn't appear when created dynamically

I have my images in an array and I have to create a div with an image inside dynamically and that image has to had the ondragstart attribute but I can't make it work. I used everything.
function showOptions(i,j){
var div= document.createElement("div");
var img = document.createElement("img");
var divOptions= document.getElementById('options');
div.id = "div"+i+j;
img.src = imgArray[i][j].src;
div.className="options";
img.draggable= true;
img.ondragstart="dragStart_handler(event)";
divOptions.appendChild(div);
div.appendChild(img);
And this is the html part:
<div id='options'> <!-- Here should be the divXX and the img --> </div>
I also used the methods addEventListener and attachEvent. But nothing seems to work. Can I write the div and the image to the HTML in another way?
This assignment is bad:
img.ondragstart="dragStart_handler(event)";
You're assigning a string to a property that expects a function.
This should work:
img.ondragstart = dragStart_handler;
This is better:
img.addEventListener("dragstart", dragStart_handler); // note: no "on"
I think you're confusing the convenience syntax where you write a little bit of JavaScript in HTML, like:
<img ondragstart="dragStart_handler(event);" />
I think you can also pass a string into functions like setTimeout:
setTimeout("dragStart_handle", 100);
This works because JavaScript expects a function and will eval the string argument.
I would avoid this sytnax.
img.setAttribute("ondragstart", "dragStart_handler(event)");
Let me know if that works for you. Works for me whilst img.ondragstart does not.

Listen for changes in DOM

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?

Categories

Resources