Friday, April 26, 2013

FlexBox made awesome

  Today I had to work with a "flexbox" jQuery plugin(FlexBox) - a combination of a text box and a drop-down list.

  Quick background of the plugin:

  • simple data-cache with list of items that just have an "id" and a "value"
  • hybrid use of either supplied object or AJAX requests for data. AJAX data requests just pass "query" (the text box value), page number and page size.
  • does not store $.data() - so you can't grab an instance of the plugin


Shortly after adding it to the solution I found out that my particular use would require tweaking of the source.
Right there we can see that there are two instances of the plugin and "Frame" is dependent on "Roll", so outright we have several shortfalls of the flexbox:

  • Frame's flexbox must know Roll value
  • Frame's JavaScript cache must be Roll-dependent (in this case)
  • AJAX request for Roll/Frames list should also have other parameters, in my case it was an EventId.
  • Plugin works only with simple AJAX enabled "sources" that just return a string, and I would have like to use ASP.NET WebMethods and the ScriptManager generated methods that are already on page.
  • minor : no option to add a "placeholder" attribute to the input field
The one thing I did not want to do is inject any kind of static code or if statements that would make this plugin unusable outside of this page. Firstly, I created C# classes for working with flexbox - data and AJAX request objects.

[Serializable]
public class FlexBoxValue {
    public int id { get; set; }
    public string name { get; set; }
}
[Serializable]
public class FlexBoxReturnResult {
    public int total { get; set; }
    public FlexBoxValue[] results { get; set; }
}
[Serializable]
public class FlexBoxGetPostObject {
    public string query { get; set; }
    public int pageNumber { get; set; }
    public int pageSize { get; set; }
}
[Serializable]
public class FlexBoxGetRollDataPostObject : FlexBoxGetPostObject {
    public string roll { get; set; }
    public long eventId { get; set; }
}

The "FlexboxGetPostObject" is a clone of the existing simple functionality that just has the three listed parameters, which I expanded for my new WebMethod to have two extra parameters.
The Rolls flexbox was the easier out of the two because I could load the list of rolls on Page Load and just using what was already in the plugin bind the data.
$("#txtRoll").flexbox(QOE.EventInfo.Rolls, {
    "width": 90,
    "placeholder": "Roll",
    queryDelay: 200
});

QOE.EventInfo.Rolls is a serialized "FlexBoxReturnResult" C# object, same object I will use for fetching list of Frames per roll. So far, so good.

It must be mentioned that above code that instances flexbox has "static"(?) parameters, meaning once flexbox is initialized none of the options could have varying values - like Roll dependency for the Frame's flexbox. Also as you would imagine, the internal JS cache for Frames would not depend on anything but its own value, so once "0" search for Roll 001 is cached, you're out of luck for other roll values. So that's why we end up with :
$("#txtFrame").flexbox("custom", {
    method: "PageMethod",
    pageMethod: PageMethods.GetRollData,
    extraProperties: {
        roll: (function () { return $("#txtRoll_input").val(); }),
        eventId: QOE.EventInfo.EventID
    },
    cacheKeyPrefix: (function () { return $("#txtRoll_input").val(); }),
    width: 90,
    placeholder: "Frame",
    queryDelay: 200
});


There are several things going on here. method - previously only supported GET or POST. pageMethod is where I am just passing a function out of my ASP.NET generated JavaScript framework. If you recall the C# classes, there's a class for "basic" flexbox usage and one with "extra parameters" for my WebMethod to find frames. Here I'm making an object to send to ASP.NET, assuming (and you should) that you know the object you WebMethod is expecting. Also notice that "roll" and "cacheKeyPrefix" are actually functions -- this is how we get dynamic values into the flexbox (awesome).

"But how can you just pass functions and values as part of the object for a WebMethod consumption, that shouldn't work", is what you're probably thinking. Plugin code changes:
//o are the plugin's options
if (o.method.toUpperCase() == 'PAGEMETHOD') {
    var postObject = {
        query: q,
        pageNumber: p,
        pageSize: pageSize
    };

    if (o.extraProperties !== null) {
        postObject = $.extend(postObject, o.extraProperties);
        for (prop in postObject) {
            if (typeof (postObject[prop]) == "function") {
                postObject[prop] = postObject[prop]();
            }
        }
    }

    o.pageMethod(postObject,
        function (msg) {
            callback(msg);
        },
        function (msg) {
        });
}

The magic happens in the block where we look for extra properties. Firstly, using jQuery's $.extend, we merge our basic postObject with always-present parameters with WebMethod-specific parameters we defined back on the aspx page (no hardcoding of anything). Secondly, we go through each property and see if it's a "function" and if so we call it and grab a value assigning the value back into the same property. If you look above there are two extra parameters for a frame search : a static EventId that does not change, and a value of the roll flexbox that does change. The last thing is to just call the pageMethod and you're done. Not going to paste the cache-key update because it's very similar function-to-value conversion so that the Frame flexbox can cache Frame values per roll dependency.

So in summary, this modified plugin can work with ASP.NET WebServices, send custom objects, have dynamic parameters, and dynamic value dependent internal cache. Awesome.

CodeBlocks