Enhancing LightSwitch Controls with jQuery Mobile (Michael Zlatkovsky)

Enhancing LightSwitch Controls with jQuery Mobile (Michael Zlatkovsky)

Rate This
  • Comments 18

UPDATED on AUGUST 1, 2013 based on Visual Studio 2012 and Update 3.

One of my favorite things about the LightSwitch HTML Client is how easy it is to create forms over data the “LightSwitch way”, and then jump straight into HTML for a bit of custom tweaking.  Because the HTML Client makes use of jQuery and jQueryMobile, these two framework are always at your disposal.  Of course, you can leverage other frameworks as well, but for this post we’ll focus on building LightSwitch custom controls that “wrap” jQueryMobile functionality.  The techniques and patterns you see here will transfer to other frameworks and control types as well.

If you’re just getting started with the LightSwitch HTML Client, you may want to peruse through some of the previous blog posts first.

OK, let’s get started!

Creating a table with two basic screens

For this post, we’ll start with a new “LightSwitch HTML Application” project, and create a very simple “Customers” table within it. This table will keep track of a customer’s name, family size, and whether or not his/her subscription is active.

To create the table, right-click on the “Server” node in the Solution Explorer, and select “Add Table”. Fill out the information as follows:

clip_image001

Next, let’s create a Browse Data Screen, using “Customers” for the data. Right-click on the Client node in the Solution Explorer, “Add Screen…”, and select “Customers” in the Screen Data dropdown:

clip_image002

Let’s create a second screen, as well, for adding or editing customer data. Again, right-click on the Client node in the Solution Explorer, and add another screen. This time, choose an “Add/Edit Details Screen” template:

clip_image003

UPDATE for VS 2012 Update 3:  If you’re using VS 2012 Update 3, after you click “OK”, you may want to re-arrange the screen layout to only use a single column.  To do so, simply drag and drop the fields in the tree designer into a single Rows Layout (labeled “left”), and delete the “right” Rows Layout:

AddEditCustomer

 

 

Adding screen interaction

Now that we have two screens, let’s add some meaningful interaction between them. Choose the “BrowseCustomers” screen in the Solution Explorer to open the screen designer, and add a new button.

image

In the dialog that comes up, select “Choose an existing method”, and specify “Customers.addAndEditNew”. The “Navigate To” property is automatically pre-filled to the right default: the Customer Detail screen that we’d created.

clip_image005

If you drag the button above the list and F5, you will get something like this:

clip_image006

If you click the “Add Customer” dialog, you will see a screen that prompts you to fill out some information. It makes sense to have the Name be regular text input, but the Family Size and Active Subscription might be better suited for alternate visual representations.

clip_image007

At the end of this blog post, you’ll be able to transform the “baseline” screen above into something like this:

clip_image008

UPDATE for VS 2012 Update 3:  In the latest version of LightSwitch, a boolean field will automatically be created as a native flip-switch out of the box.  So the only control remaining to customize is the Family Size slider.

 

Setting up a Custom Control

Let’s start with transforming “Family Size” into a Slider control. jQueryMobile offers an excellent slider control, complete with a quick-entry textbox.

clip_image009

This control does not come built-in with LightSwitch, but we can add it as a custom control, with just a bit of render code.

First, navigate to your CustomerDetail screen. With “FamilySize” selected, open the Properties window, and change the control type from “Text Box” to “Custom Control”. You’ll also want to change the width to “Stretch to Container”, so that the slider can take up as much room as it needs.

clip_image011

Now that you’ve switched “FamilySize” to a custom control, navigate to the top of the Property window and select “Edit Render Code”:

clip_image013

A code editor will open, with a method stub automatically generated for you:

myapp.CustomerDetail.FamilySize_render = function (element, contentItem) {
    // Write code here.
};

In the auto-generated function above, “element” is the DOM element (e.g., <div>) that the custom control can write to, while “contentItem” represents view-model information about the item. Thus, we’ll use “element” for where to place the custom control, and “contentItem” for what it represents.

Let’s start with a simple question: assuming you did have a custom control that magically did all the work for you, what parameters would you need to pass to it? Obviously, “element” and “contentItem” from above are necessary. We’d also need to know the minimum and maximum values for the slider… and that’s about it.

A slider control seems like a reasonably generic component, so instead of writing all of the code in this one function, it might be worth separating the generic slider logic from the specifics of this particular instance. Thus, our function above can become:

myapp.CustomerDetail.FamilySize_render = function (element, contentItem) {
    // Write code here.
    createSlider(element, contentItem, 0, 15);
};

Well, that was easy! But now, let’s actually implement the hypothetical “createSlider” function.

