Bruce Stemplewski
Bruce Stemplewski

Reputation: 1343

Common initialization / common variables in jQuery UI Widget factory?

I have a jQuery UI plugin that is working successfully. I want to convert it to jQuery UI Widget Factory. My current widget allows me to apply one initialization across all of the selected elements.

I do this just before my this.each statement. Works great.

I call my current plugin like this:

 $("input[title *= 'Cost Center Name']").simpleDialogListSP({spListName : "Cost Centers",
                                                        dialogTitle : "Cost Centers"


                                                    }); 

I have multiple input boxes with the title of Cost Center Name X. My plugin goes out and grabs a list of values that are used across all of the selected input boxes.

Now I am trying to do the same thing in Widget Factory but the only function I can find is _create which gets called for each selected element. Is there any way to have one common initialization function in Widget factory across all of the selected elements? Also to have variables that are common to all of the elements as a group?

Hopefully I have explained this correctly.

P.S. I do not want to have to pass the list as an option. I want things to be as simple as possible for the end user programmer. The current plugin makes an API call to SharePoint. I rather that complexity remain with the plugin.

I think I may have found a hint here: how to declare a static/class variable in a jquery ui widget factory widget

But xxxpigeonxxx's solution does not work because the variable is common to all widget instances and I need it to be common to just the selected elements.

Upvotes: 0

Views: 841

Answers (1)

DanielST
DanielST

Reputation: 14133

This is pretty long. The end addresses your question, the start addresses what I understand to be a lack of understanding about the jQuery Widget Factory.

Widget Factory

The jQuery Widget Factory is fairly strict (compared to vanilla JS, anyway). This is a good thing since it gives all widgets nearly the same interface, regardless of the author.

There are some things I don't think you understand about Widgets.

  • They are stateful "objects"
  • They are "rooted" on a single element (not a collection).

jQuery plugins are utility functions that act on jquery objects to do whatever you want. A jQuery widget creates a persistent object on an element. At least, this is how they are intended to be used.

So, using form validation as an example:

Plugin

Here, validate is called every time the form is submitted.

$('.myform').on('submit',function(e){
    $(this).find("input").validate(); //checks inputs and adds invalid messages.
    if($(this).find(".invalid").length){ 
        e.preventDefault(); //prevent submit if invalid inputs
        return false;
    }
});

Widget

Here, the inputs are made into validate widgets. This could be setup so that they autovalidate themselves on change, on submit, or whenever.

$(".myform input").validate({ onchange: true }); // inputs will validate themselves on change.
$('.myform').on('submit',function(e){
    if($(this).find(".invalid").length){ 
        e.preventDefault(); //prevent submit if invalid inputs
        return false;
    }
});

Alternate Widget

But you might do something like this instead:

$(".myform").myform(); // Makes the whole form a single widget that handles all validation

// Now we can listen on custom events
$(".myform").on("myformvalid",function(e,data){
    /*Do something now that the form is valid*/
}); // or
$(".myform").on("myformsubmit",function(e,data){
    /*Handle a submit*/
});

These events are namespaced and clean themselves up when destroyed.

You could also make the form widget very general purpose by using options:

$(".myform").myform({
    validate: { // specify which fields to validate and how
        "cost": "number",
        "name": "word",
        "email": isEmail // function
    },
    url: "/submit.php" // have the widget handle submitting to server
});

Note that you can do all of this with a jquery plugin too. The widget factory is just an extension that automates some of it and standardizes the interface.

Back to your question

I do not want to have to pass the list as an option. I want things to be as simple as possible for the end user programmer.

Using options is not only simple, it's the standard for all widgets. And it supports defaults.

Assuming you want to create a dialogue with the list of inputs

$('<div>').myDialogue({inputs:"input[title *= 'Cost Center Name']"}).appendTo(/*somewhere*/);

This means the widget handles getting the data from the inputs and the user only has to specify which inputs.

If you need it more flexible, have it take an array of the input data instead of the input elements:

