Quantcast
Viewing latest article 4
Browse Latest Browse All 4

Using jQuery Mobile Radio Buttons in LightSwitch

LightSwitch HTML Client, out of the box, lacks support for radio button controls.  Let’s remedy this using the jQuery Mobile radio buttons. There’s a bit more coding involved than I’d like, so I’ve wrapped much of this in a helper function to make it easier to reuse.

You can download an example project here which includes the helper function (in Scripts/lscontrols.js)

Image may be NSFW.
Clik here to view.
AddEditPerson

I make use of the radio buttons in two different cases.  The simplest is Gender, which is a field in the Person table that has three choices defined using the standard LightSwitch Choice List dialog.

Image may be NSFW.
Clik here to view.
ChoiceList

Image may be NSFW.
Clik here to view.
PersonEntity

In the LightSwitch screen designer for the AddEditPerson screen, we select our Gender field and choose “Custom Control” as the control type.  Then we edit the Render function and use this code:

myapp.AddEditPerson.Gender_render = function(element, contentItem) {
    lscontrols.radioButtons(element, contentItem, {
        isHorizontal: true
    });
};

Behind the scenes our lscontrols.radioButtons function is doing the heavy lifting:

(function() {

    function radioButtons(element, contentItem, options) {
        var rbSetName = 'rb' + contentItem.name,
            $rb,
            $rbInputs,
            $div;

        options = options || {};
        options.isHorizontal = options.isHorizontal || false;
        options.choiceList = options.choiceList || contentItem.choiceList;

        function radioButtonChoice(item) {
            var id = rbSetName + item.value,
                html = '<input type="radio" name="' + rbSetName + '" id="' + id + '" value="' + item.value + '" />' +
                    '<label for="' + id + '">' + item.stringValue + '</label>';
            return $(html);
        }

        function label(text) {
            var $l = $("<label class='msls-label-text'>").text(text);

            // to keep this example simple, I'm hardcoding the label alignment to 'Top'
            // if you need a different alignment, you may want to look at the _addAttachedLabel
            // function in the msls js file
            var $d = $("<div class='msls-attached-label msls-label-align-top msls-clear msls-vauto'>");
            return $d.append($l);
        }

        $rb = $("<fieldset id='" + rbSetName + "' data-role='controlgroup' data-type='" +
            (options.isHorizontal ? "horizontal" : "vertical") + "'>");

        options.choiceList.forEach(function(item) {
            $rb.append(radioButtonChoice(item));
        });

        if (contentItem.kind === "Details") {
            // msls doesn't auto add a label for custom group controls
            label(contentItem.displayName).appendTo(element);

            // msls isn't putting the leaf class on custom group controls,
            // though it does use it on custom value controls and its own group controls
            // we have to add it in manually otherwise the alignment will be off
            $(element).addClass("msls-leaf");
        }

        // the classes of the div and parent are important so that things align properly
        // I'm hardcoding for top-labels to keep things simple for now
        $div = $("<div class='msls-clear msls-vauto'>").append($rb);
        $div.appendTo(element);

        // bind so that changing radio buttons changes the contentItem value
        $rbInputs = $rb.find("input");
        $rbInputs.change(function() {
            var newValue = $(this).val();

            if (contentItem.kind === "Details") {
                // ids should be unique, so return the first match
                // (note: inefficient... better would be something like underscore/lodash's find)
                contentItem.value = options.choiceList.filter(function(choice) {
                    return choice.value.toString() === newValue;
                })[0].entity;
            } else {
                contentItem.value = newValue;
            }
        });

        // bind so that changing the contentItem value changes the radio button
        contentItem.dataBind("value", function(data) {
            if (!!data) {
                if (contentItem.kind === "Details") {
                    data = contentItem.value.Id;
                }
                $rbInputs.each(function() {
                    this.checked = this.value === data.toString();
                    // make sure control has been initialized first... if so, refresh
                    if ($(this).parent().hasClass("ui-radio")) {
                        $(this).checkboxradio("refresh");
                    }
                });
            }
        });
    }

    window.lscontrols = {
        radioButtons: radioButtons
    };

}());

As you can see in the comments, I’m making many simplifying assumptions in this code – you may need to adjust the code if you require labels or fields to be aligned differently, for instance.

Image may be NSFW.
Clik here to view.
CategoryRelationship

For the more complex usage scenario, consider the zero-or-one to many relationship between Category and Person. If we expect to have only a handful of categories to choose from, radio buttons might be suitable. It’s a bit trickier, since we need to first retrieve the list of categories via query, then build a choiceList and pass it as a parameter to our lscontrols.radioButtons() function.

So in this case, the render function for our Category custom control looks like this:

myapp.AddEditPerson.Category_render = function (element, contentItem) {
    myapp.activeDataWorkspace.ApplicationData.Categories.load().then(function (categories) {
        var choiceList = [];
        categories.results.forEach(function(category) {
            choiceList.push({
                value: category.Id,
                stringValue: category.Name,
                entity: category
            });
        });
        lscontrols.radioButtons(element, contentItem, {
            choiceList: choiceList,
            isHorizontal: false
        });
        $(element).trigger('create');
    });
};

Notice that we have to use $(element).trigger(‘create’) within our query callback function in order to force jQuery Mobile to render the controls. This is because the render function exits before the query has completed.

Another tip: When working with custom controls in LightSwitch, you’ll frequently find that you need to use either “Fixed Size” or “Stretch To Container” when setting width and height. “Fit To Content” (the default) rarely works well with custom controls.

In the sample project, you can observe that the binding between the radio button controls and the underlying data is two-way. I’ve added a standard LightSwitch drop down (for Gender) and modal picker (for Category) alongside the custom controls so you can see it in action.


Viewing latest article 4
Browse Latest Browse All 4

Trending Articles