You are here:   Blog
Register   |  Login

 

May 19

Written by: Michael Washington
5/19/2013 9:41 AM  RssIcon

image

An objection many developers have about using Visual Studio LightSwitch HTML Client for their projects, is that they feel they need normal web pages. To many developers, LightSwitch HTML pages look odd when viewed in a normal desktop web browser.

With the recent release of JQuery Mobile 1.3.1 (Visual Studio LightSwitch uses JQuery Mobile) , JQuery Mobile has moved from Mobile to Mobile first. Meaning, JQuery Mobile still primarily creates mobile web applications, but it now has controls that will work well for both desktop and mobile applications. Visual Studio LightSwitch has been updated to support JQuery Mobile 1.3.0 (and higher).

JQuery Mobile 1.3.0 brings a number of features that allow you to create applications that look great on both desktop web browsers and mobile web browsers. One of the best is the Reflow table that will be explored here. The Reflow table displays data in a desktop web browser like any normal data grid, yet it will dynamically pivot the table when the screen becomes smaller (rather than just squeezing the table smaller). A user can easily view and navigate the table on any sized device.

Update The LightSwitch JavaScript Runtime

image

First, follow the directions in the article: Updating The LightSwitch JavaScript Runtime to update the Visual Studio LightSwitch Client JavaScript Runtime. Follow the directions to update it to JQuery Mobile 1.3.1 (or higher).

The Built-in Screen Adaption Features

LightSwitch has built-in features that allow you to easily create screens that will adapt to any screen size. You can read more about it in the LightSwitch Team Blog posts: A New User Experience (Heinrich Wendel) and: Designing for Multiple Form Factors (Heinrich Wendel).

The most powerful technique is using columns that will automatically collapse its containing elements underneath each other when the screen size becomes smaller.

image

In the screen designer, we select Add, then New Group, then create a Columns Layout Group. We add three buttons with a minimum width of 200.

image

When we run the application the buttons will be on the same line when have a wide screen.

image

They will stack on top of each other when we have a smaller one.

image

We can style the buttons by switching to File View, adding a custom .css style, and then adding a reference to that style in the default.htm page.

image

We then select the Edit PostRender Code link for each button, and add a style to each button:

 

myapp.BrowseStudents.Previous_postRender = function (element, contentItem) {
    var PreviousButtonOutside = $(element);
    var PreviousButtonInside = $(element).children();
    PreviousButtonOutside.addClass("gray");
    PreviousButtonInside.addClass("orange");
};
myapp.BrowseStudents.Next_postRender = function (element, contentItem) {
    var NextButtonOutside = $(element);
    var NextButtonInside = $(element).children();
    NextButtonOutside.addClass("gray");
    NextButtonInside.addClass("blue");
};
myapp.BrowseStudents.AddStudent_postRender = function (element, contentItem) {
    var AddStudentButtonOutside = $(element);
    var AddStudentButtonInside = $(element).children();
    AddStudentButtonOutside.addClass("gray");
    AddStudentButtonInside.addClass("green");
};

 

image

When we run the application, the buttons look more attractive.

 

image

we can also select the group control, and select the Edit PostRender Code link for it, and use the following:

 

myapp.BrowseStudents.ButtonMenu_postRender = function (element, contentItem) {
    var MenuElementElementInside = $(element);
    MenuElementElementInside.css({
        "position": "relative",
        "float": "right"
    });
};

image

When the screen is wider, the buttons will nowbe right justified.

 

Implementing The JQuery Reflow Table

image

To implement the JQuery Reflow Table, we switch the Student List to a Custom Control.

We then select the Edit Render Code link for the control.

We use the following code:

 

var intCurrentItem = 0;
var intPageSize = 5;
var StudentTable;

 

