Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

property initialization in JavaScript [closed]

I have created a class in JavaScript like this:

var Test = function(){
    this.element = null;
    this.init = function() {
        if(Test.html == "") {
            Test.loadHtml(this);
            return;
        }
        this.initElements();
        this.someMethodInternalCall();
    };
    this.initElements = function() {
        // append the loaded html to body
        // etc...
        this.element = $("some-element-contained-in-loaded-html-apended-to-body");
    }
    this.someMethodInternalCall = function() {
        this.element.css({some:style}); // works in this place
    }
    this.someMethodExternalCall = function() {
        this.element.css({some:style}); // dosn't work in this place
        // I mean here this.element is null. WHY?
    }
};
Test.html = "";
Test.loadHtml = function() {
    // load html content by an ajax request (jQuery $.ajax method)
    // and put it in Test.html
    // then recall the Test.init ethod
    return function(caller) {
        Test.html = // load html by ajax... etc...
        caller.init();
    };
}();

function someUsage(){
    var t = new Test();
    t.init();
    t.element.css({some:style}); // error: t.element is null WHY?
    t.someMethodExternalCall(); // error: this.element is null WHY?
}

As you can see, I explain in code above. Why when we set properties after their initialization, it just takes effects in internall calls? How can I create a property that I can change it's value?

UPDATE:

It seems that I have to explain my code. The probelem is all about element property, not Test.html nor Test.loadHtml method or calling it. Test.loadHtml get fired currectly (you can test it) and Test.html gets the loaded html and the loaded html get appended to body and so on. This is a JavaScript pattern (I forgot what is its name) and works currectly. The only thing that is wrong is about property initialization -element.

like image 215
amiry jd Avatar asked Dec 31 '12 13:12

amiry jd


People also ask

How do you declare a property in JavaScript?

Another way of creating objects is using the Object() constructor function using the new keyword. Properties and methods can be declared using the dot notation . property-name or using the square brackets ["property-name"] , as shown below.

Can we initialize an object in JavaScript?

Objects can be initialized using new Object() , Object. create() , or using the literal notation (initializer notation). An object initializer is a comma-delimited list of zero or more pairs of property names and associated values of an object, enclosed in curly braces ( {} ).

What is a JavaScript initialization?

Initialization is the assignment of initial value to a variable. Initialization must be done before the variable is used. Otherwise, the variable will hold a value of "undefined" or "null", which is not desired.

How do you initialize a variable in JavaScript?

Before you use a variable in a JavaScript program, you must declare it. Variables are declared with the var keyword as follows. Storing a value in a variable is called variable initialization. You can do variable initialization at the time of variable creation or at a later point in time when you need that variable.


2 Answers

The problem is asynchronicity. When you are going off to load the HTML via AJAX the rest of the function continues...

function someUsage(){
  var t = new Test();
  t.init();
  // The following carries on whilst the request is loading it does not wait
  t.element.css({some:style}); // This is why I am null
  t.someMethodExternalCall(); // This is why I am also null
}

To get around this you could use a callback...

function someUsage(){
  var t = new Test();
  t.init(function() {
    // I do not continue until the loadHtml request has completed
    t.element.css({some:style}); // I am not null anymore
    t.someMethodExternalCall(); // I am not null anymore
  });
}

You would need to modify your init function and your loadHtml function to call the callback rather than the init method of the caller object, the init function...

this.init = function(callback) {

  // Using blank Test.html to determine whether the html has been loaded
  if(Test.html == "") {
    var me = this;

    // Call loadHtml with a callback function
    Text.loadHtml(function() {

      // I want to keep the this reference to the object and callback argument
      me.init(callback);
    });

  // It is loaded so continue set up and then trigger the callback
  } else {
    this.initElements();
    this.someMethodInternalCall();
    callback();
  }
};

There would still cause a problem if you created a number of these Test classes as each would try to get the HTML whilst the others were loading.

To get around this you just need to have a flag that is set by the first call. Any subsequent calls are ignored but the callbacks are recorded to be called when the HTML finishes loading...

Test.loadHtml = function(callback) {

  // If already loading roll up callbacks
  if(Test.loading) {

    // Callback becomes a function that calls the original callback function 
    // and then the new one
    Test.callback = (function(original) {
      return function() {
        original();
        callback();
      }
    }) (Test.callback);

  // First time it has been called set the flag to prevent multiple loads 
  // and add the callback
  } else {
    Test.loading = true;
    Test.callback = callback;

    // Added to illustrate the AJAX callback functionality
    ajax("html", function(response) {
      Test.html = response;
      Test.callback();
    });
  }
}();

The preferred approach is to enforce object validity at instantiation time, this prevents these race conditions. You throw an error if the class cannot be constructed validly, this moves the complexity surrounding the order of your operations from the class. As you can see below it is not as pretty and you have to call the load step yourself (or have something else trigger it).

new Test(); // Errors not loaded!
// We must perform the load step to use the class
Test.load(function() {
  new Test(); // Works!
});

The more elegant solution, especially for large applications, involves managing access to the class. You cannot access the class without first performing the loading step, this enforces that loading will always complete before the class is instantiated.

// views is some object managing the loading of view classes when asked for
// one or more views it will load the required HTML and return the class(es)
// so we can create instances...
views.load("Test", function(Test) {
  var t = new Test();
  t.element.css({some: style});
  t.someMethodExternalCall();
});
like image 131
Stuart Wakefield Avatar answered Nov 07 '22 06:11

Stuart Wakefield


Are you doing caller.init(); in the callback of the ajax function in loadHtml?

If not, your init function will be added to the execution stack before your html is loaded (and that's why this.element is null)

Test.loadHtml = function() {
    // load html content by an ajax request (jQuery $.ajax method)
    // and put it in Test.html
    // then recall the Test.init ethod
    return function(caller) {
      $.ajax({
        url: 'somethinghere',
        success: function(data) {
            Test.html = data;
            caller.init();
        }
      });
    };
like image 37
Jim Schubert Avatar answered Nov 07 '22 06:11

Jim Schubert