May
19
Written by:
Michael Washington
5/19/2013 9:41 AM
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
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.
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.
When we run the application the buttons will be on the same line when have a wide screen.
They will stack on top of each other when we have a smaller one.
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.
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");
};
When we run the application, the buttons look more attractive.
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"
});
};
When the screen is wider, the buttons will nowbe right justified.
Implementing The JQuery Reflow Table
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:
Next, we run the application.
When the window is wide, the table shows each row of data on its own line.
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.
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
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);
}
}
});
});
});
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...
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
|
@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
|
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
|
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
|
@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
|
@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
|
@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
|
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
|
@Pinky - In my opinion there is less code than using knockout
By Michael Washington on
7/1/2013 4:15 AM
|
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
|
@sirancheevi - All the code is available on the download page.
By Michael Washington on
7/23/2013 5:11 AM
|
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
|
@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
|
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
|
@jbooker - See "Paging and Sorting in LightSwitch HTML Client" on this site.
By Michael Washington on
9/29/2013 5:09 AM
|
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
|
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
|