Custom Controls and Data Binding in the LightSwitch HTML Client (Joe Binder)

Custom Controls and Data Binding in the LightSwitch HTML Client (Joe Binder)

Rate This
  • Comments 16

Custom JavaScript controls allow you to create user experiences that are tailored to specific use cases and business needs. Given the wealth of code samples, control frameworks, and partner control offerings available today, we wanted to ensure that existing controls could be used as-is within the LightSwitch HTML client.

Having heard some great feedback through the forums and individually, I thought it’d be useful to provide an overview of LightSwitch’s custom control support and share a few tips-and-tricks that have proven useful in my own development.

You can download the LightSwitch HTML Client here.

If you’re new to LightSwitch or the HTML client, you might want to check the following articles before reading through this article:

An Overview of UI Customization Events

UI Customization in the LightSwitch HTML client is accomplished through the render and postRender events. Here’s a basic set of guidelines to help you decide which event to use for a given scenario:

- If you need to insert new elements into the DOM, use the render event. The render event allows you to programmatically create and insert an arbitrary set of DOM elements. For example, if you want to use a control from jQuery UI, use the render event to instantiate the control and insert it into the DOM.

- If you need to augment or otherwise modify DOM elements created by LightSwitch, use the postRender event. The postRender event is commonly used to add a CSS class or DOM attribute to existing DOM elements: for example, you may want to add a jQuery Mobile class to a DOM element created by LightSwitch. We’ll also use the postRender event to change the screen header dynamically later in this article.

A First Step

Let’s jump in and create a simple example to demonstrate how the render event works.

Connect to existing data

We’ll be using the Northwind database for all of the examples in this article. If you don’t have the Northwind database installed, you can download it here.

