New LightSwitch HTML Client APIs (Stephen Provine)

Following the recent announcement of the LightSwitch HTML Client Preview 2, we are excited to introduce a set of additional APIs that allow further control over the behavior and capabilities of a LightSwitch HTML-based application. You can download Preview 2 from the Developer Center.

The APIs we included as part of the June Preview primarily covered UI customization through custom rendering of controls in the DOM, post-rendering (i.e. tweaking) of existing DOM elements, and a data binding API to wire the UI up to data. For Preview 2, we have significantly improved the design-time coding experience with better IntelliSense support and debugging of the original source code, introduced a number of additional coding entry points for responding to common events, and built a richer set of APIs for interacting with the application.

Intellisense Support

We know that writing JavaScript code can be hard when you don’t know the shapes of all the objects you are working with. IntelliSense is one mechanism that can be used to help guide developers to discover and use new APIs. In the June Preview, we offered some existing IntelliSense support for the jQuery library but had minimal support for the LightSwitch library and application-specific assets such as entities or screens.

This all changes with Preview 2. Whether navigating the contents of the LightSwitch library object (msls) or understanding the properties on generated entity or screen objects, IntelliSense now offers completion lists with appropriate glyphs and tooltip descriptions for each item. Here are some examples:

Intellisense1

Intellisense2

We still have work to do in this area to ensure IntelliSense always works in all the expected places, such as in callback functions passed to promise objects, but it has been much improved since the June Preview. Please try it out and let us know what you think!

The Entity and Screen created Events

It is a very common scenario to configure defaults for new entities and screens. The June Preview provided no way to plug in code at the appropriate time to implement this. Preview 2 introduces the created event, allowing you to write code immediately after a new entity or screen has been created.

Suppose you have created a new HTML client project and designed an Order entity in the Entity Designer as follows:

Created1

Now, switch the perspective of the entity from “Server” to “Client” using the buttons at the bottom of the designer:

Created2

Having different entity perspectives is a new feature with Preview 2 and allows you to independently configure aspects of how an entity appears on the server versus a client. One of these aspects is code. Specifically, it allows you to write JavaScript-based entity code that can run in the browser instead of using C# or VB which can only execute on the server.

Now in the designer toolbar, locate the “Write Code” dropdown, open it, and click the “created” link. This will open a code file and output the event stub into which you can add some defaulting logic: 

myapp.Order.created = function (entity) {
  
// Write code here.
  entity.OrderDate = new Date();
};

In this case, a new Order on this HTML Client will set its OrderDate property to the current date.

To try this out, let’s first configure the summary property for the entity to show the OrderDate. This is located in the properties window in the entity designer:

Created4

With this done, right click on the Client project, choose “Add Screen…” and add a browse screen for orders, then do the same for an add/edit details screen for orders. Return to the browse screen and add a button that adds and edits a new order entity:

Method1

Created5

If you run this application and click the “Add Order” button, it will launch the add/edit details screen and the order date will be set to the current date.

Custom Screen Methods

While the June Preview enabled a lot of flexibility regarding the user experience via the render and postRender events, it provided no built-in capability to add buttons to your UI that call custom code to perform specific business tasks. Preview 2 introduces custom screen methods that are designed in a similar manner to screen methods in Silverlight. For each content item that represents a button, you can write canExecute code, which determines the visible or enabled state of the button, and execute code, which actually performs the work.

Continuing the example from the previous section, let’s add a custom button that will import some orders from the sample Northwind OData service:

Method2

Method3

If you right click either the screen method or the content item representing the button in the tree, you can choose to “Edit Execute Code”. This takes you to the code editor and produces the necessary stub, into which we can add the necessary code:

myapp.BrowseOrders.ImportOrders_execute = function (screen) {
 
// Write code here.
  var northwind =
“http://services.odata.org/Northwind/Northwind.svc”;
 
return msls.promiseOperation(function (operation) {
    OData.read({ requestUri: northwind +
“/Orders?$top=10″
                 recognizeDates:
true,
                 enableJsonpCallback:
true },
                
function success(result) {
                  
var results = result.results;
                  
for (var i = 0, len = results.length; i < len; i++) {
                     var importedOrder = screen.Orders.addNew();
                     importedOrder.OrderDate = results[i].OrderDate;
                   } 
 
                   operation.complete();
                 },
                
function error(e) { operation.error(e); }); 
    });
};

Since calling an OData service is an asynchronous operation and is potentially long running, this code uses the msls.promiseOperation() function to create a LightSwitch-compatible promise object that represents the operation of calling the OData service and processing the results. This promise is then returned to the LightSwitch runtime. The runtime uses this promise to track the operation, and if it appears to be taking a long time, a progress indicator is shown to the user, blocking additional operations from running and interfering with the current operation.

LightSwitch uses OData to communicate with the middle tier, so all HTML clients already include the datajs client library which provides the OData object for reading and updating data through an OData service. This code simply reuses that API to request data from the external Northwind OData service, then on success, adds the imported orders into the screen’s collection of orders and sets the order date for each one.

Now to illustrate some simple canExecute code, let’s ensure that the user can only perform a single import per instance of the browse screen. First, add a data item to the screen of type Boolean called “ImportPerformed”. Then right click either the screen method or the content item in the tree and this time choose to “Edit CanExecute Code”:

myapp.BrowseOrders.ImportOrders_canExecute = function (screen) {
   // Write code here.
 
return !screen.ImportPerformed;
};

Finally, add a line to the execute code that sets this property to true right before completing the operation:

importedOrder.OrderDate = results[i].OrderDate; } screen.ImportPerformed = true; operation.complete(); },

When the button is initially shown, the canExecute code is called and it returns true:

Method7

When the import operation has completed, the runtime automatically detects that the ImportPerformed property changed and calls the canExecute code again, and this time it returns false, which greys out the button:

Method8

Other Events

The events that have been described so far in this post are considered primary coding entry points for a LightSwitch application. These have end to end support in the designer and are static events, meaning they apply to all instances of asset to which they are attached. For example, the entity created entry point applies to all newly created entities, or a screen method execute entry point applies to all instances of the screen in the running application.

In addition to these static events, there are also instance-based events that can be hooked up from static event handlers. These events are exposed using more traditional JavaScript event patterns, which include both single (obj.onevent) and multi-handler syntaxes (obj.addEventListener()).

The most common instance event in LightSwitch is the “change” event. In fact, since it is so common, we introduced a helper API for attaching to change events – addChangeListener() – that uses addEventListener() under the covers. Since the HTML client does not currently support imperative validation, let’s expand on the Order example and see if we can use a change listener to provide some simple custom validation of the order date.

Open the add/edit details screen for the Order and write the following code in the screen created entry point:

myapp.OrderDetail.created = function (screen) { 
  // Write code here.
 
screen.Order.addChangeListener(“OrderDate”, function (e) {
   
var order = screen.Order, contentItem = screen.findContentItem(“OrderDate”),
    today =
new Date();
    today.setHours(0, 0, 0, 0);
   
if (order.details.entityState === msls.EntityState.added && 
        contentItem.validationResults.length === 0 && 
        order.OrderDate < today) {
          contentItem.validationResults = [
            
new msls.ValidationResult( order.details.properties.OrderDate,
                                       
“Cannot be before today.”) ];
    }
  });
};

This code is initially invoked when the OrderDetail screen is created, but it subsequently attaches a listener that is called each time the OrderDate property of the Order for the screen changes. When this happens, it finds the content item that is backing the date picker control showing the order date in the UI, then sets the validation results if a) the entity is in a pending add state, b) does not already have validation errors, and c) the date does not occur before today’s date.

With this code, if you run the application and add a new order, then change the order date to a date before today, you will see the desired validation error attached to the control:

Other1

If you change it to a date in the future, the validation error disappears:

Other2

The code does not explicitly remove the existing validation error, but when the value changes the runtime clears out the current validation results and revalidates the data, so it works as desired in this case.

This example is not perfect (it does not include code to unhook from the change event when leaving the screen) but gives a sense for what can be achieved using these instance events. The change event is present on almost every LightSwitch object: entities, data services, screens, and many other objects accessible from these core objects.

The Application Object

As with the existing Silverlight client, a LightSwitch HTML client application combines a LightSwitch-provided shell with user-provided screens and data. The HTML client shell handles not only navigation between screens, tabs and dialogs but also the commit model for the application. These gestures are available through both the UI and through an application object exposed in the API.

The application object is accessible through the msls library object as “msls.application”, but also more directly through an alias to the same object in the global space called “myapp”. This object is strongly typed with all the entities and screens that have been defined for the application as well as the set of built-in methods that are called by the shell:

Application

This snapshot is taken from the example application we have been building. You can see the Order entity and OrderDetail screen assets in the dropdown, and the selected item is a generated show screen method for the OrderDetail screen. Notice that the parameters to this method include “Order”, which is the designed screen parameter for this screen. The completion list also shows some of the built-in methods like navigateHome() and saveChanges(). The onsavechanges event is not covered in this post, but in brief, it allows you to customize what it means to save when there are changes pending across multiple data services that cannot be transacted as a single unit.

Let’s make one last change to the example application: alter the ImportOrders screen method so that it imports orders sorted by order date and then automatically navigates to edit the order with the most recent order date. Here is the new code snippet (added/changed lines are highlighted in yellow):

myapp.BrowseOrders.ImportOrders_execute = function (screen) { 
  // Write code here.  
  var northwind =
“http://services.odata.org/Northwind/Northwind.svc” 
  return msls.promiseOperation(function (operation) { 
  OData.read({ requestUri: northwind +
“/Orders?$orderby=OrderDate&$top=10″
               recognizeDates:
true
               enableJsonpCallback:
true }, 
               function success(result) { 
                 var results = result.results, importedOrder;  
                 for (var i = 0, len = results.length; i < len; i++) { 
                   importedOrder = screen.Orders.addNew();  
                   importedOrder.OrderDate = results[i].OrderDate; 
                 } 
                 screen.ImportPerformed =
true
                 myapp.showOrderDetail(msls.BoundaryOption.nested, importedOrder); 
                 operation.complete(); 
               }, 
               function error(e) { operation.error(e); }); 
  });
};

The OData URI is altered to include an orderby operator, and a line is added to show the order detail screen for the last imported order. The first parameter to this method – a boundary option – is used so the caller can tell the target screen how it is being hosted. In this case, the nested boundary option is used which means that the target screen begins a nested change set over the data and shows OK and Cancel buttons in the UI. You can find more information about the available boundary options in my previous architectural post.

This is not a very interesting example, but it does illustrate the usage of a show screen method on the application object and how you can integrate screen navigations into the rest of your business logic.

Debugging Support

Finally, a short word on debugging. For the June Preview, we had suggested that you use the F12 debugger in Internet Explorer or another browser to debug code because the integrated experience in Visual Studio was not yet available. With Preview 2, you can now set breakpoints in your original source code and they will be hit if you run your project in Internet Explorer under F5:

Debugging

This support greatly simplifies the experience for debugging your custom LightSwitch code from inside Visual Studio.

Summary

The LightSwitch team has made a number of improvements to the coding experience for the HTML Client Preview 2, spanning design-time experiences, available coding entry points and additional runtime APIs. This is still a work in progress, and we are eager to drive our next wave of API work using your feedback, so please use the HTML Client forum to post any questions or comments you have on what you have seen here.

Thanks for reading!

Stephen Provine
Principal Software Design Engineer
Visual Studio LightSwitch Team