myapp.BrowseStudents.StudentsGrid_render = function (element, contentItem) {
    // clear the element
    element.innerHTML = "";
    // Create a table
    var strTable = "<table id='studentTable' class='ui-responsive table-stroke'";
    strTable = strTable + " data-role='table' data-mode='reflow'></table>";
    StudentTable = $(strTable);
    // Create thead
    var theadTemplate = $("<thead></thead>");
    theadTemplate.appendTo($(StudentTable));
    // Create tableRow
    var tablerowTemplate = $("<tr class='ui-bar-d'></tr>");
    tablerowTemplate.appendTo($(theadTemplate));
    // Crate Columns
    var Column1Template = $("<th data-priority='6'>ID</th>");
    Column1Template.appendTo($(tablerowTemplate));
    var Column2Template = $("<th data-priority='1'>First Name</th>");
    Column2Template.appendTo($(tablerowTemplate));
    var Column3Template = $("<th data-priority='2'>Last Name</th>");
    Column3Template.appendTo($(tablerowTemplate));
    var Column4Template = $("<th data-priority='3'>Birthdate</th>");
    Column4Template.appendTo($(tablerowTemplate));
    var Column5Template = $("<th data-priority='4'>Grade Level</th>");
    Column5Template.appendTo($(tablerowTemplate));
    var Column6Template = $("<th data-priority='5'>Gender</th>");
    Column6Template.appendTo($(tablerowTemplate));
    // Create tableBody
    var tablebodyTemplate = $("<tbody></tbody>");
    tablebodyTemplate.appendTo($(StudentTable));
    // Complete the element    
    StudentTable.appendTo($(element));
    // Function to show items  
    // This is required because the removeEventListener 
    // requires the same instance of the handler to be able to remove it 
    function onVisualCollectionLoaded() {
        // Show the data in the JQuery reflow table
        showItems(intCurrentItem, intPageSize, contentItem.screen);
    }
    // Show Data
    var visualCollection = contentItem.value;
    // Do we have data?
    if (visualCollection.isLoaded) {
        // Load the data
        onVisualCollectionLoaded();
    } else {
        // Create a addChangeListener that will fire when the data is loaded
        visualCollection.addChangeListener("isLoaded", onVisualCollectionLoaded);
        // Load the data
        visualCollection.load();
    }
    // Clean up addChangeListener when screen is closed.
    contentItem.handleViewDispose(function () {
        // Remove the isLoaded change listener
        visualCollection.removeChangeListener(
            "isLoaded", onPropertyChanged);
    });
};

 

The code above calls the following function to show the data in the grid:

 

function showItems(start, end, screen) {
    // Remove existing rows
    StudentTable.find("tr:gt(0)").remove();
    $.each(screen.Students.data, function (i, student) {
        if (i >= start && i < end) {
            // Create tableRow
            var tablecontentrowTemplate = $("<tr></tr>");
            tablecontentrowTemplate.appendTo($(StudentTable));
            // Set the row iD
            tablecontentrowTemplate.id = student.Id;
            // Create ID
            var IdTemplate = $("<th>" + student.Id + "</th>");
            IdTemplate.appendTo($(tablecontentrowTemplate));
            // Create FirstName
            var FirstNameTemplate = $("<td>").text(student.FirstName);
            FirstNameTemplate.appendTo($(tablecontentrowTemplate));
            // Create LastName
            var LastNameTemplate = $("<td>").text(student.LastName);
            LastNameTemplate.appendTo($(tablecontentrowTemplate));
            // Create Birthdate
            var dtBirthdate = new Date(student.Birthdate);
            var formattedBirthDate =
                ((dtBirthdate.getMonth() + 1) + "/" + dtBirthdate.getDate() + "/" + dtBirthdate.getFullYear());
            var BirthdateTemplate = $("<td>" + formattedBirthDate + "</td>");
            BirthdateTemplate.appendTo($(tablecontentrowTemplate));
            // Create GradeLevel
            var GradeLevelTemplate = $("<td>" + student.GradeLevel + "</td>");
            GradeLevelTemplate.appendTo($(tablecontentrowTemplate));
            // Create Gender
            var GenderTemplate = $("<td>").text(student.Gender);
            GenderTemplate.appendTo($(tablecontentrowTemplate));
            // Change mouse curser to hand 
            $(tablecontentrowTemplate).css('cursor', 'pointer');
            // Add click event
            $(tablecontentrowTemplate).click(function () {
                // Id of selected Student
                var selectedStudentID = tablecontentrowTemplate.id;
                // Get the selected Student
                myapp.activeDataWorkspace.ApplicationData.Students_SingleOrDefault(selectedStudentID)
                    .execute()
                    .then(function (result) {
                    // Set the selected Student
                    screen.Students.selectedItem = result.results[0];
                    // Show selected Student in Edit screen
                    myapp.showAddEditStudent(screen.Students.selectedItem,{
                        afterClosed: function (addEditScreen, navigationAction) {
                            // If the user commits a change,
                            // show the new Student in Browse Screen.
                            if (navigationAction === msls.NavigateBackAction.commit) {
                                // Update the Grid//
                                showItems(intCurrentItem, intCurrentItem + intPageSize, screen);
                            }
                        }
                    });
                });
            });
        };
    });
    // Refresh the StudentTable
    StudentTable.table("refresh");
};

 

To make the table show the full width, we open the Content/jquery.mobile.theme-1.3.1.css file and alter the .ui-table-reflow.ui-responsive tag to:

image

Next, we run the application.

image

When the window is wide, the table shows each row of data on its own line.

image

When the screen is smaller, it uses multiple lines to clearly show each row of data.

 

Paging

Normally LightSwitch provides paging for the data. When you implement a control that completely renders the data, you must implement the code to handle paging.

image

