Update global var inside a function

I’m trying to update a global var so that I can use it further down my code.

The global var is being updated by a function triggered by an onclick() button event.

I can’t seem to update the var and use it.
I have a repl here if anyone has the time to look:

Your code is doing exactly what you’ve told it. The issue you’re having is, when you update the data variable, you’re not updating the HTML elements. They aren’t being re-set inside your choose() function, only set initially (outside the choice function) to the default data.

Instead, move them inside, and try this:

// Declare it, but leave it undefined!
var data; 

function choose(choice) {
  console.log("choice:  " + choice);
  data = dataset[choice];
  console.log(JSON.stringify(data) );
  console.log("data inside function:  " + data.title);

  // with the data set changed, update the innerHTML
  document.getElementById("title").innerHTML = data.title;
  document.getElementById("description").innerHTML = data.description;
}

// then call your function to trigger the default state setting.
choose('movie');

If, instead, you want to separate the choosing from the displaying, you can:

// Declare it, but leave it undefined!
var data; 

function choose(choice) {
  console.log("choice:  " + choice);
  data = dataset[choice];
  console.log(JSON.stringify(data) );
  console.log("data inside function:  " + data.title);
}

document.querySelectorAll('.nav button').forEach( function(button){
  button.addEventListener("click", function(event){
    // this listens for a click on any of the buttons, and updates.
    // this will, however, give you unexpected results - data may or may not
    // be updated before the click is triggered!
    document.getElementById("title").innerHTML = data.title;
    document.getElementById("description").innerHTML = data.description;
  }) // end addEventListener
})// end forEach

// then call your function to trigger the default state setting.
choose('movie');

There are some libraries that will let you listen to variable changes, and trigger events on them - so using those, you could actually trigger the HTML updates each time the data variable were to change.

The problem still remains… I data var after the function has has not been updated OUTSIDE the function.

If choose("video") runs the data var is set inside the function, but data is not updated outside the function.