var listArray = $("input[title *= 'Cost Center Name']").map( function(){
    return $(this).val();
});
$('<div>').myDialogue({list:listArray}).appendTo(/*somewhere*/);;

Or a hybrid approach. Give it the elements and an optional parsing function:

$('<div>').myDialogue({
    inputs:"input[title *= 'Cost Center Name']",
    parseInputs: function(input){ // Optional, has a default
        return $(input).val();
    }
}).appendTo(/*somewhere*/);

If you want to make individual widgets for each input

The jQuery Widget Factory API has a good example for how to share options between instances.

var options = { modal: true, show: "slow" };
$( "#dialog1" ).dialog( options );
$( "#dialog2" ).dialog( options, { autoOpen: false });

Both these dialogues get the options object, and dialog2 gets extended options.

Addendum:

This only covers how they are meant to be used and what the interfaces typically look like. I didn't go into actually implementing them, but there's plenty of documentation on that. Just understand that the Widget Factory makes it difficult to do non-standard things.

Edit

Here's a full example of options use. http://jsfiddle.net/t5Lhquf1/

$.widget("me.mywidget", { // Create the widget
    options: {
        static1: "1", //defaults
        static2: "2",
        static3: "3"
    },
    // Constructor. This is called when mywidget is initialized
    _create: function () {
        this.element.text(this.options.static1); // Set the elements text as static1
    }
});

$(".a").mywidget({ //call it on all .a <p>
    static1: "I am part of the .a selection"
});

$(".b").mywidget({ //call it on all .b <p>
    static1: "I am part of the .b selection"
});

Edit 2

http://jsfiddle.net/t5Lhquf1/3/

Ok, caching system to give you "static" keyed variables. The two functions _serverRequest and _serverRequestOrFromCache could be put into the widget if you prefer. But _cache needs to be in the closure outside the widget.

It requires you to pass a key as an option. The key can be anything unique. Widgets cannot see each other. Even when they are created in one $('.selector').widget().

Also, this can be improved and possibly simplified. But it would depend on details about your app that I don't know.

(function () {

    // _cache is static for all widgets
    // structure is {key1:{data:{},status:"",callbacks:[f1,f2,...]}, key2:{...}}
    var _cache = {};

    // Do a server request. Run callbacks for this key on finish.
    function _serverRequest(key, query) {
        console.log("Server Request"); // So we can count how many were performed
        _cache[key].status = "inprogress"
        var fakeresults = "result of " + query + " with key: " + key;
        // $.json(...) // Do your actual query here
        // .done( function(){} )

        setTimeout(function () { //simulate a server request
            _cache[key].data = fakeresults;
            _cache[key].status = "done";
            $.each(_cache[key].callbacks, function () { // run all callbacks in queue
                this(_cache[key].data);
            });
            _cache[key].callbacks = []; // empty the queue
        });
    };

    // Do a server request unless one is already being performed
    // by a widget with the same key. If it is, wait for it to finish
    // and use it's result.
    function _serverRequestOrFromCache(key, query, callback) {
        if (_cache[key] == null) { // if this key is not cached
            _cache[key] = {
                status: "",
                callbacks: [callback],
                data: {}
            }
            _serverRequest(key, query);
        } else if (_cache[key].status === "inprogress") { // if another widget is getting data
            _cache[key].callbacks.push(callback);
        } else if (_cache[key].status === "done") { 
            // This could be changed to use the cache if the query is the same.
            // Currently it only uses the cache if a query is in progress.
            _cache[key].callbacks.push(callback);
            _serverRequest(key, query);
        }
    };

    $.widget("me.mywidget", { // Create the widget
        options: {
            key: "default"
        },
        mydata: {},
        // Constructor. This is called when mywidget is initialized
        _create: function () {
            var that = this;
            _serverRequestOrFromCache(this.options.key, "myquery", function (data) {
                //callback function
                that.mydata = data;
                that.element.text(data);
            });
        }
    });
})();


$(".a").mywidget({
    key: "a"
});

$(".b").mywidget({
    key: "b"
});

Upvotes: 1

Related Questions