0

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.

Community
  • 1
  • 1
Bruce Stemplewski
  • 1,343
  • 1
  • 23
  • 66
  • What exactly do you want the widget to do? Widgets are stateful, they don't just perform an operation and change something (like what your plugin seems to do). Does it store data and methods attached to an element for later use? – DanielST Dec 16 '14 at 16:43

1 Answers1

1

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"
});
DanielST
  • 13,783
  • 7
  • 42
  • 65
  • Thanks for putting all work into your answer. I guess I am dense but you lost me after the first paragraph. All I really need is a static variable across selected elements in a single selection. So if I make separate selections there would be different values in those "static" variables. – Bruce Stemplewski Dec 17 '14 at 15:51
  • @BruceStemplewski Ok, maybe I missed the mark. What goal are you trying to achieve by turning your plugin into a Widget? – DanielST Dec 17 '14 at 15:53
  • Oh, note that options are deep copied. If you need a way around that, let me know. – DanielST Dec 17 '14 at 16:02
  • @BruceStemplewski Ok, I've added a section. – DanielST Dec 17 '14 at 16:18
  • @slicetoad The section under edit using options? I do not see how that allows them to remain static for a selected groups of elements. In fact I tested it and I an getting a different scope variable for each element in one single selection (like $("input[title *= 'Cost Center Name']") might select several elements for my widget. – Bruce Stemplewski Dec 17 '14 at 18:36
  • Perhaps I should post this as a new question but is there a way for the widget to know it's jQuery selection string? Example can the widget see something like "$("input[title *= 'Cost Center Name']")"? Have an idea to use that as a "key". – Bruce Stemplewski Dec 17 '14 at 18:39
  • @BruceStemplewski Not without it being a bit hacky (or using the deprecated `$().selector`). Could you make a fiddle showing what you want with comments showing expected outputs? – DanielST Dec 17 '14 at 18:52
  • Oh! you want all the elements selected by `$("input[title *= 'Cost Center Name']")` to make *one* widget. Is that right? Because that is not how widgets work. A widget is made *for each* element in the selection. – DanielST Dec 17 '14 at 18:58
  • @BruceStemplewski something like: http://jsfiddle.net/5bs3ryb3/2/ might be what you want. It creates a widget on a SINGLE element and passes a selector for what needs accessed. – DanielST Dec 17 '14 at 19:06
  • In your example, I would want the widget to be able to get "#container" within _create without passing it as an option variable. – Bruce Stemplewski Dec 17 '14 at 20:32
  • Since it's an ID, `this.element.prop("id")` would work. You can get class lists too. You also have direct access to the element via `this.element`. But if you want a general selector string that you can plug into `$('here')`, this isn't really supported by jquery. See http://stackoverflow.com/questions/2420970/how-can-i-get-selector-from-jquery-object – DanielST Dec 17 '14 at 20:36
  • And no I do not want to make one widget from a group of selected elements. What I do want is to have them share a common store. What I do is to query a sharepoint db. I don't want to have to query 3,4,5..... times for the same data, Just once and all of the selected elements will share the same data. Or is it still considered one widget when I apply to a group of elements with one call but multiple selections? Sorry all new to this. – Bruce Stemplewski Dec 17 '14 at 20:37
  • So each input becomes a widget that needs to access all the other inputs in order to not repeat queries? First, make sure they each need to be a widget (do they each do things independently of the others?). If so, take a caching approach. I'll edit my answer with an example. – DanielST Dec 17 '14 at 20:49