Nov 5

Written by: Michael Washington
11/5/2014 8:02 PM  RssIcon

 

image

You can create WinJS applications easily using Visual Studio LightSwitch OData as your back-end. This sample demonstrates using WinJS to call LightSwitch directly using the XHR object in WinJS.

image

The sample application uses the WinJS Pivot Control to allow the user to easily navigate the application using touch with no post-backs.

image

Users can insert new records.

image

Users can select and edit existing records.

image

Users can delete records by first selecting them (right-clicking on them or press, hold, and slide each list element down if using touch), and then clicking Delete Selected.

image

Full validation and concurrency is supported.

Why would you want to use WinJS with LightSwitch?

  1. You want to use WinJS controlsWinJS has a a number of controls such as Pivot that create a unique experience that works well on touch devices.
  2. You need to implement security – Security must be implemented on the server-side. You cannot implement security inside WinJS because it is a client-side technology. LightSwitch makes security easy.
  3. You need Concurrency – When a user attempts to update a record that has been updated by another user or process since the user last retrieved the record, concurrency alerts the user. When LightSwitch is used as the back-end data source you get this feature automatically.
  4. You want to use LightSwitch for the Administration screens – There is no need to code all the screens in your application in WinJS. You can mix WinJS and LightSwitch screens in the same application.
  5. You want to build a OData service layer for use with other applications – LightSwitch is the easiest and most robust way to create an OData service.
  6. You want to speed up development – There is a lot less code to implement when you use LightSwitch as your back-end. This means faster development and fewer bugs.

 

Create The Project

We will start with a normal LightSwitch project. If you have the need for user self-registration and management, you may want to start with this sample that provides that: Allow LightSwitch Users To Self-Register and Change Passwords Using MVC.

image

Create a New Project.

image

Create a LightSwitch HTML Application.

The LightSwitch Business Layer

image

In the Server project, right-click on the Data Sources folder and select Add Table.

image

Create the ToDo table.

image

In the Write Code menu, select the Validate method.

Use the following code for the method:

            // Do not allow a task to be called {New Task]
            if (entity.TaskName == "[New Task]")
            {
                results.AddEntityError(
                    "Task cannot be named [New Task]"
                    );
            }
            // Do not allow more than 1 incomplete Task
            if (entity.IsComplete == false)
            {
                int intCountOfIncomplete =
                    this.DataWorkspace.ApplicationData.ToDoes
                    .Where(x => x.IsComplete == false)
                    .Where(x => x.Id != entity.Id).Count();
                if (intCountOfIncomplete > 0)
                {
                    results.AddEntityError(
                        "Cannot have more than 1 incomplete Task"
                        );
                }
            }

 

Add WinJS To The Project

image

Right-click on the HTML Client project and select Manage NuGet Packages (note that we could put the code in the Server project and it would still work, see: Using AngularJS Wijmo Grid With LightSwitch OData for an example of this).

image

Install the WinJS library.

 image

The WinJS JavaScript code will be added to the project.

Create The Default Page

 image

Open the default.htm page and replace the contents with the following code:

 

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta charset="utf-8">
    <title>Adding WinJS controls and styles</title>
    <!-- WinJS references -->
    <script src="WinJS/js/base.js"></script>
    <script src="WinJS/js/ui.js"></script>
    <link href="WinJS/css/ui-dark.css" rel="stylesheet">
    <!-- AddingWinJSControlsAndStyles references -->
    <link href="Content/default.css" rel="stylesheet">
    <script src="Scripts/default.js"></script>
</head>
<body>
</body>
</html>

 

This references the JavaScript and CSS files that the application needs. Some of these files are not created yet, we will create them in later steps. To see the contents of the CSS file, download the sample from the download page.

Add the following code to the body section to create the Pivot control with two Pivot items (edit and view-only):

 

    <!-- PIVOT Control... -->
    <div id="pivotControl" class="pivotControl" 
         data-win-control="WinJS.UI.Pivot" 
         data-win-options="{ title: 'ToDo', selectedIndex: 1 }">
        <div class="listviewpivotitem" 
             data-win-control="WinJS.UI.PivotItem" 
             data-win-options="{ 'header': 'edit', managedLV: true }">
            <!-- Nothing here yet... -->
        </div>
        <div class="listviewpivotitem" 
             data-win-control="WinJS.UI.PivotItem" 
             data-win-options="{ 'header': 'view-only', managedLV: true }">
            <div id="todoListViewControlUnSelected"
                 data-win-control="WinJS.UI.ListView"
                 data-win-options="{selectionMode: 'none'}">
            </div>
        </div>
    </div>

 