In the screen designer, we right-click on the Previous button and select Edit Execute Code.

We use the following code:

 

myapp.BrowseStudents.Previous_Tap_execute = function (screen) {
    // Only move back if we are not on the first record
    if (intCurrentItem > 0) {
        // Move the current record back by the page size
        intCurrentItem = intCurrentItem - intPageSize;
        showItems(intCurrentItem, intCurrentItem + intPageSize, screen);
    }
};

 

We do the same for the Next button and use the following code:

 

myapp.BrowseStudents.Next_Tap_execute = function (screen) {
    // We always try to move forward at this point
    // We may move back (later in this method) 
    // If we are moving too far forward
    intCurrentItem = intCurrentItem + intPageSize;
    // If we are trying to move to a record 
    // that is higher than all the records we have
    // try to load more records
    if (intCurrentItem + intPageSize >= screen.Students.count) {
        // See if we can load more records
        if (screen.Students.canLoadMore) {
            // We can load more records -- load them
            screen.Students.loadMore().then(function (result) {
                showItems(intCurrentItem, intCurrentItem + intPageSize, screen);
            });
        } else {
            // If we are here then we have no more records to load
            // See if we have moved too far forward
            if (intCurrentItem >= screen.Students.count) {
                //We have moved too far forward so move back
                intCurrentItem = intCurrentItem - intPageSize;
            }
            showItems(intCurrentItem, intCurrentItem + intPageSize, screen);
        }
    } else {
        // We are trying to show records that we already have loaded 
        // Show them 
        showItems(intCurrentItem, intCurrentItem + intPageSize, screen);
    }
};

 

Editing

image

To allow a record to be edited, we first create an normal Add/Edit Details screen for the entity in LightSwitch.

LightSwitch will automatically create a showAddEditStudent method that will allow us to programmatically open this screen.

We then add the following code to the showItems method (in the screen that displays the Reflow table):

 

            // Add click event
            $(tablecontentrowTemplate).click(function () {
                // Id of selected Student
                var selectedStudentID = tablecontentrowTemplate.id;
                // Get the selected Student
                myapp.activeDataWorkspace.ApplicationData.Students_SingleOrDefault(selectedStudentID)
                    .execute()
                    .then(function (result) {
                    // Set the selected Student
                    screen.Students.selectedItem = result.results[0];
                    // Show selected Student in Edit screen
                    myapp.showAddEditStudent(screen.Students.selectedItem,{
                        afterClosed: function (addEditScreen, navigationAction) {
                            // If the user commits a change,
                            // show the new Student in Browse Screen.
                            if (navigationAction === msls.NavigateBackAction.commit) {
                                // Update the Grid//
                                showItems(intCurrentItem, intCurrentItem + intPageSize, screen);
                            }
                        }
                    });
                });
            });

 

image

When we click on a row in the table, the row will come up in the edit screen.

 

Adding Records

To add a record, the following code is used:

 

myapp.BrowseStudents.AddStudent_Tap_execute = function (screen) {
    myapp.showAddEditStudent(null, {
        beforeShown: function (addEditScreen) {
            // Create new Student here so that
            // discard will work.
            var newStudent = new myapp.Student();
            addEditScreen.Student = newStudent;
        },
        afterClosed: function (addEditScreen, navigationAction) {
            // If the user commits the change,
            // show the new Student in Browse Screen.
            if (navigationAction === msls.NavigateBackAction.commit) {
                // Update the Grid//
                showItems(intCurrentItem, intCurrentItem + intPageSize, screen);
            }
        }
    });
};

 

Special Thanks

A very special thanks to LightSwitch Team members, Stephen Provine, Heinrich Wendel, and Huy Nguyen. There is no way I would have been able to get this to work without their help.

Download Code

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

(you must have Visual Studio 2012 Update 2 installed to run the code)

17 comment(s) so far...


Gravatar

Re: LightSwitch HTML Client For The Desktop Web Browser

Nice work Michael!
It seems like there's no sorting of the table available in the reflow table control... would this be the same sort method to follow to bind the data to another grid control (with sorting/searching).
Also, is this our lot in life now with the HTML client, that we need to write a lot of code to bind custom controls, or is there going to be some kind of Extensions for the HTML client where we can just drop things in?

By Stephen McMahon on   5/20/2013 4:12 AM
Gravatar

Re: LightSwitch HTML Client For The Desktop Web Browser

@Stephen McMahon - You can add sorting I just didn't add it. There are drop in extensions by ComponentOne that should be released soon.

By Michael Washington on   5/20/2013 6:03 AM
Gravatar

Re: LightSwitch HTML Client For The Desktop Web Browser

I look forward to your articles on the Wijmo stuff ;)

I suppose it would be too much to ask that they include this in their Studio for Lightswitch product! We can hope....

