Could anyone please help me sort out a bug in my beginner code? I am trying to add an list item to a list and trying to change the id of what list i'm adding it to in javascript. Thanks in advance.
<html>
<head>
<script>
window.onload = init;
function init() {
var button = document.getElementById("submit");
button.onclick = changeDiv;
}
function changeDiv() {
var counter=1
var name = "ul";
var textInput = document.getElementById("textInput");
var userInput = textInput.value;
alert("adding " + userInput);
var li = document.createElement("li");
li.innerHTML = userInput;
var ul = document.getElementById("ul" + loop());
ul.appendChild(li);
}
function loop() {
return counter;
if (counter==3){
counter==0;
}
counter++;
}
</script>
</head>
<body>
<form id="form">
<input id="textInput" type="text" placeholder="input text here">
<input id="submit" type="button">
</form>
<ul id="ul1">
</ul>
<ul id="ul2">
</ul>
<ul id="ul3">
</ul>
</body>
i think you want is one of these:
Give the scope of counter to be global (yuk)
create a closure around everything and declare counter there
you could pass counter into loop() when you call it.
define loop() in changeDiv().
I think you want #2 though so I fiddled it with several corrections in your code:
fiddle
The reason that I went with #2 is:
that a closure allows your logic to gain application to the resources it needs
but protect the scope at which other applications might be running (now or in the future) from being affected by any changes your application might attempt to that scope. For example, if you declared the counter as a global then all other javascript would potentially have read/write access to it which could negatively affect your demonstrated code, the other code, or both
keeps your current beginner code as unchanged as possible
gets you programming with an extremely important aspect of javascript that will help you today and in future as you learn
Answer #4 is similar in that it would create a closure for both changeDiv and loop whereby they both have access to what they need. However, I didn't want to change your existing logical blocks too much to stall incremental learning. But one could definitely make an argument for the loop() (which isn't really a loop but rather a setter) being enclosed in changeDiv() -- albeit you would likely remove the separate function call at that point and integrate the code more.
Essentially, you need to:
declare counter in a global scope (loop() cannot access it otherwise)
in loop(), the return statement must be the LAST thing. Anything after it won't get executed.
I altered the logic a bit and the final code is this:
window.onload = init;
var counter=1;
function init(){
var button = document.getElementById("submit");
button.onclick = changeDiv;
}
function changeDiv(){
var name = "ul";
var textInput = document.getElementById("textInput");
var userInput = textInput.value;
var id = "ul" + loop();
alert("adding " + userInput + " to " + id);
var li = document.createElement("li");
li.innerHTML = userInput;
var ul = document.getElementById(id);
ul.appendChild(li);
}
function loop(){
var tmp = counter++;
if (counter==4){
counter=1;
}
return tmp;
}
Note the changes in the loop() function.
I also altered the changeDiv() function to display the list ID in the alert.
Related
I have defined a function plusOne() which is supposed to add +1 to a variable (and then set the innerHTML of an element to be that variable). The function is called when a button is clicked.
However, the adding of +1 only works once. How do I have to change my function in order to add +1 to the variable every time the button is clicked?
Here's how my function and HTML are looking right now, along with a JSFiddle:
function plusOne() {
var number = 1;
var count = document.getElementById('count');
number++;
count.innerHTML = number;
}
<div>
<span id="count">1</span>
</div>
<div>
<button onclick="plusOne()">
+
</button>
</div>
Link to the JSFiddle: https://jsfiddle.net/johschmoll/hLymhak7/1/
Minimal solution: Move the state variable outside of the click handler's scope
Change your JavaScript to put the number variable outside of the click handler. Otherwise, you are resetting the number variable to 1 everytime the click handler is called.
var number = 1;
function plusOne() {
var count = document.getElementById('count');
number++;
count.textContent = number.toString();
}
Example: https://jsfiddle.net/hLymhak7/6/
Move the element reference outside the click handler's scope
It is also a good idea to keep the element reference outside of the click handler's scope if the element is never destroyed.
var number = 1;
var count = document.getElementById('count');
function plusOne() {
number++;
count.textContent = number.toString();
}
DOM query lookups are cheap nowadays, but a lot of them will negatively affect your app's performance.
Example: https://jsfiddle.net/hLymhak7/8/
Make the element dependency explicit
We can even pass the count element to the click handler to make it easier to test.
JavaScript
var number = 1;
function plusOne(count) {
number++;
count.textContent = number.toString();
}
HTML
<div>
<span id="count">1</span>
</div>
<div>
<button onclick="plusOne(count)">
+
</button>
</div>
The span element is actually assigned to a global variable which is within the scope of the button element just like the plusOne click handler. This means that in all examples, we could just as easily have used count or window.count to access the span element.
Example: https://jsfiddle.net/hLymhak7/12/
Best practice: Add as event listener
It is not recommended to bind the click handler by using the onclick attribute of the button element. One of the reasons is that we are only ever allowed to add one onclick handler, which is not the case with Element#addEventListener.
HTML
<div>
<span id="count">1</span>
</div>
<div>
<button id="incrementor">
+
</button>
</div>
JavaScript
var number = 1;
var count = document.getElementById('count');
var incrementor = document.getElementById('incrementor');
incrementor.addEventListener('click', plusOne);
function plusOne() {
number++;
count.textContent = number.toString();
}
See more discussions about onclick
Example: https://jsfiddle.net/hLymhak7/13/
Combine best practice with explicit element dependency
We can add a click listener that also passes the count element explicitly to the plusOne function.
var number = 1;
var count = document.getElementById('count');
var incrementor = document.getElementById('incrementor');
incrementor.addEventListener('click', function onClick() {
plusOne(count);
});
function plusOne(count) {
number++;
count.textContent = number.toString();
}
Now we are one step closer to maintainable code that is easily tested.
Example: https://jsfiddle.net/hLymhak7/14/
Final solution that is maintainable and easily tested
We can complete our solution by making the second dependency explicit, namely the number state variable.
When we pass this variable to the plusOne function, we now have a pure function which makes it easy to test and reason about.
HTML
<div>
<span id="count">1</span>
</div>
<div>
<button id="incrementor">
+
</button>
</div>
JavaScript
var number = 1;
var count = document.getElementById('count');
var incrementor = document.getElementById('incrementor');
incrementor.addEventListener('click', function onClick() {
number = plusOne(count, number);
});
function plusOne(count, number) {
number++;
count.textContent = number.toString();
return number;
}
While this is more verbose, the dependendencies are clear and the actual business logic, i.e. the plusOne function, can be extracted to a separate file and unit tested to verify that it does what it is supposed to.
Test suite
import { plusOne } from './plus-one';
describe('plusOne', () => {
let countElement;
let initialState;
let state;
beforeEach(() => {
initialState = 1;
state = initialState;
countElement = {
textContent: initialState.toString(),
};
})
it('returns an incremented state', () => {
state = plusOne(countElement, state);
expect(state).toBe(initialState + 1);
});
it('does not mutate the state', () => {
plusOne(countElement, state);
expect(state).toBe(initialState);
})
it('reflects the state in the count element', () => {
state = plusOne(countElement, state);
expect(countElement.textContent).toEqual(state.toString());
});
});
Example: https://jsfiddle.net/hLymhak7/15/
Test suite example: https://stackblitz.com/edit/stack-overflow-javascript-jquery-repeatedly-add-1-to-variable-o?file=src%2Fplus-one.spec.ts
Anti-pattern: Keep state in DOM
A lot of web apps keep the state in the DOM. While this is easy and we have less mutable state in our code, usually we want access to the state in multiple places of our apps.
Having to extract the state from the DOM in all places where we need it is not how it is supposed to be. We are supposed to keep our business logic in JavaScript and let the DOM reflect the state, not the other way around.
It also adds to a tight coupling to the DOM, making it more difficult to maintain and test.
// Keeping state in DOM is NOT recommended, but here we go...
var count = document.getElementById('count');
function plusOne() {
var number = Number(count.textContent);
number++;
count.textContent = number.toString();
}
Example: https://jsfiddle.net/hLymhak7/9/
<!-- Using inline js-->
<div>
<span id="count">1</span>
</div>
<div>
<button onclick="document.getElementById('count').innerHTML++">
+
</button>
</div>
function plusOne(){
var count = document.getElementById('count');
count.innerHTML++
}
<div>
<span id="count">1</span>
</div>
<div>
<button onclick="plusOne()">
+
</button>
</div>
Here's the code...
https://jsfiddle.net/6n2k65zs/
Try add a new item, you'll see its not working for some reason but it should be...
I can't spot any errors in the code, can someone help me out please?
And does anyone know any good debuggers? debugging JS is a nightmare!
Thanks.
<!DOCTYPE html>
<html>
<head>
<title>JavaScript To-Do List</title>
<link href="css/style.css" rel="stylesheet">
</head>
<body>
<input id="input" type="text">
<button id="btn">Add</button>
<hr>
<ul id="todo">
</ul>
<ul id="done">
</ul>
<!-- javascript anonymous self-invoking function -->
<!-- Function expressions will execute automatically -->
<script>
// from outside the action you wont be able to access the variables
// prevents another variable with a same name from conflicting
(function(){
var input = document.getElementById('input');
var btn = document.getElementById('btn');
// Object for the lists
// the reason im using ID is because ID can only be named once rather than a class which can be named 100's of times
var lists = {
todo:document.getElementById('todo'),
done:document.getElementById('done')
};
/* Parameter is string
create a list element which is stored in 'el' and returns it
*/
var makeTaskHtml = function(str, onCheck) {
var el = document.createElement('li');
var checkbox = document.createElement('input');
var label = document.createElement('span');
label.textContent = str;
checkbox.type = 'checkbox';
checkbox.addEventListener('click', onCheck);
// el.textContent = str;
// can use this method to move an element from one element to another
el.appendChild(checkbox);
el.appendChild(label);
// Text content is grabbing the text from the text box and storing it in variable el.
return el;
};
var addTask = function(task) {
lists.todo.appendChild(task);
};
var onCheck = function(event){
var task = event.target.parentElement; //targets the item clicked
var list = task.parentElement.id;
//lists.done.appendChild(task);
//swaps the 2 objects around
lists[list === 'done' ? 'todo' : 'done'].appendChild(task);
this.checked = false;
input.focus();
};
var onInput = function() {
var str = input.value.trim; // trim removes white space...
if (str.length > 0) {
addTask(makeTaskHtml(str, onCheck));
input.value = '';
input.focus();
}
};
btn.addEventListener('click', onInput);
input.addEventListener('keyup', function(event){
var code = event.keyCode;
console.log(code);
if (code === 13) {
onInput();
}
});
input.focus();
addTask(lists.todo, makeTaskHtml('Test done', onCheck));
}());
</script>
</body>
</html>
It appears to me you are not calling trim as a method, but accessing it as a variable?
Try add the () in trim:
var onInput = function() {
var str = input.value.trim(); // trim removes white space...
Your addTask function is being called with 3 parameters:
addTask(lists.todo, makeTaskHtml('Test done', onCheck));
but the function definition for addTask only takes one parameter:
var addTask = function(task)
so you need to just call addTask with just makeTaskHtml parameter, and not lists.todo which is already referenced inside the addTask function or onCheck
Or for debugging in Chrome, try Cmd-Alt–I in (Mac) or Ctrl-Alt-I (Windows).
First of all, you shouldn't put your scripts inline in JSFiddle – put them in the JS box to protect everyone's sanity! It's what it's made for...
There are other issues in the code, but the main issue seems to be in this line:
var str = input.value.trim;
Here, you're assigning str to the JS function trim. You want to assign it the the results of trim(), so try:
var str = input.value.trim();
You're still getting other errors in the console, but the basics seem to work.
I just started learning JavaScript.
I'm trying to change the text on a click.What's wrong with this code.
Please let me know.
Thanks
<!DOCTYPE html>
<html>
<body>
<script>
function change_text(id)
{
var arr = new Array("Now Click Again",""oops! Once more!","I'm Leaving!","Good Bye!");
var x = Document.getElementById("heading");
for(var i=0;i<arr.length;i++)
x.innerHTML=arr[i];
x.style.visibility="hidden";
}
</script>
<h1 onclick="change_text()" id="heading">Click on this text!</h1>
</body>
</html>
For one thing, you have two quotes here:
""oops! Once more!"
should be:
"oops! Once more!"
Document needs to be all lower-case (document).
change_text(id) is never used, and
x.style.visibility="hidden" needs to be moved outside the for loop.
You don't need a for loop at all, though, you just need to increment i++ every time the method is called, otherwise it will skip straight to "Goodbye".
<script>
var i = 0;
function change_text()
{
var arr = new Array("Now Click Again","oops! Once more!","I'm Leaving!","Good Bye!");
var x = document.getElementById("heading");
i++;
x.innerHTML=arr[i];
if(i >= arr.length) {
x.style.visibility="hidden";
}
}
</script>
EDIT:
EDIT2: OK, you got the idea before!
instead of "heading"
var x = Document.getElementById("heading");
you need to put the passed in value, id.
var x = Document.getElementById(id);
before this, you never actually use that id.
Also remember to pass that id in,
h1 onclick="change_text('heading')"
Change this line;
var arr = new Array("Now Click Again",""oops! Once more!","I'm Leaving!","Good Bye!");
and this line
var x = Document.getElementById("heading");
to this;
var arr = new Array('Now Click Again','oops! Once more!','I\'m Leaving!','Good Bye!');
var x = document.getElementById(id);
First, i don't know if it is a typo in your question but you have one extra " in your string array, this is how it should look.
var arr = new Array("Now Click Again","oops! Once more!","I'm Leaving!","Good Bye!");
And I don't know what you are trying to do with your for loop but the text you are showing will always be the last one in your array. Could you clarify what your are trying to achieve with the text in the h1 element?
Last of all, if you hide your element clearly your text won't show up, you should remove this line unless there is something I didn't understand correctly :
x.style.visibility="hidden";
Oh, and I think that your script element should have a type="text/javascript"
<script type="text/javascript">...
Unrelated to the question but if you can't spot the strings mistake in your array maybe you should change IDE. Even Notepad++ colors the strings, it might have helped you figure this one out.
I have been strugling with this for a while and I am sure there is a simple answer to this. What happens is I remove a div called "payment" then dynamicaly create it again so I can add to it. That then gets repeated as the infomation that needs to be added to it changes.
I have mangaged to get this so far.
function clearPage()
{
var d = document.getElementById("contain");
var d_nested = document.getElementById("payment");
var deleteNode = d.removeChild(d_nested);
}
function createPayment()
{
payment = document.createElement("div");
payment.id = "mine";
document.getElementById("contain").appendChild(payment);
}
function printOnPage()
{
var x = names.length;
for( var i = 0 ; i < x ; i++ )
{
var para = document.createElement("p");
var paymentDiv = document.getElementById("payment");
paymentDiv.appendChild(para);
var txtName = document.createTextNode("Item: ");
para.appendChild(txtName);
var txtNameArray = document.createTextNode(names[i]);
para.appendChild(txtNameArray);
var txtQty = document.createTextNode(" Qty: ");
para.appendChild(txtQty);
var txtQtyArray = document.createTextNode(qty[i]);
para.appendChild(txtQtyArray);
var txtCost = document.createTextNode(" Cost: ");
para.appendChild(txtCost);
var txtCostArray = document.createTextNode(prices[i]);
para.appendChild(txtCostArray);
}
}
Related HTML
<div id="contain">
<p>Payment</p>
<div id="payment">
<br />
</div>
</div>
It needs the ID of payment for both my CSS rules and for my creating the text that goes in it.
This is the error I get in FireFox
Error: paymentDiv is null Source File:
http://itsuite.it.brighton.ac.uk/ks339/sem2/javascript/js.js Line: 76
Hope someone can provide some insight in to this and please tell me if I am completly off!
Thanks
Edit: Is it easior to clear the div rather than delete it, how would I go about doing such a thing?
In create_payment(), you set the ID to 'mine'. Shouldn't it be 'payment'?
I do not understand your requirements very well, but anyway you cannot create multiple items in the page using the same id attribute, if you want to duplicate an item and still have control over it, you should be using class instead.
Try switching your code into jquery it will be cleaner and easier to understand for you & me.
Your problem is the fact that in createPayment() you're setting the id to 'mine':
payment.id = "mine";
while later on in printOnPage() you're looking for the element using id 'payment':
var paymentDiv = document.getElementById("payment");
As you mention in your edit, it is far easier just to clear the div than to remove it, specially if you still need it later.
To clear a DIV-block just set it's content to empty:
document.getElementById('payment').innerHTML = "";
I hope you find a solution! Good luck!
I have very simple html page with js code:
<html>
<head>
<title></title>
</head>
<body>
<div id="divButtons">
</div>
<script type="text/javascript">
var arrOptions = new Array();
for (var i = 0; i < 10; i++) {
arrOptions[i] = "option" + i;
}
for (var i = 0; i < arrOptions.length; i++) {
var btnShow = document.createElement("input");
btnShow.setAttribute("type", "button");
btnShow.value = "Show Me Option";
var optionPar = arrOptions[i];
btnShow.onclick = function() {
showParam(optionPar);
}
document.getElementById('divButtons').appendChild(btnShow);
}
function showParam(value) {
alert(value);
}
</script>
</body>
</html>
That page binds 10 buttons, but when you click on any button it always shows alert "option9". How is it possible assign onclick event to show correspondent option !?
Thanks!
You'll have to do something like this:
btnShow.onclick = (function(opt) {
return function() {
showParam(opt);
};
})(arrOptions[i]);
Consider the fact that when the onclick() function is executed, all it has is:
showParam(optionPar);
, verbatim. The optionPar will be resolve at the time the click event is executed, and at this point it most likely be the latest value you assigned to it. You should generally avoid passing variables in such a way.
The problem you are trying to solve is best solved by re-writing the piece such as:
btnShow.value = "Show Me Option";
var optionPar = arrOptions[i];
btnShow.optionPar = optionPar;
btnShow.onclick = function(e) {
// if I'm not mistaking on how to reference the source of the event.
// and if it would work in all the browsers. But that's the idea.
showParam(e.source.optionPar);
}
The accepted answer seems to work, but seems to be confusing and a somewhat cumbersome way to do it. A better way perhaps might be to use the data attribute for the element you're looking to assign the event listener for. It's simple, easy to understand, and way less code. Here's an example:
btnShow.data = arrOptions[i];
btnShow.onclick = function() {
showParam(this.data);
}
I attach an event handler:
window.onload = function() {
var folderElement;
tagFolders = document.getElementById("folders");
for (i = 0; i < folders.length; i++) {
folderElement = folderButtons[i];
folderElement = document.createElement("button");
folderElement.setAttribute("id", folders[i]);
folderElement.setAttribute("type", "button");
folderElement.innerHTML = folders[i];
if (typeof window.addEventListener !== "undefined") {
folderElement.addEventListener("click", getFolderElement, false);
} else {
folderElement.attachEvent("onclick", getFolderElement);
}
tagFolders.appendChild(folderElement);
}
which can retrieve anything from the element that triggered the event:
// This function is the event handler for the folder buttons.
function getFolderElement(event) {
var eventElement = event.currentTarget;
updateFolderContent(eventElement.id);
}
in which case you have to embed the option inside the element / tag. In my case I use the id.
For jquery, check out the adding event data section from the API:
...
for (var i = 0; i < arrOptions.length; i++) {
$('<input id="btn" type="button" value="Show Me Option"><input>').appendTo("#divButtons")
$('#btn').bind("click", {
iCount: i},
function(event) {
showParam(arrOptions[iCount]);
});
}
The accepted answer is correct but I feel that no real explanation was done.
Let me try to explain, the issue here is classical missing closure.
The variable 'i' is getting increased by 1 per loop iteration,
and the on-click event actually is not being executed, whether only applied to the a element, it getting summarize up to the length of arrOptions which is 10.
So, the loop continues up until 'i' is 10,
Then, whenever the on-click event is being triggered, it takes the value of i which is 10.
now, for the solution,
in the solution we are using a closure, so that when we apply the value of 'i' to the on-click event of the a element, it actually gets the exact value of i at in time.
The inner function of the onclick event create a closure where it references the parameter (arrOptions[i]), meaning what the actual i variable is at the right time.
The function eventually closes with that value safely,
and can then return its corresponding value when the on-click event is being executed.
You pass just the reference of the variable to the function, not it's value. So every time the loop is iterated, it assigns a reference to your anonymous function and all of them point to the same value in memory. But since you use the same variable name in the loop, you overwrite the value of the variable. You can concatenate the variable to a string to preserve it's value. For example like that:
btnShow.onclick = new Function("", "showParam(" + arrOptions[i] + ");");
The first parameter is the name of the function but afaik it is optional (it can be left blank or omitted at all).
pp();
function pp()
{
for(j=0;j<=11;j++)
{
if(j%4==0)
{
html+= "<br>";
}
html += "<span class='remote' onclick='setLift(this)' >"+ j+"</span>";
}
document.getElementById('el').innerHTML = html;
}
function setLift(x)
{
alert(x.innerHTML);
}