1. Create a new HTML Client Project (VB or C#)

image

2. Select “Attach to external data source”

clip_image001

3. Select “Database”

4. Enter the connection information for your Northwind database.

5. Select all entities and click Finish.

Build some basic screens

1. Right-click on the HTML Client node in the Solution Explorer and select “New Screen”

2. Select the “Browse Data Screen” template, using the “Order” entity as a Data Source

4 - New Browse Orders Screen

Inserting a Custom Control and Handling postRender

With the screen created, the first thing you’ll notice that each control created on the screen has an associated postRender event in the “Write Code” dropdown.

image

If you need to change or add CSS classes or other DOM attributes on any of the content items shown in the designer, use the postRender event. We’re going to focus on the render event today, though, as it’s the most effective means of creating customized UI in LightSwitch and seems to generate the most interest in the forums.

The render event is only available for custom controls, so we need to add a custom control to the screen before we can use it.

1. Change the default Summary control for the order item to a custom control:

clip_image007

2. Now open the “Write Code” dropdown and select the “RowTemplate_Render” event.

clip_image008

The code stub for the render event includes two parameters, element and contentItem.

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

The element represents the DOM element-- the <div>-- into which our custom UI will be generated. The contentItem represents the view model to which our custom control will be bound. In this example, the content item represents a single order item in the list. Let’s start by generating our list items to show the order date.

myapp.BrowseOrders.RowTemplate_render = function (element, contentItem) { 
var orderDate = $("<p>" + contentItem.value.OrderDate + "</p>");
orderDate.appendTo($(element));
};

On the first line, we’re using jQuery to create a paragraph element that contains the order date. Since this custom control is hosted inside a list, the content item represents a single Order. “contentItem.value” returns the order instance, and contentItem.value.OrderDate returns the order’s date. Once our custom HTML is created, we use jQuery to append it to the placeholder <div> element encapsulated in the element parameter. We can now run the application and see the list items displaying the respective order date.

Displaying Navigation Properties

Displaying the order date was a good first step, but it probably makes more sense to display the Customer for a given order. Since the Order has a “Customer” navigation property, we can probably just update our code to display the Customer’s CompanyName instead of the date:

myapp.BrowseOrders.RowTemplate_render = function (element, contentItem) { 
var orderDate = $("<p>").text(contentItem.value.Customer.CompanyName);
 orderDate.appendTo($(element)); 
};

Unfortunately, this doesn’t work; a list of empty list items is displayed:

clip_image010

Let’s set a breakpoint in our code and use the Watch Window to see what’s going on:

10 - undefined customer

Our list items are blank because contentItem.value.Customer is “undefined”. Typically “undefined” errors are rooted in typos in one form or another, but in this case the binding path is correct. In fact, the problem is that the Order.Customer property is not loaded. We need to understand a bit more of the LightSwitch view model to understand why the property isn’t loaded and how we can fix the problem.

When we build LightSwitch UI using the designer, LightSwitch figures out the data elements that should be loaded on the screen based on the controls in the layout tree. For example, if we were to manually drag the Order.Customer property onto the screen, LightSwitch would make sure that the Customer is fetched when the screen is loaded. But in this instance, we’re building custom UI and LightSwitch has no way of knowing what data should be loaded; we have to explicitly tell it to load the customer. This is pretty easy to do using the designer:

1. Click the “Edit Query” link for the Orders collection in the designer.

clip_image013

2. Now find the “Managed included data” link in the property sheet for the query.

clip_image014

3. Change “Customer: Excluded (Auto)” to “Customer: Included”

clip_image015

Changing this setting to “Included” tells LightSwitch to fetch the Customer for each order that’s queried; it is effectively the span of the query. Now we can run our application again and see that the Company name is properly displayed.

Render function Tips

1. When referring to the screen’s view model (e.g., contentItem.value.OrderDate), make sure that the casing of view model properties in code matches the casing used in the screen designer exactly.

clip_image016

var orderDate = $("<p>" + contentItem.value.OrderDate + "</p>");

 

3. If you would like to use jQuery to interact with the container div—the element parameter—make sure to wrap it in $(element). The element passed in is not a jQuery object, so you need to create one.

orderDate.appendTo($(element));

4. If you see “undefined” errors, inspect the “undefined” element under the debugger to ensure that the code does not include a typo. The Visual Studio Watch window is a great tool for this purpose.

5. Whenever a custom control is displaying navigation properties, always include the navigation property in the parent query’s span by using the “Manage included data” feature.

An Introduction to Data Binding

Thus far we’ve been adding custom UI inside list items, but many scenarios will call for custom UI on a standalone screen. It’s possible to replace any UI on a LightSwitch screen with the exception of the header/title using the render event described above, but we have some new challenges to deal with when working outside the context of the list. In particular, it’s much more likely that the data we’re displaying in our custom control will change over the lifetime of the screen.

For example, consider a scenario where we’re displaying the order’s ShippedDate on the OrderDetail screen:

1. The user gestures to “Ship It” on the order detail screen.

2. The “Ship It” button sets the order’s ShippedDate to the current date.

If we use the above approach, our UI will not reflect the new ShippedDate because the render function is only called when the control is created. LightSwitch’s databinding APIs are a much better option for this scenario. In fact, it’s best to always use data binding when displaying LightSwitch data to guard against scenarios where data is loaded or changed asynchronously.

Add a detail screen

1. Add a new screen to the application

2. Select the “Add/Edit” screen template and choose “Order” as the data source

14 - View Order Screen

3. Switch back to the BrowseOrders screen and select the OrdersList

4. Click the “Tap” action link in the property sheet

15 - Edit tap action

5. Configure the tap action to launch our “EditOrder” screen as follows:

clip_image021

6. Now switch back to the Edit Order screen and select the top-level RowsLayout and gesture to handle the postRender event

image

Set the screen title dynamically

As our first introduction into LightSwitch databinding, we’re going to set the title of the screen dynamically. We could grovel through the DOM to find the element that contains the screen title, but it’s much easier to update the underlying view model property to which the screen title is bound: “screen.details.displayName”

Add the following to the postRender function:

myapp.EditOrder.Details_postRender = function (element, contentItem) { 
contentItem.dataBind(
"screen.Order.ShipAddress",
function (newValue) { contentItem.screen.details.displayName = newValue; });
};

The dataBind(…) function accepts a binding path as a string and a function that will be called when the corresponding value is loaded or changed. However simple, the dataBind method saves us from manually hooking up change listeners to the view model. If we run the application, we’ll see the screen title change in accordance with the ShipAddress.

Databinding a scalar field

We can extend the above example to add a new data-bound custom control to the screen.

1. Add a new custom control to the Details tab of the EditOrder Screen

image

2. Set the binding path for the control to the ShippedDate

clip_image024

3. Open the Write Code dropdown and select “Order_ShippedDate_Render”

4. Add the following code to the render function.

myapp.EditOrder.Order_ShippedDate_render = function (element, contentItem) { 
var shippedDate = $('<p id="shippedDate"></p>');
shippedDate.appendTo($(element));
contentItem.dataBind(
"stringValue", function (newValue) { shippedDate.text(newValue); });
};

There are a few subtle differences between this code and the earlier example that set the screen title:

1- contentItem in this example represents the ShippedDate property, so the binding path is relative to the ShippedDate.

2- We’re binding to the “stringValue”. As its name implies, the string value is a formatted string representation of the underlying property value. Binding to “stringValue” instead of “value” is advantageous when using a text control, because “stringValue” will give us a properly formatted string; whereas simply displaying the JavaScript Date object in this case does nothing—setting the “text” attribute of a <p> to something other than a string will throw an error.

Two-way data binding

Two-way data binding works is just an extension of the above example. We’ll just change the paragraph element to an <input> and listen for its change events:

myapp.EditOrder.Order_ShippedDate_render = function (element, contentItem) { 
var shippedDate = $('<input id="shippedDate" type="datetime"/>');
shippedDate.appendTo($(element));
// Listen for changes made via the custom control and update the content
// item whenever it changes
shippedDate.change(function () {
if (contentItem.value != shippedDate.val()) {
contentItem.value =
new Date(shippedDate.val()); }
});
// Listen for changes made in the view model and update our custom control
contentItem.dataBind("stringValue", function (newValue) { shippedDate.val(newValue); });
};

When the input value changes, we set the underlying content item’s value. In this example, the content item maps to a date field, so we need to assign it a date object.

Databinding Tips

Although data binding in LightSwitch only entails a single API, there are a number of things to keep in mind when you’re using it. Let’s recap:

· Always use data binding when displaying LightSwitch data. There is no guarantee that the data you’re trying to display in a render event will be loaded when your render event is fired; databinding allows you to guard against cases where data isn’t loaded when your control is created or when that data changes elsewhere in the application.

· Bind to the stringValue property on the contentItem when displaying data as text. The stringValue gives you the formatted string representation of whatever type you’re displaying.

· When setting view model values, make sure the source and target property values are the same type; otherwise the assignment will fail silently. (See our two-way databinding example above.)

· Whenever possible, set the binding path of your control in the designer and use relative binding in code. Setting the binding path in the designer has several advantages: (1) it tells LightSwitch the data you’re using so it can be loaded automatically; (2) it allows your custom control code to be more generalized and re-used more easily—you aren’t hard-coding the binding to a particular screen; (3) relative binding allows you to easily access relevant content item properties, such as the stringValue and displayName.

Interacting with “live” DOM elements

jQueryMobile and LightSwitch do most of the heavy lifting required to manipulate the DOM, but you may run into scenarios that require you to interact with the “live” DOM. The good news is that the full extent of frameworks like jQuery are at your disposal within LightSwitch. It is important to keep in mind, however, that the DOM that we see inside our _render and postRender methods is not, strictly speaking, the “live” DOM—it’s the DOM as it exists before jQueryMobile expands it. While this is generally a good thing—it enables us to use jQueryMobile classes and other DOM annotations in our code—it does mean that we have to wait for jQueryMobile to complete its expansion before the DOM is live.

Let’s walk through a simple example that popped up on the forums to illustrate the problem. Consider that you want to set focus on our ShippedDate custom control from above. Easy, right? There’s a jQuery focus event we can probably just call…

myapp.EditOrder.Order_ShippedDate_render = function (element, contentItem) { 
var shippedDate = $('<input id="shippedDate" type="datetime"/>');
shippedDate.appendTo($(element));
// Listen for changes made via the custom control and update the content
// item whenever it changes
shippedDate.change(function () {
if (contentItem.value != shippedDate.val()) {
contentItem.value =
new Date(shippedDate.val()); }
});
// Listen for changes made in the view model and update our custom control
contentItem.dataBind("stringValue", function (newValue) { shippedDate.val(newValue); });
shippedDate.focus();
};

If we run the application, you’ll notice that our custom control doesn’t receive focus as expected. The problem is that the control isn’t visible and/or “live” yet so the browser can’t set focus on it. We can work around this by wrapping the call—and any other similar types of operations—in setTimeout(…).

myapp.EditOrder.Order_ShippedDate_render = function (element, contentItem) { 
var shippedDate = $('<input id="shippedDate" type="datetime"/>');
shippedDate.appendTo($(element));
// Listen for changes made via the custom control and update the content
// item whenever it changes
shippedDate.change(function () {
if (contentItem.value != shippedDate.val()) {
contentItem.value =
new Date(shippedDate.val()); }
});
// Listen for changes made in the view model and update our custom control
contentItem.dataBind("stringValue", function (newValue) { shippedDate.val(newValue); });
setTimeout(
function () { if (shippedDate.is(':visible')) { shippedDate.focus(); } }, 1000);
};

While this might seem arbitrary, it’s the simplest way to workaround issues of this sort; keep it in mind if you need to interact with the live DOM.

Wrapping Up

Custom controls and databinding are powerful capabilities within LightSwitch; they are the escape hatch that allow you to create differentiated user experiences atop the default LightSwitch client. It’s our hope that you find the entry points described here useful in your application development. If you run into any issues, head on over to the forums and we’d be more than happy to help you.

As a follow-up to this post, we’ll dive into the CSS side of UI customization and show how to use custom CSS in conjunction with the code you’ve seen today.

Thanks for your support and taking the time to try out the HTML Client!

- Joe Binder, Program Manager LightSwitch Team

Leave a Comment
  • Please add 5 and 5 and type the answer here:
  • Post
  • Thanks!

  • Silverlight development terminated?

  • Silverlight development is by no means terminated; we just chose to highlight the HTML client-specific capabilities in this post because it's a new feature with very little documentation at this point.

  • Joe - I was in .NET, focusing more on Mobility since last year and while turning back to LS, it gives really a good feel with the support for Html client. I just did a blog post around last month ( blogs.jinishans.com/.../open-source-tools-in-mobility-why.html ) saying .NET/JEE field left this space unattended. After looking Html client and you folks using jQuery/jQM extensively, I feel I was ashamed of not keeping myself updated. I need to do another post in detail on SL Html client.

    Awesome work folks. I look for more posts from your team on how to use other Html 5 frameworks like Sencha Touch, to get an idea how to do it, though you've mentioned in another post it's very much possible.

    BTW, is there any chance you folks are going to provide native Theme (L&F) support for Windows 8, WP 8, iOS & Android or it's upto the HTML 5 framework (jQM/Sencha) purview.

  • Hi Joe,

    I left a comment on the suggest site for LS, using the silverlight client heavily but assume my suggest will also have a big improvement for the web client product in terms for server requests and how its  hooked up to the binding(sorry havent had time to use html client but it looks awesome).

    Please can you give me some feedback on my comment e.g. possibly we're doing something wrong. I am not sure why this issue has not been brought up earlier and spotted on LS1 comming from a hardcore web asp.net background, the multiple requests to render a page is bad for our perf and with odata bloating the response (we're gzipping) it not nay better in LS2.

    - please see the comment here:

    visualstudio.uservoice.com/.../3092582-improve-the-request-model-in-lightswitch

    thanks and let the team know great effort!

  • I need some help. I am developing a web app on lightswitch. So far I have been able to create a property with choice list (Texas, Colorado) a user can select from. I have append the whole thing to a screen. I would like to add a computed property. the computed property will need to use a numerical value that is binded to the state. Could you help me in other words; Texas needs to be 500 as an example and Colorado 450.

    Thank you

  • Hi rennigeb,

    I'm not sure what is the purpose of the computed property, could you explain more? if you only care about showing the numeric value of the statue, you can save it as the value of the item in the choice list and save the state name as description of the same item.

  • @rennigeb - For this situation you might want to look into using a simple choice list (for fixed values) or a related lookup table (for large dynamic lists).

    msdn.microsoft.com/.../ff852006.aspx

    msdn.microsoft.com/.../ff961882

    Computed properties in the HTML client are screen-only and are not saved to the data source. Here's an example of doing that if that's what you want: lightswitchhelpwebsite.com/.../Computed-Properties-With-the-LightSwitch-HTML-Client.aspx

    HTH,

    -Beth

  • Hi,

    very useful post but how to I include data for the non editable query of an edit screen?

    Indeed, my screen obect is Document. Document has a owner and the owner has houses. I'm using a custom control an the edit screen of Document and I have acess to contentItem.value.Document.Owner1 but I cannot loop on contentItem.value.Document.Owner1.Houses. Is there a way to force houses to be loaded by the screen?

    Tks.

  • Hi Joe

    Great post, very useful. I just have a bug of display when I enter a value in a inputBox (type='number'): the inputBox is smaller than before the input. Do you have an idea where is the thing which causes that?

    Thank you in advance.

  • @what about edit screen - Check out this post for info on how to loop through collections lightswitchhelpwebsite.com/.../HUY-Volume-III-ndash-Popups-Dirty-Visual-Collections-and-Using-Prototypes-To-Calculate-Child-Collections.aspx

    @Rom's - You can set the sizing of controls on the screen designer properties windows under "sizing". By default it's set to "Stretch to container" but you can also set a fixed size. Also check that the size of your contain (i.e. Row or Column layout) isn't too small.

    HTH,

    -Beth

  • Object doesn't support this property or method appendTo in visual studio 2013 html client .

    here is my code

    myapp.SelectedCategorySoftware.Price2_render = function (element, contentItem) {

       // Write code here.

       var slider = "<input type='range'  value='60' min='0' max='100' />"

       slide.appendTo($(element));

    };

    what is the issue?

  • Hi GHR_ARASH,

    Your object is a string. String don't have a method of ApplyTo.

    Make it a JQuery object like this:

    var slider = $("<input type='range'  value='60' min='0' max='100' />");

    Refer http://jquery.com/ for more details.

    Hope this solves your problem. And you are always welcome to ask questions on our forum(social.msdn.microsoft.com/.../home).

  • Hi Joe,

    Great article!  I'm working on a POC for a client to convince them that we should move from the Silverlight to HTML Client, but need more robust Grid and Tree CRUD controls.  Therefore, would appreciate any insight/direction on how to incorporate Kendo UI Web controls.  It appears possible through Custom Controls, but I'm too new to this to understand the exact approach, or if it's even feasible.  Would really appreciate your feedback and help.  Thanks!

  • Lets say you wanted to create a custom control to input a custom table or even take the current table and want to change the array of data that is being displayed. Current data displayed shows dates and readings. I want to be able to create a new array after sorting and compressing by month/year and sum Readings for that month/year and bind to new table created or current table that is there.

    I have filtered query that I created and added it to screen. On the render event of custom control I create a new table and add to element. Is there a way to create the binding to my created table with new array with msls.bind() or contentItem.databind()? Or if I am using a table instead of custom control can I create the binding to the tables data array to newly created array in render code using msls.bind() or contentItem.databind()

    Thanks

Page 1 of 2 (16 items) 12