By Stephen McMahon on   5/21/2013 4:36 AM
Gravatar

Re: LightSwitch HTML Client For The Desktop Web Browser

Thanks a lot Michael! This post really got me started on jQuery mobile tables :)

But now I'm stuck on how to implement filtering. I would really appreciate it if you could point me in the right direction.

Cheers Simon

By Simon Trockel on   6/18/2013 4:12 AM
Gravatar

Re: LightSwitch HTML Client For The Desktop Web Browser

@Simon Trockel - See: http://lightswitchhelpwebsite.com/Blog/tabid/61/EntryId/186/Server-Side-Search-using-the-LightSwitch-HTML-Client.aspx

By Michael Washington on   6/18/2013 4:15 AM
Gravatar

Re: LightSwitch HTML Client For The Desktop Web Browser

@Michael: Thanks! How can I catch the change of the visualCollection to update my reflow table?

By Simon Trockel on   6/18/2013 3:58 PM
Gravatar

Re: LightSwitch HTML Client For The Desktop Web Browser

@Simon Trockel - The StudentsGrid_render is bound to the collection. Any change to the collection will automatically update the render and therefore the table in the render.

By Michael Washington on   6/18/2013 3:59 PM
Gravatar

Re: LightSwitch HTML Client For The Desktop Web Browser

Hello!
I like the example. But I find it very complex to create the table by jQuery.

Is there a chance to bind the Data direct into an html page?

My consideration would be something like I did with knockout:
I create a page.html which look like this:









Then I create a custom control. And load the page.html into this context.
myapp.Browse_People.TIB_PEOPLE_render = function (element, contentItem) {
// Write code here.

$.get("Content//Html//page.html", function (data) {
($(data)).appendTo($(element));
});

Is there a way to do something like this? And If- have you maybe some example?
Thanks a lot.

By Pinky on   7/1/2013 4:02 AM
Gravatar

Re: LightSwitch HTML Client For The Desktop Web Browser

@Pinky - In my opinion there is less code than using knockout

By Michael Washington on   7/1/2013 4:15 AM
Gravatar

Re: LightSwitch HTML Client For The Desktop Web Browser( Reflow Table not working)

Hello!.. I tried this example working fine, except Reflow table option. If I am resizing browser width table format not supporting, can you share css property for reflow table,

By sirancheevi on   7/23/2013 5:11 AM
Gravatar

Re: LightSwitch HTML Client For The Desktop Web Browser

@sirancheevi - All the code is available on the download page.

By Michael Washington on   7/23/2013 5:11 AM
Gravatar

Re: LightSwitch HTML Client For The Desktop Web Browser

Michael,

Nice Blog. The paging is client-side right? So all the data is sent to the client then rows are rendered or not by showItems according to start/end. Do you have an example of server-side paging? I assume we'd use PreProcessQuery for that(?)

Thanks,
Josh

By jbooker on   9/26/2013 2:00 PM
Gravatar

Re: LightSwitch HTML Client For The Desktop Web Browser

@jbooker - It is not pure client-side. Only some of the data is loaded and the rest only when you ask for it. A Preprocess Query would not change the behavior at all.

By Michael Washington on   9/26/2013 2:03 PM
Gravatar

Re: LightSwitch HTML Client For The Desktop Web Browser

Thanks Michael. If I wanted to enable runtime dynamic sort together with paging then PreProcessQuery would necessary, correct? Or is there a javascript method such as load() to change the sort?

By jbooker on   9/27/2013 8:17 AM
Gravatar

Re: LightSwitch HTML Client For The Desktop Web Browser

@jbooker - See "Paging and Sorting in LightSwitch HTML Client" on this site.

By Michael Washington on   9/29/2013 5:09 AM
Gravatar

Re: Email Template?

How would one render a clickable email via this template method?
I've tried:
var EmailTemplate = $("' + person.Email + '');
EmailTemplate.appendTo($(tablecontentrowTemplate));
However that does not work.
If I use
var EmailTemplate = $("").text(' + person.Email + '');
EmailTemplate .appendTo($(tablecontentrowTemplate));

It outputs the HTML as text.

By jmanley on   10/20/2013 9:37 AM
Gravatar

Re: LightSwitch HTML Client For The Desktop Web Browser

Never mind, found it, here is the code incase it helps anyone else, I think the single vs double quotes got me:
if(person.Email.length > 0)
{
//var EmailTemplate = $("").text("" + person.Email + "");
var EmailTemplate = $("" + person.Email + "");
EmailTemplate.appendTo($(tablecontentrowTemplate));
}
else
{
var EmailTemplate = $("").text("No Email Found");
EmailTemplate.appendTo($(tablecontentrowTemplate));
}

By jmanley on   10/20/2013 9:48 AM

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