New LightSwitch HTML Client APIs (Stephen Provine)

New LightSwitch HTML Client APIs (Stephen Provine)

Rate This
  • Comments 28

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

Leave a Comment
  • Please add 6 and 3 and type the answer here:
  • Post
  • Hi Stephen, I am having problems when reading one of the list column using the above procedure.

    I have an employee table having relation in order details table.

    importedOrder.Employee = results[i].Employee;

    screen.OrderDetails.selectedItem.Employee = results[i].Employee;

    I've tried many ways but can't figure it out. Could you please help me how to read list column from master table and select it automatically?

    Regards

    Dwarapudi

  • Hi Dwarapudi,

    Can you be more specific? What value are you getting back from results[i].Employee? I would guess that this value is not valid unless you included an "$expand=Employee" clause in the OData query. See www.odata.org/.../uri-conventions for more information on this clause.

    If you are still having problems after trying this, please post the question in the forums (social.msdn.microsoft.com/.../lightswitchhtml) instead of as a comment on a blog post.

    Thanks,

    Stephen

  • Hi Stephen,

    Thank you for your response. The question is how to get a value from the other table and inserting into the linked table.

    E.g : I have two tables, one is Orders and Second one is Customers.  When creating an order I want to add few of the customers based on filter condition.

    Orders

    ---------------

    OrderDate

    Customer - FK

    Customers

    -------------------

    Customer ID - PK

    CustomerName  

    I am using your sample code  by showing CustomerName  in the screen and inserting the customer Id in to the orders table. How to show the customernames  in the screens list.

    ===================================

    var results = result.results, importedOrder;

                            for (var i = 0, len = results.length; i < len; i++) {

    importedOrder = screen.Orders.addNew();

                               // alert(results[i].Name);  - Getting Customer Name successfully

                                importedOrder.Customer = results[i].Customer;

                            }

    ===================================

       Regards

    Dwarapudi

  • Hi Dwarapudi,

    One last question - as part of the import operation, are you importing both orders AND employees, or only orders (and you want to select existing employees for each order)?

    Thanks,

    Stephen

  • Hi Stephen.

    I am importing only Orders. In orders It contains OrderID, OrderDate and CustomerID etc.  I am getting Id  But unable to read the name from Customers Table.

    Please let me know if it is not clear so that I can send you the sample screen shots? please mail me your email address to dwarapudi@live.in

    Thank You,

    Rama

  • Rama,

    Ok, I think I understand your problem now. Just to confirm, you are querying to get orders and expanding to include customers, since that is the only way reading from traditional OData services to get customer information for each order. When you are importing the orders, you want to associate each order with the customer that was also queried.

    This scenario is not currently well supported in LightSwitch. The ideal way you would do this is to directly set the foreign key value on the imported order, like this:

    importedOrder.CustomerID = results[i].Customer.CustomerID;

    Then importedOrder.Customer would automatically be loaded by LightSwitch based on the CustomerID. However, this is not possible because we are not exposing foreign keys. You tried this:

    importedOrder.Customer = results[i].Customer;

    But this does not work because the object expected to be returned by importedOrder.Customer is a LightSwitch Customer object, not a raw object containing data returned by an OData query.

    The only way you can make this work today is if you manually query the LightSwitch service for each Customer based on the ID you read from the query, and then after retrieving that data, assign the actual entity value. For each imported order, you could call a function like this:

    function setCustomerByID(importedOrder, customerID) {

       myapp.activeDataWorkspace.NorthwindData.Customers

           .Customers_SingleOrDefault(results[i].Customer.CustomerID).execute().then(function (results) {

               importedOrder.Customer = results.results[0];

       });

    }

    In practice, if you are bulk importing a lot of orders, you would want to optimize this so that you don't keep querying for the same customer many times. Therefore you might start by first maintaining a mapping of customerIDs to imported orders (create an object whose keys are the customerID values and whose values are arrays of imported order objects). Then after importing all the orders, iterate through the keys on the object, run the above query once for that customer ID, then set the same customer on all the imported orders in the associated array.

    Sorry there is not an easier way right now to do this. Hope this information helps. And please post these kinds of questions in the future in the LightSwitch forums - it is easier to manage these kinds of questions there.

    Stephen

  • <a href="www.designeroffices.com/">Designer Offices</a>

    I really want to see such more posts in future. Excellent post!

  • designeroffices.com/contact-us.html

    Here everything has been described in systematic manner so that reader could get maximum information and learn many things.

  • Hi,

    I'm working with VS2013 HTML LS and I've noticed that msls.BaundaryOption is not longer parameter in myapp.show... methods. Is it still possible to set up msls.BaundaryOption?

    Thanks

  • @Woj2, the boundary option parameter was removed before we shipped the final first release of the HTML client. Instead of being explicit with the boundary option, we tied the setting to the kind of screen: browse or edit. You can read more about the updated approach in this blog post:

    blogs.msdn.com/.../a-new-user-experience.aspx

    Hope that helps!

  • I know javascript syntax, but I've been stuck in Silverlight. It is very hard now to transition to the webclient since we have to learn not 1 but 4 javascript libraries, and how they combine to produce the desired functionality. You are wrapping functionality for ease of use, great, but where is the API documentation? Please help! We need something akin to javadocs!

  • @Christopher, thanks for the feedback. We definitely recognize that the lack of documentation right now isn't ideal. Your best options at the moment are to pick up patterns from blog posts like these and/or rely on the JavaScript intellisense to discover what is available in the msls library and the various objects it exposes. The intellisense documentation is granular, but quite complete, and we'd love to hear specific feedback if this isn't working well for you (due to the dynamic nature of the JavaScript language, intellisense can be prone to failure but we want to incrementally improve it where we can).

    Thanks,

    Stephen

  • Guys thank you so much for releasing the HTML Client API! I can't tell you how useful it has been. I reference it all the time and it has really helped me to feel confident that I am doing things the right way. Keep up the good work!

Page 2 of 2 (28 items) 12