Rendering a Slider

The first thing our createSlider function needs to do is create the necessary DOM element. jQueryMobile’s Slider documentation tells us that the Slider operates over a typical <input> tag. So a reasonably first attempt would be to do this:

function createSlider(element, contentItem, min, max) {
    // Generate the input element.
    $(element).append('<input type="range" min="' + min +
        '" max="' + max + '" value="' + contentItem.value + '" />');
}

On F5, you will see that the field indeed renders as a slider. Mission accomplished?

Unfortunately, not quite. If you drag another FamilySize element to the screen (just so you have a regular text box to compare to), you’ll see that the input slider does not seem to do anything. You can drag the slider up and down, yet the other textbox does not reflect your changes. Similarly, you can type a number into the other textbox and tab out, yet the slider is not impacted.

clip_image015

What you need to do is create a binding between contentItem and the slider control. To do this, you’ll actually create two separate one-way bindings: one from contentItem to the slider, and one from the slider to contentItem. Assume for a moment you already have a $slider object, bound to an already-created jQueryMobile slider. The code you would write would be as follows:

 

// If the content item changes (perhaps due to another control being
//     bound to the same content item, or if a change occurs programmatically), 
//     update the visual representation of the slider:
contentItem.dataBind('value', function (newValue) {
    $slider.val(newValue);
});

// Conversely, whenever the user adjusts the slider visually,
//     update the underlying content item:
$slider.change(function () {
    contentItem.value = $slider.val();
});

How do you get the $slider object? The simplest way is to let jQueryMobile do its usual post-processing, but this post-processing happens after the render functions have already executed. You could use setTimeout (which is sometimes necessary – see Joe’s Custom Controls post for an example), but in the case of jQueryMobile controls, we can just use their “init” functions.  For a slider control, the function we want is “slideinit”.  We’ll use jquery’s .one event handler to attach this one-time bit of code to the element initialization

Putting it all together, the final createSlider code is:

function createSlider(element, contentItem, min, max) {
    // Generate the input element.
    $(element).append('<input type="range" min="' + min +
        '" max="' + max + '" value="' + contentItem.value + '" />');

    // Now, after jQueryMobile has had a chance to process the 
    //     new DOM addition, perform our own post-processing:
    $(element).one('slideinit', function () {
        var $slider = $('input', $(element));

        // If the content item changes (perhaps due to another control being
        //     bound to the same content item, or if a change occurs programmatically), 
        //     update the visual representation of the slider:
        contentItem.dataBind('value', function (newValue) {
            $slider.val(newValue);
        });

        // Conversely, whenever the user adjusts the slider visually,
        //     update the underlying content item:
        $slider.change(function () {
            contentItem.value = $slider.val();
        });
    });
}

When you F5, you’ll see that both Family Size controls – the textbox and the custom slider – stay in sync. Success!

clip_image016

 

 

Creating a Flip Switch for a Boolean field

UPDATE for VS 2012 Update 3: In Visual Studio 2012 Update 3, creating a Flip Switch is no longer necessary – a flip-switch control is automatically created for Boolean fields.  Thus, this section is only necessary if you want to customize the labels on the flip-switch, or if you want to learn more about how you would implement a similar control yourself.

 

Having created the Slider, let’s create another control for the Boolean field of “Active Subscription”. Just as with the Slider control, jQueryMobile offers a great Flip Toggle Switch (with customizable labels, if you’d like):

clip_image017

As with the “Family Size” field, we can go to the Active Subscription’s properties and switch it from a Text Box to a Custom Control. I will also change the width to “Stretch to Container”, change the Display Name to just “Subscription”, and finally click “Edit Render Code”:

myapp.CustomerDetail.ActiveSubscription_render = function (element, contentItem) {
    // Write code here.
};

As before, let’s encapsulate the main functionality in a function, that we’ll call from the ActiveSubscription_render method. The information we want to pass in is similar to the Slider. We’ll need “element” and “contentItem”, and we’ll need the analog of a “min” and “max”: in this case, the labels for the true and false states. Reading over the Flip Toggle Switch documentation, we’ll also notice that width must be set explicitly in case for non-standard on/off labels. Thus, we’ll pass in a width parameter as well.

myapp.CustomerDetail.ActiveSubscription_render = function (element, contentItem) {
    // Write code here.
    createBooleanSwitch(element, contentItem, 'Active', 'Inactive', '15em');
};

Now for the fun part – the createBooleanSwitch function. Conceptually, the function is very similar to createSlider.

First we initialize the DOM:

var $selectElement = $('<select data-role="slider"></select>').appendTo($(element));
    $('<option value="false">' + falseText + '</option>').appendTo($selectElement);
    $('<option value="true">' + trueText + '</option>').appendTo($selectElement);

