-2

I'm trying to update the array with values once the user clicks on an element in the DOM. The elements seem to be pushed into the array inside the anonymous function but outside the function the array is still empty. How can I fix that? Here is the javascript code:

function getSelection() {
    var selections = [];

    var container = document.getElementById("main-container");
    var choices = document.querySelectorAll('li');

    choicesLength = choices.length;

    for(var i = 0; i < choicesLength; i++) {

        choices[i].addEventListener("click", function(){
            var position = this.getAttribute("value");
            selections.push(position);
            // logs array and updates with each click 
            console.log(selections);
        });
    }       

    // logs empty array
    console.log(selections);

}

Basically, after the items are clicked, the main array needs to be updated with what they clicked.

Any help would be appreciated.

Thanks

Ryan Mc
  • 833
  • 1
  • 8
  • 25
  • The array is local to the `getSelection` function and the `console.log` is only executed once - how exactly are you testing that the elements get added to this array? (Or when is that function executed?) – UnholySheep Oct 15 '16 at 08:41
  • You seem to be trying to use JavaScript to make list items act like checkboxes. You should just use checkboxes instead. – Quentin Oct 15 '16 at 08:43
  • Quentin, that's by the by and doesn't solve the problem. I can change that later. – Ryan Mc Oct 15 '16 at 08:49
  • @RyanMc, it logs an empty array because the function `getSelection` gets executed once however the listeners are bound to the elements in that one function call. So the `selections` array that listener refers to is not the one you define inside the scope of your function. – Vivek Pradhan Oct 15 '16 at 08:54
  • @VivekPradhan thanks for the help. It makes sense. Still not sure how to remedy it though. – Ryan Mc Oct 15 '16 at 08:59

4 Answers4

0

The function you pass to addEventListener won't run until the event happens.

It isn't possible for you to click any of the list items before the console.log that runs immediately after the assignment fires.

Basically, after the items are clicked, the main array needs to be updated with what they clicked.

… and that is what will happen. Look at the results of the console.log inside the click handler.

Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
  • Can you show me an example? I'm not sure what you mean.. I have looked at the results of console.log inside the handler, as you can see in my code – Ryan Mc Oct 15 '16 at 08:50
  • @RyanMc — Short of writing your HTML and using it with your code, not really, your code already functions as an example of this. You are looking at the results inside your handler, that's the `console.log` I referred to, and it should show the array filling up as you click. – Quentin Oct 15 '16 at 09:08
  • Yes, it does show the array filling up as I click. But the main selections array is never updated. It remains empty. I know it's a scope issue but don't know what to chaneg – Ryan Mc Oct 15 '16 at 09:12
  • @RyanMc — "the main selections array is never updated" — You only have one selections array and it is updated. What isn't updated is the output of `console.log` because that tells you what the value of it was **when you called console.log**. The output in your console doesn't get updated just because the value changed. – Quentin Oct 15 '16 at 09:15
  • When you click on the list item, you change the contents of the array *now*, your click doesn't travel back in time and change it before you first looked at the contents of the array. – Quentin Oct 15 '16 at 09:18
  • That makes sense. That being said, how do I then run a conditional check on the length of the array? – Ryan Mc Oct 15 '16 at 09:21
  • @RyanMc — You check it inside the click event handler (i.e. at the point where `console.log` shows you what you expect) – Quentin Oct 15 '16 at 09:21
-1

Basically move selections otuside of the function getSelection and make it public.

var selections = [];

function getSelection() {
    var container = document.getElementById("main-container");
    var choices = document.querySelectorAll('li');
    choicesLength = choices.length;
    for(var i = 0; i < choicesLength; i++) {
        choices[i].addEventListener("click", function(){
            var position = this.getAttribute("value");
            selections.push(position);
            console.log(selections);
        });
    }       
    console.log(selections);
}
window.onload = getSelection;

// access selections outside of getSelection()
setInterval(function () {
    if (selections.length < 2) {
        console.log('length is smaller than two.');
    }
}, 1500);
<ul><li value="1">1</li><li value="2">2</li><li value="3">3</li><li value="4">4</li><li value="5">5</li><li value="6">6</li></ul>
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • What if the OP wants to call the `getSelection` method some other place. Why should it be done only on body `load`? A little explanation would help. – Vivek Pradhan Oct 15 '16 at 08:47
  • Thanks for your answer but I get the same result as before. It's still not updating the selections array. Basically I need to do a conditional test on the array length to change the behaviour on the page. eg – Ryan Mc Oct 15 '16 at 08:48
  • eg... // if selections length is less than 2, do one thing, else do another – Ryan Mc Oct 15 '16 at 08:49
  • The question clearly shows that the OP can see the output of `console.log` so the function must be being called already. – Quentin Oct 15 '16 at 09:17
  • Re edit: Making `selections` global won't help. It is already in-scope for everything that tried to access it. This is a question about timing not scope. – Quentin Oct 15 '16 at 09:25
