I was looking at some javascript code to create a calculator app. It's straightforward, but what confused me was when there's function that returns a function.
With the code below, I understand each button of the calculator needs an event listener to utilize "click", upon which the function is called depending on what button was clicked. The addValue(i) function adds the button value to the result field when the button is clicked. The function below has a function that returns a function. Without the function call, the result returns "789+456-123/0.=x"
Can some explain why the addValue(i) function needs to return a function for this function to work.
for (var i=0; i<buttons.length; i++) {
if(buttons[i] === "="){
buttons[i].addEventListener("click", calculate(i));
} else {
buttons[i].addEventListener("click", addValue(i));
}
}
function addValue(i) {
return function() {
if(buttons[i].innerHTML === "÷") {
result.innerHTML += "/";
} else if (buttons[i] === "x") {
result.innerHTML += "*";
} else {
result.innerHTML += buttons[i].innerHTML;
}
}
};
In this example, I'm assuming that the webpage is a calculator, similar to:
|---------------|
| |
|---------------|
| 1 | 2 | 3 | * |
|---|---|---|---|
| 4 | 5 | 6 | / |
|---|---|---|---|
| 7 | 8 | 9 | = |
|---------------|
In this case, the button buttons[i] is one of the calculator buttons; | 1 |, | 5 |, etc. The actual number of the button is its index, so buttons[2] would be | 2 |, buttons[7] would be | 7 |, etc. And I'm guessing other values, like buttons[0] and buttons[10], are the operator buttons (| = |, | * |, | / |).
Now, when the user clicks on one of these buttons, the character on the button, which is the button's innerHtml, is added to the calculator's result (by adding it to the result's innerHTML), which is just a display of the operation. So if I clicked | 3 |, then | * |, then | 5 |, the calculator would look like
|---------------|
| 3*5 |
|---------------|
| 1 | 2 | 3 | * |
|---|---|---|---|
| 4 | 5 | 6 | / |
|---|---|---|---|
| 7 | 8 | 9 | = |
|---------------|
Here's where the addEventListener comes into play. When you call element.addEventListener("click", func), whenever element is clicked, func will be called. So to add the button to the result when it's clicked, you could do:
buttons[i].addEventListener("click", addButtonValueToResult);
where addButtonValueToResult is the function to add the button's value to the result.
The problem is, the different buttons have different values, so one function like this won't work for all of them. Therefore, addButtonValueToResult can't be a simple function; each button needs its own function. One such solution is to ditch the for loop and just do something like:
buttons[1].addEventListener("click", add1ToResult);
buttons[2].addEventListener("click", add2ToResult);
buttons[1].addEventListener("click", add3ToResult);
...
and then have functions like:
function add1ToResult() {
result.innerHTML += "1"
}
But this takes a lot of unneeded work, because you need to add functions for each of the number buttons (| 1 | through | 9 |) as well as the operator buttons (| = |, | / |, and | * |), which just add their innerHtmls to the result. Moreover, the button's index is already assigned to a variable i and the entire button can be referenced with that index with buttons[i]. Couldn't you just have the program make a function automatically, for each button, that when given the button's index i, gets the button (through buttons[i]) and adds that button's value to the result?
Well that's exactly what this program does: addValue(i) doesn't just add the button's inner value itself; it returns another function that, also with a few test cases for special buttons, adds the button's inner value. Looking at this code:
function addValue(i) {
return function() {
if(buttons[i].innerHTML === "÷") {
result.innerHTML += "/";
} else if (buttons[i] === "x") {
result.innerHTML += "*";
} else {
result.innerHTML += buttons[i].innerHTML;
}
}
};
Say you call addValue(3); this will return a function that will add the digit 3 to the result, in result. If you call addValue(9); this will return a function that will add the digit 9 to the result, in result. You can call the function addValue returns and actually add a digit to the result, through (addValue(digit)()). But addEventListener doesn't take an evaluated result; it takes a function, which it will later call when the button is clicked.
Related
This question already exists:
JavaScript - use eval to get value in code string
Closed 1 year ago.
I am trying to evaluate JavaScript code based on what a user enters into a code editor. I get their code as a single string, use babel to transform it and then split on newlines to get the code by line. I want to see if the current line evaluates to something and if so, print that out. I skip lines that do not evaluate to anything but the problem is if some writes code like the following:
1 | const x = 5;
2 | x
Ideally I would like to print something like:
1 |
2 | 5
The problem though is that I need line 1 to evaluate line two. If I just join the lines of code and evaluate that (something like: const x = 5;\nx;) it will result in 5;
However, if a line further down does not evaluate to anything, then it will also return 5 but that is not correct because const y = 3; does not evaluate to 5. So for example the following:
1 | const x = 5;
2 | x
3 | const y = 3;
concatenated (something like: const x = 5;\nx\nconst y = 3;): would result in:
1 | undefined
2 | 5
3 | 5 // PROBLEM: this should also be undefined.
I have tried this solution: Context-preserving eval but it does not answer my question. Moreover, The solution throws an error in the following line when I tried it out:
// 'void (__EVAL = ${__EVAL.toString()});' causes an error
var __EVAL = s => eval(`void (__EVAL = ${__EVAL.toString()}); ${s}`);
I want to evaluate the latest statement in a block of code, given I have the code written before that, but only return a result if the current line evaluates to something. So as one final example, the following code:
1 | const x = 5;
2 | x
3 | const y = 3;
4 |
5 | const add = (a, b) => {
6 | return a + b;
7 | };
8 |
9 | add(x, y);
should give:
1 |
2 | 5
3 |
4 |
5 |
6 |
7 |
8 |
9 | 8
I would like to transform the code in my project from jQuery.on to .addEventListener and remove the dependance on jQuery.
Main question: Is there a way of doing this with a relatively low amount of code or do I need to write a custom event handling like jQuery seems to have?
------------------------------------------
| a |
| |
| ----------------------------- |
| | b | |
| | | |
| | --------------- | |
| | | c | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | --------------- | |
| | | |
| | | |
| ----------------------------- |
| |
| |
------------------------------------------
clicking c should return the handler for c, then b then a
I have read multiple stackoverflow questions* and answers which give me a basic idea of the differences between the two (or mostly the aditional logic jQuery adds). In this fiddle you can see a basic example I would like to get working the way jQuery works.
It is clear to me that when the event bubbles up from the target (div.c) it reaches div.a and then the listeners on div.a get triggered in the order they where added, which in this example results in a, b, c instead of c, b, a like jQuery does.
b is bold because it does not get triggered though this whould be the desired behaviour.
The reason it does not get triggered is because the e.target (div.c) does not match the querySelector .b.
To me it looks like jQuery uses its own handle/triggering of events when they are triggered from the .addEventHandler.
And it seems to take the DOM nesting into consideration eventhough the listeners are triggered from a common parent.
This last part of taking the DOM nesting into consideration is what I would like to have using 'vanilla' js.
Using $._data(element, 'events') we can access the events jQuery has stored internally to see them.
The guid attribute here seems to indicate the order the events are to be triggerd.
How the order is determined/triggered however I was unable to find.
Any help is really appreciated!
* related questions
jQuery .on(); vs JavaScript .addEventListener();
jQuery event handlers always execute in order they were bound - any way around this?
jQuery on() stopPropagation not working?
You can walk up all the ancestors of target and check if they match a specific selector and call a callback for each matching instance
The order they fire will still be the order they are added to the root element
HTMLElement.prototype.delegate = function(evtName, selector, callback) {
var rootElement = this;
this.addEventListener(evtName, function(e) {
var matchingAncestors = [], parent = e.target.parentElement;
// TODO: Abort if target is root element
if (e.target.matches(selector)) {
console.log('selector', selector,'matches target')
callback(e)
} else {
// we know it's not the target so work way up ancestors finding selector matches
while (parent && parent !== rootElement) {
if (parent.matches(selector)) {
matchingAncestors.push(parent)
}
parent = parent.parentElement
}
// call the callback for each instance
matchingAncestors.forEach(function(el) {
console.log(el, ' is matching ancestor of target')
callback(e)
});
}
})
}
var a = document.querySelector(".a");
a.addEventListener("click", function(e) {
console.log('*********************************')
console.log("Vanilla a\n");
});
a.delegate('click', '.b', function(e) {
console.log("Vanilla-delegate b\n");
})
a.delegate('click', '.c', function(e) {
console.log("Vanilla-delegate c\n");
})
div {padding-left:30px; border:1px solid #ccc}
<div class="a">
a
<div class="b">
b
<div class="c">
c<br>(click me)
</div>
</div>
</div>
Note this is untested in production
I was practicing my javascript with CodeFights and after I finished an exercise I saw this function as a result:
// Subject :
// Several people are standing in a row and need to be divided into two teams.
// The first person goes into team 1, the second goes into team 2,
// the third goes into team 1 again, the fourth into team 2, and so on.
// You are given an array of positive integers - the weights of the people.
// Return an array of two integers, where the first element is the total weight of
// team 1, and the second element is the total weight of team 2
// after the division is complete.
// Example :
// For a = [50, 60, 60, 45, 70], the output should be
// alternatingSums(a) = [180, 105].
// answer
alternatingSums = a => a.reduce((p,v,i) => (p[i&1]+=v,p), [0,0])
I don't understand what p[i&1]+=v,p means.
The & symbol is a bitwise binary operator.
To understand what would happen, you have to convert each item to binary.
| i (decimal) | i (binary) | i & 1 |
|-------------|------------|-------|
| 0 | 0 | 0 |
| 1 | 1 | 1 |
| 2 | 10 | 0 |
| 3 | 11 | 1 |
| 4 | 100 | 0 |
| 5 | 101 | 1 |
Effectively, every even number will be transformed to 0, and every odd number will be transformed to 1.
If I was trying to achieve that outcome, I personally would have used the modulus operator (%)
p[i%2] += v;
But that's just me.
The other part is that there are two statements separated by a comma:
(p[i&1]+=v,p)
That's saying "Perform this action, then return p. It's shorthand for:
alternatingSums = a => a.reduce((p,v,i) => {
p[i&1]+=v;
return p;
},
[0,0])
It looks for an element of the p array that has index of i&1 - it is a bitwise AND operation. Then, increments its value by a value of v variable. Finally, returns the value of p variable.
I have a text field represented as: field = {text: "", valid: false}, and an input with [(ngModel)]="field.text".
I want to make that field only accept a defined set of characters (for this issue, numbers), and doing (keypress) doesn't work on mobile, so I did: (ngModelChange)="fieldChanged(field)"
The method does the following:
fieldChanged(field) {
console.log(field.text);
field.text = Array.from(field.text).filter((char:string) => "0123456789".indexOf(char) != -1).join("");
console.log(field.text);
}
And it's behaviour is extremely weird.
Legend:
- input: what key was pressed
- before update: first console.log
- after update: second console.log
- output: what I see on screen in the input
| input | before update | after update | output |
|---------|---------------|--------------|--------|
| "" | "" | "" | "" | <- starting position, no event
| "a" | "a" | "" | "a" |
| "a" | "aa" | "" | "aa" |
| "4" | "aa4" | "4" | "4" |
| "a" | "4a" | "4" | "4a" |
| "a" | "4aa" | "4" | "4aa" |
| "4" | "4aa4" | "44" | "44" |
Why does it always update the output when I enter a legal character? It should be working for each event call.
Edit:
Plunker
I think the cause is that modifying the value on ngModelChange breaks change detection, for example if you change the value back to the previous value, because an invalid character was added.
A workaround:
constructor(private cdRef:ChangeDetectorRef) {}
fieldChanged(field) {
console.log(field.text);
field.text = Array.from(field.text).filter((char:string) => "0123456789".indexOf(char) != -1).join("");
console.log(field.text);
var tmp = field.text;
field.text = null; // or some other value that normally won't ever be in `field.text`
this.cdRef.detectChanges();
field.text = tmp;
this.cdRef.detectChanges(); // I guess this 2nd call won't be necessary
}
If anyone having the issues in updating the value, use setTimeout function while updating
// setTimeout function
setTimeout(() => {
field.text = temp;
this.cdRef.detectChanges();
}, 1);
Having a problem, which occurs in Firefox and Safari, but in Chrome it works fine.
This javascript array contains quiz questions, which I display one at at a time. However, sometimes not all records are iterated. I used the console, and it is definitely loading all questions, but still sometimes skips a record (usually only the first one).
EDIT: I have noticed that on quizes this array works for, all questionids are in order, i.e 290,291,293 in the array. But in in an example of one not working, the quiz id's are in this order, 286,285,287,288 and 285 is the one that is skipped, this could be part of the problem.
Here is my Javascript array code, please help me solve this problem.
var currentquestion;
jQuery.ajax({
url:"quizajax.php",
dataType: "json",
data: {
quizidvalue: <?=$thisquizid?>
},
}).done(function(data) {
questions = data;
for(i in data){
console.log(data[i]);
}
});
function nextQuestion (){
for(i in questions) {
if(i<=currentquestion)
continue;
currentquestion = i;
for(y in questions[i]) {
console.log("CurrentA: "+ currentquestion);
console.log("I: " + i);
console.log(questions[i][y].answerid);
}
console.log("CurrentQ: "+ currentquestion);
console.log("I: " + i);
console.log(questions[i]);
questionVariables ();
break;
}
Example code from db,
questionid | quizid | questiontype | qdescription | qfilelocation | noofanswers | answertype
------------+--------+--------------+------------------+------------------------------------+-------------+------------
285 | 55 | text | 6 answer text | null | 6 | text
287 | 55 | text | 4ans text q | null | 4 | text
289 | 55 | text | 2 answers text q | null | 2 | text
286 | 55 | text | 5 answer text q | null | 5 | text
288 | 55 | text | 3 answer text q | null | 3 | text
290 | 55 | image | image q and a | image/55/712013a88298585d415c.jpeg | 4 | image
291 | 55 | video | video q and a | video/55/8efa10195f0c20d1254f.mp4 | 4 | video
The continue statement is causing the loop to skip part of its execution.
Try debugging so see where code logic has the bug.
for(i in questions) {
if(i<=currentquestion) {
console.log('Skipping question: ' + i); // add debugging
continue;
}
...
EDIT: (several comments based on update)
Its best to iterate through arrays using traditional for loops:
for (var i = 0, len = questions.length; i < len; i++) {
But the outer loop isnt event needed
If you initialize var currentquestion = 0; the outer loop can be replaced
function nextQuestion (){
for(y in questions[currentquestion]) {
console.log("CurrentA: "+ currentquestion);
console.log(questions[currentquestion][y].answerid);
}
console.log("CurrentQ: "+ currentquestion);
console.log(questions[currentquestion]);
questionVariables ();
currentquestion++; //update here
}
It looks like your code depends on order, so you can sort
.done(function(data) {
questions = data;
questions.sort(function(q1,q2){
//assuming questionid is the correct property
if (q1.questionid < q2.questionid) {
return -1;
} else if (q1.questionid > q2.questionid) {
return 1;
}
return 0;
});
...
You could have used jquery.getJSON or $.ajax( { success ....})
You should probably reset currentquestion = 0; in your done method.