So a few lines later I need to do this:
d3.json(data.url, function (error, data) {}

But data.url does not have the video url

So in my opinion, the best way to do something like this is by having data be an object, with getters and setters. It’s a little different than the approach you’re using, but with that, you can trigger events when the data changes.

Here’s the idea: have a function, as you did, called choose(event) (for example). All that does, when the user clicks a button, is get their selection and update the data object by using:

data.current = dataset[cName];

Within data, the current would be a setter. It would set a property on the data object, but here’s the neat bit – we could tell it to do other things. For example, we could allow the user to register listeners to the data object, say using data.registerListener(updateDOM); – this would run the function updateDOM any time the data object changes. How do we know the data object has changed? Well, if we’re following our proper channels, then we called that setter above.

Here’s the code for a data object that would work like this:

data = {
  _current: null,
  _listeners: [],
  set current(option){
    // update the currently selected option...
    this._current = option;
    // and call any callbacks that are listening for changes to current
    this._listeners.forEach(listener => listener(this._current) );
  },
  get current(){
    return _current;
  },
  registerListener(listener){
    this._listeners.push(listener);
  },
  deregisterListener(listener){
    this._listeners = this._listeners.filter(instance => instance !== listener)
  }
}

so we have four powerful functions going on here, within data:

  • a setter called current lets us set the current dataset. Note that it sets this._current, which is a convention used to indicate internal properties. Also, note that when it sets that value, it also calls each of the attached listeners.
  • a getter called current that returns the _current data.
  • a function called registerListener() which does what it says on the box: registers the listener. These will be run every time we use the setter, thus simulating an event listener.
  • a function called 'deregisterListener()`, which so far I haven’t tested, but which should remove the given listener from the array of listeners, thus no longer running it when the data set changes.

How might this be used? How about this:


// This function will be attached to the nav buttons click handlers. When the button is clicked, the data
//   object is updated. Note that I've created a default value for event, of null. Doing so, we can test
//   for that, and respond if the user has called the function with no event object.
function choose(event = null) {
  // cName no longer has to be global, as we've made data global instead. This is the only place that
  //   will need to access the dataset array, so let's move cName in here.
  let cName;
  event === null ? cName = "movie" : cName = event.target.name;
 
  // In appearance, calling the event.current setter looks exactly like setting a variable.
  //    But it starts some MAGIC!!
  data.current = dataset[cName];
}

// This function will simply update the DOM nodes with the appropriate content. We will register this on
//   our data object shortly.
const updateDOM = () => {
  console.log('This listener updates the DOM!')
  document.getElementById("title").innerHTML = data.current.title;
  document.getElementById("description").innerHTML = data.current.description;
}

// Another one to register with the data object, this will update the d3 content when the data changes
const updateD3Content = () => {
  console.log(`The data set has been updated, let's update the d3!`)
  d3.json(data.current.url, function (error, data) {
    if (error) throw error;
    document.getElementById("dataOutput").innerHTML = data.name + " - " + data.children[0].name;
  });
}

// First, we'll register the click handler...
document.getElementById("nav").addEventListener("click", choose);

// next we'll register our two data listeners.
data.registerListener(updateD3Content);
data.registerListener(updateDOM);

// now, if we want, we can simply call our choose function, to start the whole ball o wax...
choose();

This is running on repl here. Hope that gives a direction to consider! :wink:

1 Like

Wow! thanks for this, I understand what it’s doing too.
I never thought my problem would require such a complex solution :thinking:

1 Like

So, every so often, someone says something that sparks a… STORY TIME! And the ‘complex solution’ comment did. So here it is:

Back in the day, when I was in grade school and high school, I would regularly get beat on, hard. I wouldn’t fight back, couldn’t fight back, didn’t know how to fight back. Then I read Orson Scott Card’s Ender series, and realized - fight hard, fight dirty, and fight to win not just this fight, but all the ones that come after it. If I had to fight, it would be wise to completely crush and humiliate my aggressor, so that the rest of them would know better than to even start.

Still don’t like to fight, but there was a valuable lesson in that, that applies here.

End of story time…

So what’s the lesson, you may ask? When I write code, I try not to answer the question in front of me. I try to answer questions that I don’t even see coming, the ones that will pop up in six months or a year or so. I try to find the solution that will give me a better long-term bang for my buck.

The data Object we defined? That’s pretty extensible. It is useful in many many places. Imagine a two-player game, for example, and that data object being used to keep the score. We could then have a function that, when one user or the other does something that changes their score, updates the data object with the new score. We could also have a few listeners to that score update:

  • Update the scoreboard DOM node;
  • Send the new score to a back end (firebase, or node, or whatever),
  • Check if the new score is a win for either side
  • … All sorts of things could happen

The strength of it is, none of the other parts need to know why or how the data object was updated, just that it happened, and triggered some callbacks. The data object doesn’t need to know about any other object, it simply runs the callbacks that have been fed to it, blindly.

I’m actually looking at this data object, and seeing it as a pretty powerful, extensible data store. I could see an interface to it going something like:

let gameDataStore = new DataStore();

// we could create properties dynamically, and create the getters and setters 
//   for them on the fly!
gameDataStore.watchedProperty('p1Score');
gameDataStore.watchedProperty('p2Score');

// Perhaps even allow for multiple properties to be defined at a single call?
gameDataStore.watchedProperty('p1Wins', 'p2Wins');

// We could attach listeners to specific watched properties, and attach to 
//   multiple properties in one call!
gameDataStore.watch('p1Score, p2Score', updateDOMScoreboard);
gameDataStore.watch('p1Score, p2Score', sendUpdatedScore);
gameDataStore.watch('p1Score, p2Score', checkForWin)

// checkForWin might update the watched p1Wins property, which would trigger the following
gameDataStore.watch('p1Wins', playFanfare)

// When we did this, it would use a setter similar to earlier implementations, with its own listeners.
gameDataStore.p1Score = gameDataStore.p1Score+1;

This is all just thinking out loud, a thought experiment, but I’m going to work on it for a little, simply because I see it as a powerful gadget. :wink:

1 Like