-1

This is about Javascript Scoping , you can lean more about it here:
Learn more about Javascript Scoping and hoisting

The first console.log(selection) is local while the last console.log(selection) is is global, the two are not the same, that is why it is empty. Here is the edited form of my solution:

function getSelection() {
  var selections = [];
  var container = document.getElementById("main-container");
  var choices = document.querySelectorAll('li');
  choicesLength = choices.length;
  for (var i = 0; i < choicesLength; i++) {

    choices[i].addEventListener("click",pushArray(i));
  }
  
  function pushArray(elm) {
  var position = choices[elm].getAttribute("value");
  selections.push(position);
  // logs array and updates with each click 
 console.log(selections);
}
  // logs empty array
  console.log(selections);
}


document.body.addEventListener("load", getSelection());
<ul>
  <li value="1">1</li>
  <li value="2">2</li>
  <li value="3">3</li>
  <li value="4">4</li>
  <li value="5">5</li>
  <li value="6">6</li>
  <li value="7">7</li>
  <li value="8">8</li>
  <li value="9">9</li>
  <li value="10">10</li>
</ul>
Jerry Okafor
  • 3,710
  • 3
  • 17
  • 26
  • While this points in the right direction. I think this should be a comment rather than an answer. May be you should provide a solution on how should OP refer to the `selections` array so that its scope is not lost due to the event listener bindings. – Vivek Pradhan Oct 15 '16 at 08:49
  • To be honest, I had a sneaky feeling it was a scope issue, as I remember learning about this. However, I can't rightly remember how to work around it. As Vivek said, a solution would be helpful. – Ryan Mc Oct 15 '16 at 08:52
  • This is just plain wrong. There is only one variable called `selections` and it is in scope for every attempt to access it – Quentin Oct 15 '16 at 09:09
  • I have a dded a solution up here, as you may not be using Es6, this solution works, but if you are using the new Es6, you can use let keyword to add a block level scope. – Jerry Okafor Oct 15 '16 at 09:11
  • This answer deserves a positive vote at least, not a negative vote. – Jerry Okafor Oct 15 '16 at 09:13
  • @Jerry — No, it doesn't. It isn't helpful. It is a massive red herring. – Quentin Oct 15 '16 at 09:16
  • @Quentin i just edited the answer with a code snippet, have you looked at it again. – Jerry Okafor Oct 15 '16 at 09:18
  • @Quentin it is not so, it show the final array and not an empty array like other code. – Jerry Okafor Oct 15 '16 at 09:20
  • It now adds all the items when the code loads and ignores the clicks completely. Why do you still have `addEventListener` in there? – Quentin Oct 15 '16 at 09:21
  • @Quentin you are right, but i have also copied the code locally and played around with it, i dont't know why he would choose that structure, he could as well do the rest of his activities inside the callback function. – Jerry Okafor Oct 15 '16 at 09:57
-1

Adding to my comment, I think closures can solve this problem pretty efficiently.

I haven't tested the following code:

function getSelection() {
this.selections = [];

var container = document.getElementById("main-container");
var choices = document.querySelectorAll('li');

choicesLength = choices.length;
var that = this;
for(var i = 0; i < choicesLength; i++) {

    choices[i].addEventListener("click", function(){
        var position = this.getAttribute("value");
        that.selections.push(position); //Explicitly specify context
        // logs array and updates with each click 
        console.log(that.selections);
    });
}       

return function(){
   this.selections;
}
}

You call the getSelection function like this:

var getUpdatedArray = getSelection();
//At a later stage when you want the array just do:
getUpdatedArray(); //This should return the selections array.

I explicitly bind the selections array to your function scope by doing this.selections = [] and then set this context inside the click listeners by doing var that = this.

To reiterate the problem:

You were binding event listeners in your getSelection method that gets called once only. However, clicks can happen at any time later on. So you need to ensure two things:

  1. The selections array referred to in the click event handler is the one defined in scope of getSelection method.
  2. Somehow, preserve the scope of getSelection method even after it is executed.

Closures, magically solve both these problems. :)

A good starting point on why to use closures is this.

Vivek Pradhan
  • 4,777
  • 3
  • 26
  • 46
  • Thanks. As far as I can tell, it does the exact same thing as before. Unless I'm misunderstanding how to use it. – Ryan Mc Oct 15 '16 at 09:16
  • It does more or less the same thing as before. It just stores the variables in different places. – Quentin Oct 15 '16 at 09:16
  • @Quentin, it preserves the scope of `getSelection()` method. I don't understand how is it more or less the same thing as before. This creates a closure of the scope of `getSelection()` method in the returned function. – Vivek Pradhan Oct 15 '16 at 09:40
  • @VivekPradhan — The scope was already preserved everywhere the original code was trying to use it. The OP isn't trying to return the array. If they were trying to return it then simply returning the array would have returned the array (because objects are passed by reference in JS). – Quentin Oct 15 '16 at 09:43