Also, add the following code to define the templates for the Pivot control and the ListView:

 

    <!-- Start PIVOT... -->
    <div class="ToDoItemTemplate" 
         data-win-control="WinJS.Binding.Template">
        <div class="ToDoItem">
            <div class="hitTarget win-interactive"></div>
            <h2 class="author" 
                data-win-bind="innerHTML: author"></h2>
            <h5 class="time" 
                data-win-bind="innerHTML: time"></h5>
            <h5 class="title win-pivot-slide1" 
                data-win-bind="innerHTML: title; style.color: titleColor"></h5>
            <h5 class="previewText win-pivot-slide2" 
                data-win-bind="innerHTML: previewText"></h5>
        </div>
    </div>
    <!-- ListView Template... -->
    <div id="ToDoListViewTemplate"
         class="itemContainer"
         data-win-control="WinJS.Binding.Template">
        <h2 class="itemData">
            <input id="ToDoToggle" 
                   type="checkbox" 
                   disabled="disabled" 
                   data-win-bind="checked:IsComplete" />
            <span data-win-bind="textContent: TaskName" />
        </h2>
    </div>

 

Create The JavaScript

Create a default.js file and use the following code that sets up some variables and initializes WinJS:

 

(function () {
    "use strict";
    var result;
    var todoList = [];
    var dataList = [];
    var todoListUnSelectedView;
    WinJS.UI.processAll().done(function () {
        var todoListViewControlUnSelectedElement =
            document.getElementById('todoListViewControlUnSelected');
        todoListUnSelectedView =
            new WinJS.UI.ListView(todoListViewControlUnSelectedElement, null);  
        var ListViewUnSelectedTemplateElement =
            document.getElementById("ToDoListViewTemplate");  
        var ListViewUnSelectedTemplate =
            new WinJS.Binding.Template(ListViewUnSelectedTemplateElement, null);  
        todoListUnSelectedView.itemTemplate =
            ListViewUnSelectedTemplateElement;  
        todoListUnSelectedView.forceLayout();
    });
})();

 

Add the following code that calls LightSwitch OData and provides data to the ListView control:

 

 // Get the ToDo items
    WinJS.xhr({ url: "/ApplicationData.svc/ToDoes" })
        .then(ResponseToDoData)
        .done(DisplayToDoData);
    function ResponseToDoData(response) {
        result = response.responseXML;
    }
    function DisplayToDoData() {
        // Clear list
        todoList = [];
        var items = result.querySelectorAll("entry");
        for (var ctr = 0; ctr < items.length; ctr++) {
            var todo = {};
            todo.Id =
                items[ctr].querySelector("Id")
                .textContent;
            todo.TaskName =
                items[ctr].querySelector("TaskName")
                .textContent;
            todo.IsComplete =
                Boolean(items[ctr].querySelector("IsComplete")
                .textContent === 'true');
            todo.Etag =
                items[ctr].attributes['0'].nodeValue;
            todo.RowVersion =
                items[ctr].querySelector("RowVersion")
                .textContent;
            todoList.push(todo);
        }
        dataList =
            new WinJS.Binding.List(todoList);
        todoListUnSelectedView.itemDataSource =
            dataList.dataSource;
    }

 

image

If we seed the database with some sample items and run the project at this point, we will see the Pivot control and the ListView control inside the view-only Pivot Item populated with data.

CRUD Methods

We will now cover the code required to Create Update and Delete data.

First, add the following code to the edit Pivot Item in the default.htm page to provide a ListView and a form to allow records to be edited:

 

           <div id="todoListViewControl" class="listViewList"
                 data-win-control="WinJS.UI.ListView"
                 data-win-options="{selectionMode: 'multi'}">
            </div>
            <button id="addNew">Add New</button>
            <button id="deleteSelected">Delete Selected</button>
            <br /><br />
            <div id="EditBox">
                <input id="ToDoId" type="hidden" />
                <input id="ToDoEtag" type="hidden" />
                <input id="ToDoRowVersion" type="hidden" />
                <div>
                    Task Name
                    <input id="ToDoTaskName" type="text" /><br />
                </div>
                <div>
                    IS Complete
                    <input id="ToDoEditToggle" type="checkbox" />
                </div>
            </div><br />
            <div>
                <button id="saveSelectedEdit" 
                        class="action secondary">Save</button>
            </div>

 

