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)

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.

ChoiceList

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.

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.

7 thoughts on “Using jQuery Mobile Radio Buttons in LightSwitch

  1. Lloyd Derbyshire

    Great work thx Jewel. Downloaded and ran your sample app and it worked well. Now to check the code and have a play. Much appreciated.

  2. Anurag Srivastava

    Jewel you are stealing my heart dear. I am a newbie in LightSwitch and your articles are rocking. This one is more than impressive.

Comments are closed.