Ad

Why Doesn't This Javascript Closure Work As I Hoped?

- 1 answer

I wrote this piece of code, hoping that after running the document.ready() part the widget_controller property on search_form.widget_date_range object will become a controller. To my surprise, it is undefined. What is going wrong here?

search_form.widget_date_range = (function() {
  var element_selector = "div.date-range-slider";
  var settings = {
    bounds: {
      min: new Date(2003, 0, 1),
      max: new Date()
    },
    defaultValues: {
      min: new Date(2003, 0, 1),
      max: new Date()
    },
    wheelMode: "zoom",
  };
  var widget_controller = {};
  var onDocumentReady = function() {
    widget_controller = $(element_selector).dateRangeSlider(settings);
    widget_controller.on("valuesChanged", onDataChange);
    console.log(widget_controller);
  };
  var onDataChange = function(e, data) {
    // alert("Value just changed. min: " + data.values.min + " max: " + data.values.max);
    $("form#searching").trigger("submit");
  };
  return {
    onDocumentReady: onDocumentReady,
    widget_controller: widget_controller
  };
})();

$(document).ready(function() {
    ...
    search_form.widget_date_range.onDocumentReady();
});
Ad

Answer

I think you are missing a point about references in JavaScript:

var search_form={};
search_form.widget_date_range = (function() {
   // Whatever code here

  // widget_controller variable references an empty object
  var widget_controller = {}; // #A
  var onDocumentReady = function() {
    // Here you redefine widget_controller,
    // but not the returned_object.widget_controller property
    widget_controller = $(element_selector).dateRangeSlider(settings); #B
    widget_controller.on("valuesChanged", onDataChange);
    console.log(widget_controller);
  };
  // Whatever here
  return {
    onDocumentReady: onDocumentReady,
    // returned_objet.widget_controller references the empty object
    widget_controller: widget_controller #C
  };
})();

One solution could be:

  ...
  return {
    onDocumentReady: onDocumentReady,
    get widget_controller() {
      return widget_controller; #D
    }
  };
  ...

JSFiddle demo here: https://jsfiddle.net/xgya3mzh/2/

EDIT: (it seems like more explanation are required)

First : the { get property() {} } syntax is from ES5 and you can find doc on MDN here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get

You could accomplish the same with ES3 with another syntax, explained here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineGetter (but use this syntax only if you wish to support IE8 and below - I hope you don't have to).

At #A you declare the variable widget_controller and set it to an (almost) empty object ({}); (I say almost because it still has a prototype with some inherited methods and properties).

At #C you set the property widget_controller of an object literal (which will be returned by the closure) to the reference of the empty object.

At #B, which happens after#C, you redefine widget_controller of the closure, but you don't redefine the widget_controller of the object returned by the function, which still references the value of the empty object.

In my code, in #D, the same widget_controller of the closure is returned whenever you access the property widget_controller of the object returned by the closure.

I hope it is clearer, though I am not very sure...

Ad
source: stackoverflow.com
Ad