11

Regarding the jquery ui widget factory...

What is the best way to have a static variable/class level variable that is shared amongst all instances?

e.g.

$.widget("ui.staticTest", {
    staticVar:'unchanged',
    test: function(a){
        if(a){
            this.staticVar= a;
        }
        alert(JSON.stringify(this.staticVar));
    }
});

$('#test').staticTest();
$('#parent').staticTest();

$('#test').staticTest('test','changed');
$('#parent').staticTest('test');

in the above, if staticVar were static, $('#parent').staticTest('test'); would alert 'changed' but instead it alerts 'unchanged'.

(this code is on jsfiddle if you wanna have a play: http://jsfiddle.net/alzclarke/Sx8pJ/)

The solutions i can think of myself are ugly:

1) $('body').data('sharedVariable', myData) - this doesn't seem like good practice, what if someone or something clears the body of data 2) store it in the prototype namespace e.g. = $.ui.staticTest.prototype.staticVar = myData; this also rings alarm bells

alzclarke
  • 1,725
  • 2
  • 17
  • 20

7 Answers7

26

Closures is your answer

(function($) {

var staticVariable = '';

$.widget('ui.newWidget', {
    options: {

    },
    _init: function() {
    }

});

})(jQuery);

staticVariable will only be available in the scope of the widget you just defined since it's wrapped up in a anonymous function that's called immediately (like you should already be doing for your jquery/jquery ui plugins.

jedierikb
  • 12,752
  • 22
  • 95
  • 166
Eric Pigeon
  • 1,091
  • 2
  • 10
  • 21
  • 1
    this is a great answer, so simple, and unlike the other answers, it allows static variables to be truly private. – alzclarke May 24 '12 at 17:36
  • 2
    Can I just ask how one would access (set) staticVariable from outside the widget? I need an option which is configurable once for all widget instances. – Marc Nov 03 '12 at 16:10
  • you could make a function to do so and make it have global scope – Eric Pigeon Nov 09 '12 at 16:48
2

Seems that widgets have no shared scope. Whatsoever. So what I would do is define a shared object on the jQuery UI object itself, like you propose yourself.

Note: the shared object name should reflect the widget name and namespace.

$.ui.staticShared = { color: "#fff" };

$.widget("ui.static", {
    color: function (o)
    {
        if (typeof o !== "undefined") $.ui.staticShared.color = o;

        return $.ui.staticShared.color;
    }
});

This is as clean as it gets in my eyes. And sure; there's a risk of people overriding the shared object, but it's not as if the world is going to fall apart if that happens.

If you want some sort of fail-safe/feedback on if the shared object is invalid or has been overridden, you could do a check on the object structure on the _init event.

$.widget("ui.static", {
    _init: function ()
    {
        if (typeof $.ui.staticShared === "undefined")
        {
            alert("$.ui.staticShared is required for this plug-in to run");
        }
    },
});

The _init event is fired whenever an instance is created.

cllpse
  • 21,396
  • 37
  • 131
  • 170
  • hi there, good thought, but here you are declaring and assigning a global variable not a class variable. if i run your code with the line alert(_static); at the end, it also alerts 'bar'. similarly, if any code before the call uses this variable name the value is lost – alzclarke Apr 01 '11 at 06:43
  • @alzclarke: You're right. _Static gets defined on the window object. Nicely spotted :) – cllpse Apr 01 '11 at 09:10
  • better than what i suggested i think... : > – alzclarke Apr 01 '11 at 13:39
1

actually if your variable is an object, it is automatically static... HMMMMMMMM very helpful! (not)... especially considering that variables containing primitives show the opposite behaviour. Must be something to do with the old "the pointer isn't the object" idiom.

ANYWHO...

IF YOUR VARIABLE CONTAINS AN OBJECT {} THE FOLLOWING WILL BE THE CORRECT ANSWER, OTHERWISE, SEE THE OTHER ANSWERS:

STATIC EXAMPLE:

$.widget("ui.staticTest", {
    staticVar:{val:'unchanged'},
    _create: function(){
    },
    test: function(a){
        if(a){
            this.staticVar.val= a;
        }
        alert(JSON.stringify(this.staticVar.val));
    }
});


$('#test').staticTest();
$('#parent').staticTest();

$('#test').staticTest('test','changed');
$('#parent').staticTest('test');

http://jsfiddle.net/alzclarke/Sx8pJ/9/

NON STATIC EXAMPLE:

$.widget("ui.staticTest", {
    _create: function(){
      this.staticVar={val:'unchanged'};
    },
    test: function(a){
        if(a){
            this.staticVar.val= a;
        }
        alert(JSON.stringify(this.staticVar.val));
    }
});


$('#test').staticTest();
$('#parent').staticTest();

$('#test').staticTest('test','changed');
$('#parent').staticTest('test');

http://jsfiddle.net/alzclarke/Sx8pJ/10/

alzclarke
  • 1,725
  • 2
  • 17
  • 20
  • Take note of this example if you're after complex instance variables - primitive types are unique per instance of the widget, but object types are shared between instances. If you need instance object types, make them part of this.options, which is always instance specific – David Laing Jun 20 '11 at 11:07
  • yep - except where this violates encapsulation, in which case your other option is to declare the object variable in the _create or _init methods. – alzclarke Jul 20 '11 at 09:18
  • 1
    Static variables are often constants, which by convention would have ALL_CAPS names. It's ironic that the only ALL_CAPS I see on this page are in this shouty answer. – Michael Scheper Sep 22 '16 at 18:14
0

it seems like, bit misunderstanding about the "this" keyword. Here this keyword making that variable as instance specific. Try the below code.

$.widget("ui.staticTest", {
    staticVar:'unchanged',
    test: function(a){
        if(a){
            staticVar= a;
        }
        alert(JSON.stringify(staticVar));
    }
});

$('#test').staticTest();
$('#parent').staticTest();

$('#test').staticTest('test','changed');
$('#parent').staticTest('test');
JS Mitrah
  • 686
  • 5
  • 12
  • @alzclarke, I tried that, I am getting "changed" in both calls. Am I missing something? – JS Mitrah Apr 01 '11 at 06:30
  • sorry you're right! Good thought, actually the code is declaring and assigning a global variable not a class variable. if i run your code with the line alert(staticVar); at the end, it also alerts 'changed'. similarly, if any code before the call uses this variable name the value is lost. – alzclarke Apr 01 '11 at 06:48
0

//Detailed example explaining static, instance and global variables in the widget scope

$.widget("ui.staticTest", {
    //staticVar is an instance variable, regardless of what it is named as
    staticVar: 'brownMamba',

    _create: function() {

    },

    //test the static variable
    testStatic: function(a) {
        if (a) {
        //Here you're actually creating a new static variable called 
        //staticVar which is associated with the staticTest object as you assign 
        //value to it. 
            //Lemme show you what I mean with an example
            //Here it alerts 'Undefined' as 'staticVar' does not exists
            //don't confuse it with the staticVar: 'brownMamba' declared above, 
            //that one is an instance variable
            alert("Type of $.ui.staticTest.staticVar before assignment: " + typeof $.ui.staticTest.staticVar);

            $.ui.staticTest.staticVar = a; 
            //At this point it alerts the type of 'a', which in our case is  'string'            
            alert("Type of $.ui.staticTest.staticVar after assignment: " + typeof $.ui.staticTest.staticVar);

            //value of instance variable at this point
            alert("typeof this.staticVar: " + typeof this.staticVar);
            alert("Value of this.staticVar: " +  this.staticVar);
            //value of global variable at this point
            //'Undefined' as it does not exist
            alert("Type of staticVar: " + typeof staticVar); //or window.staticVar


        } else {
            alert("value of staticVar in testStatic with no argument: " + $.ui.staticTest.staticVar);
        }
    },

    //test the instance variable
    testInstance: function(a) {
        if (a) {
        //Here you're actually working with the instance variable declared above, 
        //with the value 'brownMamba' 
        //Lemme show you what I mean with an example
        //Here it alerts 'string' as 'staticVar' exists and is assigned a string
            alert("typeof this.staticVar is " + typeof this.staticVar + " and its 
value is " + this.staticVar);

            this.staticVar = a; 
            alert("typeof this.staticVar after assignment is " + typeof this.staticVar);
            alert("Value of this.staticVar after assignment is " + this.staticVar);
        } else {
            alert("value of this.staticVar in testInstance with no argument: " + this.staticVar);
        }
    },

    //test the Global variable
    testGlobal: function(a) {
        if (a) {
        /*Here you're actually creating a global variable called staticVar*/ 
            //Lemme show you what I mean with an example
            //Here it alerts 'Undefined' as 'staticVar' does not exists
            alert("Type of staticVar before assignment: " + typeof staticVar);

            staticVar = a; //creating a global variable, which will be declared 
in window scope, i.e. window.staticVar 
            //At this point it alerts the type of a, which in our case is a 'string'            
            alert("Type staticVar after assignment: " + typeof staticVar);
            alert("Value of staticVar after assignment: " + staticVar)
        } else {
            alert("value of staticVar in testGlobal with no argument: " + staticVar);
        }
    }
});

//instantiating widget
$('#test').staticTest();
//instantiating widget
$('#parent').staticTest();

$('#test').staticTest('testStatic', 'changed');
//value will be sustained as its associated to the object
$('#parent').staticTest('testStatic');

$('#test').staticTest('testInstance', 'bluemamba');
//here the value doesn't change as its an instance variable and its value is not altered
$('#parent').staticTest('testInstance');

$('#test').staticTest('testGlobal', 'bluemamba');
//here the value is still sustained as the global is created in the previous call
$('#parent').staticTest('testGlobal');

http://jsfiddle.net/hiteshubharani/z5k4E/6/

brownmamba
  • 755
  • 1
  • 8
  • 16
  • hello, rooster's solution is basically to use a global variable, but with a managed namespace. therefore you don't really need to reference it in the constructor. I have modified your solution – alzclarke May 04 '11 at 13:09
  • alzclarke, I would've appreciated you adding your own answer instead of completely modifying my post. – brownmamba May 14 '11 at 02:42
  • @brownmamba fair enough, i've reverted my edit and added a new answer, soz, didn't mean to offend! – alzclarke May 15 '11 at 08:43
  • @alzclarke Your reverted post wasn't close to the original post of mine. But, I appreciate the fact that you atleast tried, and created your own post. Thanks. – brownmamba May 15 '11 at 22:08
  • @brownmamba, i simply used the history dropdown to revert to the previous state and so am not sure what went wrong... can you correct it? – alzclarke May 22 '11 at 17:27
  • @alzclarke Don't worry about that. I actully upgraded my example with detailed explanation. – brownmamba May 23 '11 at 20:17
0

simple implimentation of rooster's soludion:

$.widget("ui.staticTest", {
test: function(a){
    if(a){
        $.ui.staticTest.staticVar= a;
    }
    alert(JSON.stringify($.ui.staticTest.staticVar));
}
});
$.ui.staticTest.staticVar =  'unchanged';


$('#test').staticTest();
$('#parent').staticTest();
$('#test').staticTest('test');
$('#test').staticTest('test','changed');
$('#parent').staticTest('test');
alzclarke
  • 1,725
  • 2
  • 17
  • 20
0

You could use the general JS encapsulation pattern:

$.widget("ui.staticTest", (function() {

    staticVar:'unchanged';

    return {
        test: function(a){
            if(a){
                staticVar= a;
            }
            alert(JSON.stringify(staticVar));
        }
    };
} ()));

Fork of your jsFiddle at: http://jsfiddle.net/pettys/RwKdZ/

Pro: nothing magical going on, not messing with jQuery space at all. Con: indents your widget another level.

pettys
  • 2,293
  • 26
  • 38