Next, we add the following code (and the global variables they reference) to the WinJS initialization code section. This wires-up the buttons needed to perform editing:

 

        // Edit buttons
        var element = document.body;
        element.querySelector("#addNew")
            .addEventListener("click", doClickAdd, false);
        element.querySelector("#deleteSelected")
            .addEventListener("click", doClickDelete, false);
        element.querySelector("#saveSelectedEdit")
            .addEventListener("click", doClickSave, false);
        // Set-up Editing
        ToDoTaskName = document.getElementById("ToDoTaskName");
        ToDoEditToggle = document.getElementById("ToDoEditToggle");
        ToDoEditId = document.getElementById("ToDoId");
        ToDoEtag = document.getElementById("ToDoEtag");
        ToDoRowVersion = document.getElementById("ToDoRowVersion");
        ToDoEditId.value = "-1"; // Default value 

 

Create Code

When a user clicks the Add New button, the following code runs to clear any values in the form and to set the Id to –1 so we will know it is a new record:

 

    function doClickAdd() {
        // Create a blank record
        ToDoEditId.value = "-1" // So we know it is a new record
        ToDoTaskName.value = "";
        ToDoEtag.value = "";
        ToDoRowVersion.value = "";
        ToDoEditToggle.checked = false;
    }

 

When the user clicks the save button, the following code runs that saves the record and checks for any validation errors:

 

   function doClickSave() {
        // Get current record
        var todo = {};
        todo.Id = ToDoEditId.value;
        todo.TaskName = ToDoTaskName.value;
        todo.IsComplete = ToDoEditToggle.checked;
        todo.RowVersion = ToDoRowVersion.value;
        var accept1 = "application/atomsvc+xml;q=0.8, ";
        var accept2 = "application/json;odata=fullmetadata;";
        var accept3 = "q=0.7, application/json;q=0.5, */*;q=0.1";
        // Make OData Call.
        if (todo.Id == -1) {
            // In Insert
            WinJS.xhr({
                type: "POST",
                url: "/ApplicationData.svc/ToDoes",
                headers: {
                    "DataServiceVersion": "3.0",
                    "MaxDataServiceVersion": "3.0",
                    "Prefer": "return-content",
                    "Content-type": "application/json;odata=verbose",
                    "Accept": accept1 + accept2 + accept3
                },
                data: JSON.stringify(todo)
            }).done(
                // When the result has completed, check the status.
                function completed(result) {
                    if (result.status === 201) {
                        // Refresh list
                        WinJS.xhr({ url: "/ApplicationData.svc/ToDoes" })
                            .then(ResponseToDoData)
                            .done(DisplayToDoData);
                    }
                }, function (result) {
                    // Get the validation error messages from LightSwitch
                    var JsonError = JSON.parse(result.response);
                    var XMLError = JsonError['odata.error'].message.value;
                    var parser = new DOMParser();
                    var xmlDoc = parser.parseFromString(XMLError, "text/xml");
                    var ValidationResults =
                        xmlDoc.querySelectorAll("ValidationResults");
                    // Loop through all Valication errors
                    if (ValidationResults['0'] !== undefined) {
                        for (var ctr = 0; ctr < ValidationResults['0']
                            .childNodes.length; ctr++) {
                            // Show Error
                            alert(ValidationResults['0'].childNodes[ctr]
                                .childNodes['0'].textContent);
                        };
                    }
                });
        }
    }

 

Update Code

When a user is updating a record, this code detects what record that was selected in the list and retrieves the record:

 

    function doClickEdit(eventInfo) {
        // Get the Id of the selected item
        var CurrentId = eventInfo.detail.itemPromise._value.data.Id;
        // Make OData Call
        WinJS.xhr({ url: "/ApplicationData.svc/ToDoes(" + CurrentId + ")" })
                    .then(ResponseToDoData)
                    .done(DisplayToDoItem);
    }

 

