Watch Your Scoping

I recently found a bug in a plugin and so I figured that would be a good time to offer a public service announcement as to why you have to be careful with scoping in JavaScript.  The plugin I’m featuring is just a quick example of the problem.  Suppose your had the following HTML snippet:


< div id="section1" class="section" >
First Button
< span id="sp1" >0< /span >
< /div >
< div id="section2" class="section" >
Second Button
< span id="sp2" >0< /span >
< /div >
< div id="section3" class="section" >
Third Button
< span id="sp3" >0< /span >
< /div >

Pretty simple. The idea of my plugin was to target the root DIV tag, and then parse down to the BUTTON and SPAN at an individual level. Now, as I think about writing this, the reality is of the situation, I should have considered the JQuery Widget API, as I think that would have been better situated for what I am about to do. I was under the gun to get this feature completed, and this solution was not optimal, but it worked and did get the job done. Anyway, the JQuery plugin is below:

$.fn.parse = function() {
  return this.each(function() {
    var spn = $(this).find("span");
    btn = $(this).find("button");
    
    btn.on("click", function(e) {
       var n = parseFloat(spn.html());
       n += 1;
       spn.html(n);
    
       btn.html(btn.html() + "(clicked)");
    });
  });
}

The idea is that the span and button are retrieved within the context of the root DIV, and when the button is clicked, both elements are updated to indicate that a click occurred. You’ll notice though, that while the span gets the proper update, the button does not (always targets the last). This is simply because the button doesn’t include “var” declaration. This means the button is scoped beyond the current context of the each() callback. Because of this, the last button was selected and thus the last button is represented in the query.

This produces the result:

jsscreenshot

Adding VAR was the immediate solution. Another reminder why you need to make sure you variables are scoped correctly.

Just to brainstorm, another solution could be to, using the current reference within the button click, access the button through the callback and even target the SPAN that way, rather than relying on variables.

You can view the Gist here.

Advertisements

Knockout Part 3: Events

Knockout supports an eventing structure that can tie a function to an element through a binding statement.  While in JQuery, a developer may attach to an element’s event through this:

$(“#btn”).click(function() { .. });

In knockout we can also support this functionality by defining an event handler using a declarative statement:

<input type=”button” data-bind=”click:clickHandler” />

This requires defining in our view model a method:

function viewModel() {
var self = this;

self.clickHandler = function() { .. };
}

Knockout links methods in the model to the event, and supports click and submit out of the box.  If you want to attach to any other event, an event binding allows you to customize the method name, as in the following code sample.  This assumes mover and mout are methods in the view model.

<div data-bind=”event:  mouseover: mover, mouseout: mout”></div>

Events, depending on the type, may accept an argument.  The submit binding passes in a reference to the form element, and accepts a boolean value as the result.  If true, the form posts back as normal; if false, post back is blocked. The click handler can use an empty method, but can also take two references, one for the data object, and another object specific to the event raised.

Another feature with events is scoping; scoping means that we can use the $parent, $parents, or $root references (referenced in the previous blog post) to refer to an event handler at that particular scope (like  data-bind=”$root.mover”; using $root, we can directly refer to a handler in the view model, when the scope has changed).

Everything illustrated thus far used a method as the event handler; code can also be entered in line, as in the following example too.

<button data-bind=”click: function(data, event) { .. }”>Save</button>

In all of these examples, we defined a way to attach to events outside of the normal JavaScript/JQuery structure that developers are used to, and I hope you can see how powerful that events can be within the Knockout framework.