Then, once jQueryMobile initializes the control, we:

1. Set the initial value of the flip switch (we did this in the DOM for the slider, but it’s a little trickier with the flip switch, so I’ve encapsulated it into a shared function that’s used both during initialization and on updating)

2. Bind contentItem’s changes to the flip switch control

3. Bind the flip switch’s changes back to the contentItem

4. One additional step, which we didn’t need to do for the slider: explicitly set the flip switch’s width, as per the documentation.

The resulting createBooleanSwitch code is as follows:

function createBooleanSwitch(element, contentItem, trueText, falseText, optionalWidth) {
    var $selectElement = $('<select data-role="slider"></select>').appendTo($(element));
    $('<option value="false">' + falseText + '</option>').appendTo($selectElement);
    $('<option value="true">' + trueText + '</option>').appendTo($selectElement);

    // Now, after jQueryMobile has had a chance to process the 
    //     new DOM addition, perform our own post-processing:
    $(element).one('slideinit', function () {
        var $flipSwitch = $('select', $(element));

        // Set the initial value (using helper function below):
        setFlipSwitchValue(contentItem.value);

        // If the content item changes (perhaps due to another control being
        //     bound to the same content item, or if a change occurs programmatically), 
        //     update the visual representation of the control:
        contentItem.dataBind('value', setFlipSwitchValue);

        // Conversely, whenver the user adjusts the flip-switch visually,
        //     update the underlying content item:
        $flipSwitch.change(function () {
            contentItem.value = ($flipSwitch.val() === 'true');
        });

        // To set the width of the slider to something different than the default,
        //    need to adjust the *generated* div that gets created right next to
        //    the original select element.  DOM Explorer (F12 tools) is a big help here.
        if (optionalWidth != null) {
            $('.ui-slider-switch', $(element)).css('width', optionalWidth);
        }

        //===============================================================//

        // Helper function to set the value of the flip-switch
        //     (used both during initialization, and for data-binding)
        function setFlipSwitchValue(value) {
            $flipSwitch.val((value) ? 'true' : 'false');

            // Having updated the DOM value, refresh the visual representation as well
            //  (required for a slider control, as per jQueryMobile's documentation)
            $flipSwitch.slider(); // Initializes the slider
            $flipSwitch.slider('refresh');
            // Because the flip switch has no concept of a "null" value
            //    (or anything other than true/false), ensure that the 
            //    contentItem's value is in sync with the visual representation
            contentItem.value = ($flipSwitch.val() === 'true');
        }
    });
}

On F5, we see that our control works. Again, I put in a dummy textbox for “Active Subscription” to show that changes made in one field automatically get propagated to the other. You’ll notice that on initial load, the text field is set to “false”, rather than empty, as before. This is because of the flip switch has no concept of a “null” value, so I explicitly synchronize contentItem to the flip switch’s visual representation in the setFlipSwitchValue function above.

clip_image018

Removing the temporary “Active Subscription”, we get to this final result:

clip_image019

Next Steps:

Hopefully, this post whets you appetite for what you can do with Custom Controls in the LightSwitch HTML Client, and gives you a clear sense on how to wrap arbitrary controls into LightSwitch-usable components. Leave a comment if you have any questions, and have fun with LightSwitch!

- Michael Zlatkovsky, Program Manager

Leave a Comment
  • Please add 5 and 6 and type the answer here:
  • Post
  • Hello.

    I have the same Problem as Luca.

    My slider is bound to a custom property. Everything works fine as Long as i type in the value in the field or Change the slider. Everything is synch. But when I set the value of the field with help of JS, the slider will not update.

    Any ideas?

  • Ahh. sorry.

    With this it works fine:

    ...

               //     update the visual representation of the slider:

               contentItem.dataBind('value', function (newValue) {

                   $slider.val(newValue);

                   //refresh visuals

                   $slider.slider("refresh");    //<<=====!!!!!

               });

               // Conversely, whenever the user adjusts the slider visually,

    ...

  • Thanks Michael for this nice example.  I am going to follow the steps now to try it.  Michael, I have a big need for an Image Slider and was wondering if JQuery has one and could it be adapted to your example here.  I have tried creating one myself but still an amateur with integrating the Java Widgets to LightSwitch HTML.  We badly need a good Image Slider for LightSwitch HTML and just can't seem to find anything other that Jan Van der Hagen's nice example using a collection (which I also can't seem to get to work exactly right) and is not a true Image Slider.  Thank you again for your nice example.

    Ed Piccoli

    enpiccoli@msn.com

Page 2 of 2 (18 items) 12