When a user clicks the save button the following code runs:

 

            // An Update
            WinJS.xhr({
                type: "PUT",
                url: "/ApplicationData.svc/ToDoes(" + todo.Id + ")",
                headers: {
                    "If-Match": ToDoEtag.value,
                    "DataServiceVersion": "3.0",
                    "MaxDataServiceVersion": "3.0",
                    "Prefer": "return-content",
                    "Content-type": "application/json;odata=verbose",
                    "Accept": accept1 + accept2 + accept3
                },
                data: JSON.stringify(todo)
            }).done(
                // When the result has completed, check the status.
                function completed(result) {
                    if (result.status === 200) {
                        // Refresh list
                        WinJS.xhr({ url: "/ApplicationData.svc/ToDoes" })
                            .then(ResponseToDoData)
                            .done(DisplayToDoData);
                    }
                }, function (result) {
                    // handle error conditions.
                    // A 412 is an OData concurrency error when IF-Match is passed
                    // See: http://bit.ly/ZJ1IJE
                    if (result.status === 412) {
                        // Set _errorMsg
                        var alert1 = "The data has been modified on the server.";
                        var alert2 = "Please refresh before making changes";
                        alert(alert1 + alert2);
                    }
                    // Get the validation error messages from LightSwitch
                    var JsonError = JSON.parse(result.response);
                    var XMLError = JsonError['odata.error'].message.value;
                    var parser = new DOMParser();
                    var xmlDoc = parser.parseFromString(XMLError, "text/xml");
                    var ValidationResults = xmlDoc.querySelectorAll("ValidationResults");
                    // Loop through all Valication errors
                    if (ValidationResults['0'] !== undefined) {
                        for (var ctr = 0; ctr < ValidationResults['0']
                            .childNodes.length; ctr++) {
                            // Show Error
                            alert(ValidationResults['0'].childNodes[ctr]
                                .childNodes['0'].textContent);
                        };
                    }
                })

 

Delete Code

When a user deletes a record the following code runs:

 

    function doClickDelete() {
        // Get the selected items
        if (todoListView.selection.count() > 0) {
            var accept1 = "application/atomsvc+xml;q=0.8, ";
            var accept2 = "application/json;odata=fullmetadata;";
            var accept3 = "q=0.7, application/json;q=0.5, */*;q=0.1";
            todoListView.selection.getItems().done(function (items) {
                for (var ctr = 0; ctr < items.length; ctr++) {
                    // Get current record
                    var todo = {};
                    todo.Id = items[ctr].data.Id;
                    todo.TaskName = items[ctr].data.TaskName;
                    todo.IsComplete = items[ctr].data.IsComplete;
                    todo.Etag = items[ctr].data.Etag;
                    todo.RowVersion = items[ctr].data.RowVersion;
                    // Make OData Call.
                    WinJS.xhr({
                        type: "DELETE",
                        url: "/ApplicationData.svc/ToDoes(" + todo.Id + ")",
                        headers: {
                            "If-Match": todo.Etag,
                            "DataServiceVersion": "3.0",
                            "MaxDataServiceVersion": "3.0",
                            "Accept": accept1 + accept2 + accept3
                        },
                        responseType: "blob"
                    }).done(
                        // When the result has completed, check the status.
                        function completed(result) {
                            if (result.status === 204) {
                                // Refresh list
                                WinJS.xhr({ url: "/ApplicationData.svc/ToDoes" })
                                    .then(ResponseToDoData)
                                    .done(DisplayToDoData);
                            }
                        });
                }
            })
        }
    }

 

Special Thanks

This article would not be possible without code from Dale Morrison (@dale_mohttp://blog.ofanitguy.com).

Links

Try WinJS

Using JayData to Consume the Visual Studio LightSwitch OData Business Layer in a AngularJs CRUD Application

Download Code

The LightSwitch project is available at http://lightswitchhelpwebsite.com/Downloads.aspx

(you must have Visual Studio 2013 (or higher) to run the code)

Tags: WinJS
Categories:

Your name:
Gravatar Preview
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Security Code
CAPTCHA image
Enter the code shown above in the box below
Add Comment   Cancel 
Microsoft Visual Studio is a registered trademark of Microsoft Corporation / LightSwitch is a registered trademark of Microsoft Corporation