VSCode, TypeScript, AngularJS & Kendo UI

Our team has been heads down working with VSCode, TypeScript, AngularJS, Kendo UI and last but not least NativeScript. VSCode has quickly become our teams de facto IDE and TypeScript has become the standard when it comes to any JS development. TypeScript has definitely helped us with large scale enterprise JS development. I’ve recently published an article on Telerik Developer Network: “Improving Development with TypeScript“, which dives into why TypeScript is an absolute must. If VSCode and/or TypeScript sparks any interest (which it should), have a quick read 🙂

Article: http://developer.telerik.com/featured/improving-development-with-typescript
Live demo: http://kendo-typed.azurewebsites.net/app/diagram/diagram.html

2016-03-14_15-48-15

Note: source link can be found in the published article, back-end source code is NodeJS, however it could easily be be hosted on any other back end server e.g. .NET, Java, etc. since it’s really just an AngularJS front end app.

Happy coding..! 🙂

Our Telerik Kendo UI Case Study 2014 Released – Enterprise Benefits and Value Added

Telerik Kendo UI Case Study is out, quick read on CBRE enterprise benefits leveraging Kendo UI Professional.

2014-11-03_14-07-52

Customer Profile
CB Richard Ellis (CBRE) is one of the world’s premier full-service real estate companies, with 349 offices in 42 countries and over 44,000 employees. The company’s $223 billion in transactions span sales and leasing, loan originations, property and corporate facilities management and much more, making CBRE the global leader in real estate services and investment.

Business Situation
CBRE has over 100 mobile apps–some public facing–but most are unique to the company internal operations. However, disparate development platforms scattered around the world made it difficult to maximize economies of scale. The company needed a framework to deliver application portability to a spectrum of devices and operating systems, while at the same time, providing the “wow” factor field agents needed to close real estate deals.

Solution
When CBRE application development shifted increasingly to HTML5 and CSS3, the development team conducted a thorough review of the competitive landscape and after an exhaustive research they chose the Telerik Kendo UI® framework.

Benefits

  • Less resources needed to develop, test and keep things consistent for a wide array of machines, browsers and versions
  • Development time savings of 35%-50%
  • Confidence that each app will run flawlessly on whatever device selected: iPad, iPhone, Android tablet, Windows Phone, Mac or Windows desktop

http://www.telerik.com/company/success-stories/details/real-estate-giant-cbre-cuts-mobile-app-dev-time-by-50-with-telerik

http://www.telerik.com/docs/default-source/case-studies-documents/cbre_casestudy_2.pdf

CBRE Telerik Kendo UI Enterprise Case Study 2014 (PDF Download)

Migrating & Deploying Telerik’s Sitefinity CMS to Windows Azure

So I started to migrate Telerik’s Sitefinity CMS to Windows Azure this weekend and based on all the documentation out there…, well let’s just say it wasn’t as comprehensive as I thought it should be; hence this blog post for others that may need some supplementary guidance with http://www.sitefinity.com/documentation/documentationarticles/installation-and-administration-guide/deployment/deploying-sitefinity-projects-to-windows-azure/.

I’ll keep this in Azure for as long as I can, mean while here’s the Url of the sample Sitefinitity instance that was deployed to Azure in this blog post: http://sitefinityinazure.cloudapp.net.

Note: Throughout this process you can debug, build, however do not perform a clean or rebuild on the solution. The Sitefinity assemblies are included and referenced from the bin folder, performing a clean or rebuild will wipe these assemblies out unless you copy them out of the bin folder into another folder (e.g. folder named “Assemblies”) and re-reference them. This is a known issue when doing a clean or rebuild http://www.sitefinity.com/devnet/kb/sitefinity-4-x/an-error-%E2%80%9Ccould-not-load-file-or-assembly-telerik-sitefinity-migrationcontracts-or-one-of-its-dependencies-%E2%80%9D-is-thrown-when-you-rebuild-or-clean-your-sitefinity-4-2-project.aspx. If time permits I will post another blog for this solution.

For the purpose of what actually works, we will stick with the sequence of these steps:

  1. Start with a fresh install and instance of a Sitefinity site
  2. Create a test page, with a content widget
  3. Create the SQL Azure Instance
  4. Create the SQL Azure Blob Storage
  5. Migrate the local database to SQL Azure and assert that the application runs fine locally using the SQL Azure instance in the cloud
  6. Configure the Azure Blob storage
  7. Add and configure the Azure Web Role to the solution and confirm that it works locally with the Azure emulators, this installs with Azure SDK (optional step)
  8. Deploy our Sitefinity instance into Windows Azure as a Cloud Service, we will provision the Cloud Service as part of the first time deployment to Azure

So let’s start with our Sitefinity instance, for the purpose of this post I’ve created a new Sitefinity instance (with the Community license) and named it SitefinityInAzure running on a local SQL2008R2 database instance. Database name will also be named the same as the Sitefinity site name, SitefinityInAzure. I would highly recommend you practice this migration on a fresh new blank site rather than on actual production site until you get the hang of things.

Start off by making sure your local instance of Sitefinity is golden, we can do this by spinning up Sitefinity Project Manager and browse to Admin area by clicking [Go To Administration] on the context menu.

Do a quick sanity check and validate that everything on the Administration is golden.

Go ahead and create a test page. This can be done by going to Pages > Create New Page > Save > Publish.

View the page.

Now let’s start the database migration first, first in order is to create and setup an instance of SQL Azure.

Go ahead and copy the connection string so that we can use this in a bit.

Launch your Sitefinity instance with Visual Studio.

Open up the DataConfig.config file, where Sitefinity’s connection string is stored and let’s go ahead and modify it to use SQL Azure.

Before:


<?xml version="1.0" encoding="utf-8"?>
<dataConfig xmlns:config="urn:telerik:sitefinity:configuration" xmlns:type="urn:telerik:sitefinity:configuration:type" config:version="5.1.3270.0" initialized="True">
	<connectionStrings>
		<add connectionString="data source=localhost;Integrated Security=SSPI;initial catalog=SitefinityInAzure" providerName="System.Data.SqlClient" dbType="MsSql" name="Sitefinity" />
	</connectionStrings>
	<urlEvaluators>
		<add description="ForumsEvaluatorDescription" resourceClassId="ForumsResources" type="Telerik.Sitefinity.Forums.Web.ForumsEvaluator" enabled="True" name="Forums" />
	</urlEvaluators>
</dataConfig>

After (note the dbType attribute has been set to SqlAzure):


<?xml version="1.0" encoding="utf-8"?>
<dataConfig xmlns:config="urn:telerik:sitefinity:configuration" xmlns:type="urn:telerik:sitefinity:configuration:type" config:version="5.1.3270.0" initialized="True">
	<connectionStrings>
		<add connectionString="data source=[your-azure-servername-goes-here].database.windows.net;initial catalog=SitefinityInAzure; User ID=[your-azure-username-for-sqlazure]; Password=[your-password-for-sqlazure-goes-here]" providerName="System.Data.SqlClient" dbType="SqlAzure" name="Sitefinity" />
	</connectionStrings>
	<urlEvaluators>
		<add description="ForumsEvaluatorDescription" resourceClassId="ForumsResources" type="Telerik.Sitefinity.Forums.Web.ForumsEvaluator" enabled="True" name="Forums" />
	</urlEvaluators>
</dataConfig>

Update your web.config for Azure configuration, first uncomment the sectionGroup with name Telerik.

Before:


  <configSections>
    <section requirePermission="false" name="microsoft.identityModel" type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    <!--<sectionGroup name="telerik">
      <section name="sitefinity" type="Telerik.Sitefinity.Configuration.SectionHandler, Telerik.Sitefinity" requirePermission="false" />
    </sectionGroup>-->
    <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
      <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
      <section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
    </sectionGroup>
  </configSections>

After:


  <configSections>
    <section requirePermission="false" name="microsoft.identityModel" type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    <sectionGroup name="telerik">
      <section name="sitefinity" type="Telerik.Sitefinity.Configuration.SectionHandler, Telerik.Sitefinity" requirePermission="false" />
    </sectionGroup>
    <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
      <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
      <section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
    </sectionGroup>
  </configSections>


Uncomment the Telerik section (towards the end of the web.config file).

Before:


  <!-- Begin telerik section  -->
  <!--<telerik>
    <sitefinity>
      <environment platform="WindowsAzure"/>
      <sitefinityConfig storageMode="Database" />
    </sitefinity>
  </telerik>-->
  <!-- End telerik section  -->


After:


  <!-- Begin telerik section  -->
  <telerik>
    <sitefinity>
      <environment platform="WindowsAzure"/>
      <sitefinityConfig storageMode="Database" />
    </sitefinity>
  </telerik>
  <!-- End telerik section  -->
</configuration>

Now that our Sitefinity app is ready to work with SQL Azure, let’s migrate our actual data first. For this you will need to download SQL Azure Migration Wizard (http://sqlazuremw.codeplex.com/).

Launch an instance of SQL Azure Migration Wizard and select: Analyze / Migrate – SQL Database option and click next.

Select the current SitefinityInAzure local database that you be migrating from (source).

Generate the SQL Script (that SQL Azure Migration Wizard will use to execute on the target SQL Azure Instance, that we created earlier).

Great now let’s run our local instance of SitefinityInAzure and verify that our on-premise app is working with our off-premise SQL Azure instance where we just migrated our data too. We can do this by browsing to the Administration page.

Great, we are half way there now. All we have left is getting our application Azure ready and deployed to Azure.

Let’s start off by making sure that all the required assemblies will be deployed in the Azure by expanding the references folder and checking that all the required assemblies to be deployed to Azure by setting the “Copy Local” to true.

Be default all of the required assemblies should already have the property “Copy Local” set to true set already, so there should not be any work here required besides a quick check, this step can be skipped.

This should be done for all references with the exception of:

  • System.Windows
  • Microsoft.WindowsAzure.ServiceRuntime
  • MySql.Data
  • Telerik.OpenAccess.MySql.Data
  • Interop.DexterLib

Now let’s go ahead and add our Azure Web Role to our solution, right click the Solution and add a Windows Azure Cloud Service project from VS templates and give it a name SitefinityWebRole.

When prompted for what type of role you would like to include, DO NOT SELECT ANY OPTION (the right side selected list box should be blank), we will manually add this in the next step, click OK.

Now with the context menu of the SitefinityWebRole Roles folder click “Web Role Project in solution” and choose SitefinityWebApp as the project to be added as the WebRole.

Let’s go ahead and create a blob storage in Azure so that Sitefinity will have a place to log to, go ahead and name the Blog Storage: sitefinityinazure.

Copy the primary access key for you Azure Blob Storage.

Let’s go ahead and configure the new SitefinityWebRole by go the properties of the newly added Role (click on the properties of the context menu of SitefinityWebRole/Roles/SitefinityWebApp).

Configuration, make sure the Service Configuration “Cloud” is selected.

Endpoints

Local Storage

Registry, the last thing we need to do is reorder some permissions, or you may get the an exception with the Azure runtime starts up in debug mode. Browse to HKLM/Software/Microsoft/Windows Azure with Regedt32, you will get a prompt that this action will Re-Order your permissions click OK, and on the next screen click Apply then OK. Do the same for the HKLM/Software/Microsoft/Windows Azure Emulator.

The WebRole configuration is now complete, now before deploying anything, let’s validate that everything works fine locally with the Azure SDK and the Azure Emulators by hitting F5 or clicking the debug button within Visual Studio.

If you get this prompt to modify your web.config for debugging, go ahead and click OK.

You should see the Windows Azure Debugging Environment spin up (this could take a minute or two).

We should see the “About Us” test page we created earlier running on our locally on our Windows Emulators.

Now, let’s deploy to Azure!

You can skip this step, however I went ahead and configured this so that I could RDP into this instance.

Click on Publish, this could take a while since you are also provisioning your WebRole service for the first time as well as deploying. Once completed you should see this area in your Windows Azure Activity Log.

Click on the Website Url link.

I will leave the Windows Azure resources/instances that were created for the purposes of this blog for as long as I can, until then you can view the actual Sitefinity instance that was used to deploy in Azure at this Url: http://sitefinityinazure.cloudapp.net.

Made a few changes to the content on the “About Us” page we created earlier just to excercise some of Sitefinity’s CMS abilities 😉

Log-in to the back end (don’t want to state the obvious here, however our Url is http://sitefinityinazure.cloudapp.net/Sitefinity/Dashboard, in Azure).

Sitefinity has been migrated and deployed into Windows Azure!

One last note that I thought was interesting, was how easy it is connecting with RDP to your Azure Cloud Service instance.

Log in to the RDP session with your server credentials you used when deploying.

Happy CMS’ing..! 🙂

Telerik’s HTML5 Kendo UI Web Validator Framework

I recently wrote a blog on Telerik’s Kendo UI Web MVVM Framework. This blog post was intended to illustrate the power of MVVM binding on the client side (declaritive binding).

In this blog post I wanted to demonstrate the ability to do some nice validation with the Kendo UI Web Validator Framework which also let’ you do a quite bit of validtion declaritively. With that beind said let’s get right into it using the MVVM form we created from the previous post.

Let’s take a quick at where we left off at on our last form.


    <div id="formContainer">
        <h1>
            My First MVVM Web View!</h1>
        <form id="myForm" action="">
        <label>
            First Name</label>
        <input type="text" data-bind="value: firstName, disabled:  isDisabled" />
        <label>
            Last Name</label>
        <input type="text" data-bind="value: lastName, disabled:  isDisabled" />
        <label>
            Email</label>
        <input type="text" data-bind="value: email, disabled:  isDisabled" />
        <label>
            Twitter</label>
        <input type="text" data-bind="value: twitter, disabled:  isDisabled" />
        <label>
            Site</label>
        <input type="text" data-bind="value: site, disabled:  isDisabled" />
        <label>
            Address</label>
        <input type="text" data-bind="value: address, disabled:  isDisabled" />
        <label>
            City</label>
        <input type="text" data-bind="value: city, disabled:  isDisabled" />
        <label>
            State</label>
        <input type="text" data-bind="value: state, disabled:  isDisabled" />
        <label>
            Zip</label>
        <input type="text" data-bind="value: zip, disabled:  isDisabled" />
        <label>
            Occupation</label>
        <select data-bind="source: occupations, value: occupation, disabled:  isDisabled">
        </select>
        <br />
        <br />
        <input type="button" value="Load" data-bind="click: load" />
        <input type="button" value="Edit" data-bind="click: edit" />
        <input type="button" value="Cancel" data-bind="click: cancel" />
        <input type="button" value="Reset" data-bind="click: reset" />
        </form>
    </div>

Now let’s add a couple of extra fields and declaritively add some validation to our form. While doing this let’s go ahead and change the types to our input controls with the appropriate HTML5 (http://www.w3schools.com/html5/html5_form_attributes.asp) types e.g. date, number, email, etc.. Quick note, notice that we are using the HTML5 [pattern] attribute for the input field named “Phone”.


    <div id="formContainer">
        <h1>
            My First MVVM Web View <br />
            now with Validation!</h1>
        <form id="myForm" action="">
        <label>
            First Name</label>
        <input type="text" name="firstName" data-bind="value: firstName, disabled:  isDisabled" required validationMessage="please enter a first name" />
        <label>
            Last Name</label>
        <input type="text" name="lastName" data-bind="value: lastName, disabled:  isDisabled" required validationMessage="please enter a last name" />
        <label>
            Email</label>
        <input type="email" name="email" data-bind="value: email, disabled:  isDisabled" required />
        <label>
            Twitter</label>
        <input type="url" name="twitter" data-bind="value: twitter, disabled:  isDisabled" required data-required-msg="please enter a {0}" data-url-msg="please enter a valid url" />
        <label>
            Site</label>
        <input type="url" name="site" data-bind="value: site, disabled:  isDisabled" required validationMessage="please enter a {0}" data-url-msg="please enter a valid url"/>
        <label>
            Address</label>
        <input type="text" name="address" data-bind="value: address, disabled:  isDisabled" required validationMessage="please enter a {0}" />
        <label>
            City</label>
        <input type="text" name="city" data-bind="value: city, disabled:  isDisabled" required validationMessage="please enter a {0}"/>
        <label>
            State</label>
        <input type="text" name="state" data-bind="value: state, disabled:  isDisabled" required validationMessage="please enter a {0}"/>
        <label>
            Zip</label>
        <input type="text" name="zip" data-bind="value: zip, disabled:  isDisabled" required validationMessage="please enter a {0}"/>
        <label>
            Phone</label>
        <input type="tel" name="phone" pattern="\d{10}"  data-bind="value: phone, disabled:  isDisabled" required data-required-msg="please enter a {0} number" data-pattern-msg="please enter a 10 digit phone number"/>
        <label>
            Lucky Number</label>
        <input type="number" name="luckynumber" data-bind="value: luckyNumber, disabled:  isDisabled" required data-required-msg="please enter a lucky number" data-max-msg="please enter a number less than 100" min="1" max="100" />
        <label>
            Birth Date</label>
        <input type="date" name="birthdate" data-bind="value: birthDate, disabled:  isDisabled" required validationMessage="please enter a {0}" />
        <label>
            Occupation</label>
        <select name="occupation" data-bind="source: occupations, value: occupation, disabled:  isDisabled" required valdationMessage="please select a {0}">
        </select>
        <br />
        <br />
        <input type="button" value="Load" data-bind="click: load" />
        <input type="button" value="Edit" data-bind="click: edit" />
        <input type="button" value="Cancel" data-bind="click: cancel" />
        <input type="button" value="Reset" data-bind="click: reset" />
        <button type="submit" value="Submit">Save</button>
        </form>
    </div>

Notice that all we are doing here is really providing the Validator Framework with is validation messages for the type of validation. There is a convention that we need to follow which is marking the control with the attribute: data-[rule]-msg, [rule] just needs to be replaced with actual rule name so for example data-required-msg=”please enter a luckly number” will be the message that is displayed when there is no number in the input field and data-max-message=”please enter a number less than 100″ will be the validation message that is shown if a user enters a number that is greater than 100. Now that all of our validation messages are setup let’s add our one line of script to get this all of our validations wired up (line 45).


    var myViewModel;
    $(document).ready(function () {
        myViewModel = kendo.observable({
            firstName: "Long",
            lastName: "Le",
            email: "lelong37@gmail.com",
            twitter: "twitter.com/lelong37",
            site: "blog.longle.net",
            address: "3737 Galatic Avenue",
            city: "Cloud City",
            state: "Texas",
            occupations: ["", "Hacker", "Jedi", "Ninja"],
            occupation: "Jedi",
            phone: "1111111111",
            luckyNumber: 34,
            birthDate: null,
            isSaved: false,
            isDisabled: true,
            edit: function (e) {
                this.set("isDisabled", false);
            },
            cancel: function (e) {
                this.set("isDisabled", true);
            },
            reset: function (e) {
                this.set("firstName", null);
                this.set("lastName", null);
                this.set("email", null);
                this.set("twitter", null);
                this.set("site", null);
                this.set("address", null);
                this.set("city", null);
                this.set("state", null);
                this.set("zip", null);
                this.set("occupation", "");
                this.set("phone", null);
                this.set("luckyNumber", null);
            },
            load: function (e) {
                LoadJohnDoesInfo();
            }
        });

        kendo.bind($("#myForm"), myViewModel);
        var validator = $("#myForm").kendoValidator().data("kendoValidator");
    });

Now with our declaritive validation and one line of Javascript to wire everything up let’s give our form a spin. Let’s clear out our form by and try saving by clicking [Edit], [Reset] and [Save].

Let’s test out our messsages that are custom to a specific validation type by typing in an invalid Url for our Twitter, Site and invalid email address when filling out our form and clicking [Save].

Notice how are validation messages are not the displaying the standard required messages but now they are validation messages specific to the type of validation. For example with the field “Lucky Number” the standard validation message was “please enter a lucky number” now that we actually typed in a value that is greater than the max attribute that was set to 100 we are getting the declartive validation message “please enter a number less than 100”. Also for the field [email] we also have typed in something however because we changed the input type to email (new with HTML5) out of the box the framework is displaying a standard message for us “email is not a valid email” even though we did not explicitly declare a validation message for email.

Now let’s fix all the fields with the correct values email, twitter, site, phone and lucky number and notice as we tab out of each field our validation messages dissapear and we are able to post the form.

Last but not least we can manually invoke the Validator by invoking the validate() method, let’s go ahead and rewire our click button to go ahead and validate our form as well as display a message to the user.


        $("button").click(function (e) {
            e.preventDefault();
            if (validator.validate()) {
                alert("Your form is golden!");
            } else {
                alert("Your form has errors!");
            }
        });

Let’s give this a try.

Happy Coding! 🙂

Download:

Telerik’s Kendo UI Web MVVM Framework Rocks!

For those of us that fell in love with the awesome binding power that WPF and Silverlight brought to the masses with MVVM (Model-ViewModel-Model), well I have good news for you folks. Telerik has their Kendo UI Web MVVM Framework so that you can leverage MVVM in your (MVC) web apps!

Quick synopsis on MVVM for those of us that are new to it (especially MVC devs) and in the interest of time I’ll try to do this in a 60 second nutshell. So when working with a view whether it be in WPF, Silverlight, and now MVC, you can declaritively set which controls bind to which properties to your ViewModel (simply a JSON object with properties). So for example I can set a DropDownList (select) control on my view to bind directly to a property of my ViewModel that is a collection.

So let’s get right to it with a few simple examples.

First add a script references for jQuery and Kendo UI, here are some links for Microsoft’s and Teleriks’ CDN’s for these scripts.


&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Transitional//EN&quot; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;&gt;
&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot;&gt;
&lt;head&gt;
    &lt;title&gt;Kendo UI Web MVVM&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;/body&gt;
&lt;script src=&quot;http://code.jquery.com/jquery-1.7.1.min.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;http://cdn.kendostatic.com/2012.2.710/js/kendo.all.min.js&quot;&gt;&lt;/script&gt;
&lt;/html&gt;

Now let’s create a simple form.


&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Transitional//EN&quot; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;&gt;
&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot;&gt;
&lt;head&gt;
    &lt;title&gt;Kendo UI Web MVVM&lt;/title&gt;
    &lt;script src=&quot;http://code.jquery.com/jquery-1.7.1.min.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;http://cdn.kendostatic.com/2012.2.710/js/kendo.all.min.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
    &lt;style type=&quot;text/css&quot;&gt;
        body
        {
            font-family: Arial;
        }
        label
        {
            display: block;
            margin-top: 10px;
        }
        #formContainer
        {
            background-color: #F2F7F9;
            width: 400px;
            padding: 20px;
            margin: 50px auto;
            border: 6px solid #8FB5C1;
            border-radius: 15px;
            position: relative;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div id=&quot;formContainer&quot;&gt;
        &lt;h1&gt;My First MVVM Web View!&lt;/h1&gt;
        &lt;form id=&quot;myForm&quot; action=&quot;&quot;&gt;
        &lt;label&gt;
            First Name&lt;/label&gt;
        &lt;input type=&quot;text&quot; /&gt;
        &lt;label&gt;
            Last Name&lt;/label&gt;
        &lt;input type=&quot;text&quot;  /&gt;
        &lt;label&gt;
            Email&lt;/label&gt;
        &lt;input type=&quot;text&quot;  /&gt;
        &lt;label&gt;
            Twitter&lt;/label&gt;
        &lt;input type=&quot;text&quot;  /&gt;
        &lt;label&gt;
            Site&lt;/label&gt;
        &lt;input type=&quot;text&quot; /&gt;
        &lt;label&gt;
            Address&lt;/label&gt;
        &lt;input type=&quot;text&quot; /&gt;
        &lt;label&gt;
            City&lt;/label&gt;
        &lt;input type=&quot;text&quot; /&gt;
        &lt;label&gt;
            State&lt;/label&gt;
        &lt;input type=&quot;text&quot; /&gt;
        &lt;label&gt;
            Zip&lt;/label&gt;
        &lt;input type=&quot;text&quot; /&gt;
        &lt;label&gt;
            Occupation&lt;/label&gt;
        &lt;select&gt;
        &lt;/select&gt;
        &lt;br /&gt;
        &lt;br /&gt;
        &lt;input type=&quot;button&quot; /&gt;
        &lt;input type=&quot;button&quot; /&gt;
        &lt;input type=&quot;button&quot; /&gt;
         &lt;input type=&quot;button&quot; /&gt;
        &lt;/form&gt;
    &lt;/div&gt;
&lt;/body&gt;

&lt;/html&gt;

Now let’s create a ViewModel (JavaScript JSON object or class if you will) with some default values for our View (in this case our form) to bind to. We will also add some methods to our ViewModel so that we can bind our buttons that are on our view too.


  &lt;script language=&quot;javascript&quot; type=&quot;text/javascript&quot;&gt;

    var myViewModel;
    $(document).ready(function () {
        myViewModel = kendo.observable({
            firstName: &quot;Long&quot;,
            lastName: &quot;Le&quot;,
            email: &quot;lelong37@gmail.com&quot;,
            twitter: &quot;twitter.com/lelong37&quot;,
            site: &quot;blog.longle.net&quot;,
            address: &quot;3737 Galatic Avenue&quot;,
            city: &quot;Cloud City&quot;,
            state: &quot;Texas&quot;,
            occupations: [&quot;Please Select&quot;, &quot;Hacker&quot;, &quot;Jedi&quot;, &quot;Ninja&quot;],
            occupation: &quot;Jedi&quot;,
        });

        kendo.bind($(&quot;#myForm&quot;), myViewModel);
    });

&lt;/script&gt;

Note: Line 18 is what will bind our form to our ViewModel, if your wondering why nothing is happening yet it’s because we are missing once more step which is to provide the binding (mapping) information, which will map our controls to our ViewModel. The beauty here is we will do this declaritively with without code.

Our View will have two modes: read-only and edit, so we will bind our controls for where each control will get and set it’s values to and another binding for enabling and disabling them.


    &lt;div id=&quot;formContainer&quot;&gt;
        &lt;h1&gt;My First MVVM Web View!&lt;/h1&gt;
        &lt;form id=&quot;myForm&quot; action=&quot;&quot;&gt;
        &lt;label&gt;
            First Name&lt;/label&gt;
        &lt;input type=&quot;text&quot; data-bind=&quot;value: firstName, disabled:  isDisabled&quot; /&gt;
        &lt;label&gt;
            Last Name&lt;/label&gt;
        &lt;input type=&quot;text&quot; data-bind=&quot;value: lastName, disabled:  isDisabled&quot; /&gt;
        &lt;label&gt;
            Email&lt;/label&gt;
        &lt;input type=&quot;text&quot; data-bind=&quot;value: email, disabled:  isDisabled&quot; /&gt;
        &lt;label&gt;
            Twitter&lt;/label&gt;
        &lt;input type=&quot;text&quot; data-bind=&quot;value: twitter, disabled:  isDisabled&quot; /&gt;
        &lt;label&gt;
            Site&lt;/label&gt;
        &lt;input type=&quot;text&quot; data-bind=&quot;value: site, disabled:  isDisabled&quot; /&gt;
        &lt;label&gt;
            Address&lt;/label&gt;
        &lt;input type=&quot;text&quot; data-bind=&quot;value: address, disabled:  isDisabled&quot; /&gt;
        &lt;label&gt;
            City&lt;/label&gt;
        &lt;input type=&quot;text&quot; data-bind=&quot;value: city, disabled:  isDisabled&quot; /&gt;
        &lt;label&gt;
            State&lt;/label&gt;
        &lt;input type=&quot;text&quot; data-bind=&quot;value: state, disabled:  isDisabled&quot; /&gt;
        &lt;label&gt;
            Zip&lt;/label&gt;
        &lt;input type=&quot;text&quot; data-bind=&quot;value: zip, disabled:  isDisabled&quot; /&gt;
        &lt;label&gt;
            Occupation&lt;/label&gt;
        &lt;select data-bind=&quot;source: occupations, value: occupation, disabled:  isDisabled&quot;&gt;
        &lt;/select&gt;
        &lt;br /&gt;
        &lt;br /&gt;
        &lt;input type=&quot;button&quot; value=&quot;Load&quot; /&gt;
        &lt;input type=&quot;button&quot; value=&quot;Edit&quot; /&gt;
        &lt;input type=&quot;button&quot; value=&quot;Cancel&quot; /&gt;
         &lt;input type=&quot;button&quot; value=&quot;Reset&quot; /&gt;
        &lt;/form&gt;
    &lt;/div&gt;

Now if we refresh the page we that our View is not bound to our ViewModel…!

Note: Notice that our form is in read-only mode, all of our controls are disabled for editing because the disabled attribute is bound to the isDisabled property in our ViewModel whose default value is set to true.

For those of us that are ASP.NET MVC developers, the answer is yes you can get and post your ViewModel back as JSON using jQuery from and to an actions on your controller!

Now let’s demonstrate binding our buttons to methods on our ViewModel, first let’s add a couple of methods to our ViewModel (edit, cancel, reset, and load)

  • edit, will enabled our View for editing
  • cancel, will set our View back to read-only mode
  • reset, will clear out our View
  • load, will load John Doe’s information into our View (obviously here you could load this from using a “GET” to an action off of your controller)

    var myViewModel;
    $(document).ready(function () {
        myViewModel = kendo.observable({
            firstName: &quot;Long&quot;,
            lastName: &quot;Le&quot;,
            email: &quot;lelong37@gmail.com&quot;,
            twitter: &quot;twitter.com/lelong37&quot;,
            site: &quot;blog.longle.net&quot;,
            address: &quot;3737 Galatic Avenue&quot;,
            city: &quot;Cloud City&quot;,
            state: &quot;Texas&quot;,
            occupations: [&quot;Please Select&quot;, &quot;Hacker&quot;, &quot;Jedi&quot;, &quot;Ninja&quot;],
            occupation: &quot;Jedi&quot;,
            isSaved: false,
            isDisabled: true,
            edit: function (e) {
                this.set(&quot;isDisabled&quot;, false);
            },
            cancel: function (e) {
                this.set(&quot;isDisabled&quot;, true);
            },
            reset: function (e) {
                this.set(&quot;firstName&quot;, null);
                this.set(&quot;lastName&quot;, null);
                this.set(&quot;email&quot;, null);
                this.set(&quot;twitter&quot;, null);
                this.set(&quot;site&quot;, null);
                this.set(&quot;address&quot;, null);
                this.set(&quot;city&quot;, null);
                this.set(&quot;state&quot;, null);
                this.set(&quot;zip&quot;, null);
                this.set(&quot;occupation&quot;, &quot;Please Select&quot;);
            },
            load: function (e) {
                LoadJohnDoesInfo();
            }
        });

        kendo.bind($(&quot;#myForm&quot;), myViewModel);
    });

    function LoadJohnDoesInfo() {
        myViewModel.set(&quot;firstName&quot;, &quot;John&quot;);
        myViewModel.set(&quot;lastName&quot;, &quot;Doe&quot;);
        myViewModel.set(&quot;email&quot;, &quot;jdoe@skyranch.com&quot;);
        myViewModel.set(&quot;twitter&quot;, &quot;twitter.com/jedi&quot;);
        myViewModel.set(&quot;site&quot;, &quot;starwars.com&quot;);
        myViewModel.set(&quot;address&quot;,  &quot;1212 SkyRanch&quot;);
        myViewModel.set(&quot;state&quot;, &quot;California&quot;);
        myViewModel.set(&quot;zip&quot;, &quot;98000&quot;);
        myViewModel.set(&quot;occupation&quot;, &quot;Jedi&quot;);
    }

Add our binding meta-data to our buttons declaritively.


        &lt;input type=&quot;button&quot; value=&quot;Load&quot; data-bind=&quot;click: load&quot; /&gt;
        &lt;input type=&quot;button&quot; value=&quot;Edit&quot; data-bind=&quot;click: edit&quot; /&gt;
        &lt;input type=&quot;button&quot; value=&quot;Cancel&quot; data-bind=&quot;click: cancel&quot; /&gt;
        &lt;input type=&quot;button&quot; value=&quot;Reset&quot; data-bind=&quot;click: reset&quot; /&gt;


So let’s invoke some our methods on our ViewModel and give our button’s a run for their money.

Now let’s click on the [Load] button and here I just wanted to demonstrating that all we need to do here to update the View is interact with the ViewModel, any changes to the ViewModel and the View automatically updates because they are bound together, which is the magic and essense of the MVVM pattern!

Our load method on our ViewModel.


            load: function (e) {
                LoadJohnDoesInfo();
            }

Which turns around and invokes our LoadJohnDoesInfo method and set’s his data on our ViewModel.


    function LoadJohnDoesInfo() {
        myViewModel.set(&quot;firstName&quot;, &quot;John&quot;);
        myViewModel.set(&quot;lastName&quot;, &quot;Doe&quot;);
        myViewModel.set(&quot;email&quot;, &quot;jdoe@skyranch.com&quot;);
        myViewModel.set(&quot;twitter&quot;, &quot;twitter.com/jedi&quot;);
        myViewModel.set(&quot;site&quot;, &quot;starwars.com&quot;);
        myViewModel.set(&quot;address&quot;, &quot;1212 SkyRanch&quot;);
        myViewModel.set(&quot;state&quot;, &quot;California&quot;);
        myViewModel.set(&quot;zip&quot;, &quot;98000&quot;);
        myViewModel.set(&quot;occupation&quot;, &quot;Jedi&quot;);
    }

Notice when the View first loads our entire form is disabled for our read-only mode and when we click [Edit] all of our controls that had the binding for disabled that was bound to the isDisabled property on our ViewModel which be default is set to true. Once we click on the Edit button the isDisabled property is set to true enabling all of our controls for edit mode.

Our declaritive binding on one of our controls.


&lt;input type=&quot;text&quot; data-bind=&quot;value: firstName, disabled:  isDisabled&quot; /&gt;

The Edit method that is invoked when clicking the [Edit] button.


            edit: function (e) {
                this.set(&quot;isDisabled&quot;, false);
            },

Hitting the [Reset] button will simply invoke the reset method on our ViewModel which clears out all our properties by setting them null values and again our View is automagically updated because it is bound to our ViewModel.


            reset: function (e) {
                this.set(&quot;firstName&quot;, null);
                this.set(&quot;lastName&quot;, null);
                this.set(&quot;email&quot;, null);
                this.set(&quot;twitter&quot;, null);
                this.set(&quot;site&quot;, null);
                this.set(&quot;address&quot;, null);
                this.set(&quot;city&quot;, null);
                this.set(&quot;state&quot;, null);
                this.set(&quot;zip&quot;, null);
                this.set(&quot;occupation&quot;, &quot;Please Select&quot;);
            },

Well great, I know we aren’t doing anything ground breaking here, however this post is really just to illustrate MVVM on the client-side using Telerik’s Kendo UI Web MVVM Framework and ellaborate on how we can really just work with the ViewModel to update the View vice- versa any updates to the View will update our ViewModel because they are “bound” together using MVVM.

Last but not least, even though the Kendo UI Framework was written with jQuery, notice how we didn’t have to explicitly use any jQuery or Javascript to set or get any values from our controls. I’m not at all saying jQuery is a bad thing, however when developing you probably want to be coding something that is immediately adding business value rather than coding jQuery selectors to get and and set values in your View right..? :p

Happy coding…! 🙂

Download sample code: https://skydrive.live.com/redir?resid=949A1C97C2A17906!1951

Telerik’s HTML5 Kendo UI Grid with Server Side Paging, Sorting & Filtering with MVC3, EF4 & Dynamic LINQ

Update: 06/18/2013 – It is recommended that you follow this post for Kendo UI Grid, Datasource filtering http://blog.longle.net/2013/06/18/mvc-4-web-api-odata-entity-framework-kendo-ui-grid-datasource-with-mvvm/

Update: 05/11/2012 – Added support for querying objects with child properties

Update: 04/24/2012 – Added recursion to the filters processing to support multiple search criterias on the same field while at the same time supporting searches across multiple fields.

I recently did a post Telerik’s HTML5 Kendo UI (Grid, Detail Template, TabStrip) which illustrated how to wire up their HTML5 Grid and handle server side paging. After doing so I quickly found myself needing to wire up the rest of server side bells and whistles e.g. sorting, filtering, etc.. Did some relentless googling and didn’t find any good resources on how to do this with MVC3 and EF4 so hence this blog post for the those of us that are doing just that. Rather than starting from scratch I’ll go ahead and continue where the my last blog left off.

So this first thing we need to do is configure our Kendo UI Grid for to do server side sorting and filtering so that we decompose what the requests pay loads look like coming from the Grid when performing these types of actions on it.

Configuring the Kendo UI Grid:


    $(document).ready(function () {
        var grid = $(&quot;#grid&quot;).kendoGrid({
            dataSource: {
                type: &quot;json&quot;,
                serverPaging: true,
                serverSorting: true,
                serverFiltering: true,
                allowUnsort: true,
                pageSize: 5,
                transport: {
                    read: {
                        url: &quot;Products/GetAll&quot;,
                        dataType: &quot;json&quot;,
                        type: &quot;POST&quot;,
                        contentType: &quot;application/json; charset=utf-8&quot;,
                        data: {}
                    },
                    parameterMap: function (options) {
                        return JSON.stringify(options);
                    }
                },
                schema: {
                    model: {
                        fields: {
                            ProductId: { type: &quot;number&quot; },
                            Name: { type: &quot;string&quot; },
                            Status: { type: &quot;string&quot; },
                            Created: { type: &quot;date&quot; }
                        }
                    },
                    data: &quot;Products&quot;,
                    total: &quot;TotalCount&quot;
                }
            },
            height: 700,
            sortable: true,
            groupable: true,
            pageable: true,
            filterable: true,
            columns: [
                    { field: &quot;ProductId&quot;, title: &quot;ProductId&quot; },
                    { field: &quot;ProductType&quot;, title: &quot;ProductType&quot; },
                    { field: &quot;Name&quot;, title: &quot;Name&quot; },
                    { field: &quot;Created&quot;, title: &quot;Created&quot;, format: &quot;{0:MM/dd/yyyy}&quot; }
                ],
            detailTemplate: kendo.template($(&quot;#template&quot;).html()),
            toolbar: kendo.template($(&quot;#toolBarTemplate&quot;).html()),
            detailInit: detailInit,
            dataBound: function () {
                this.expandRow(this.tbody.find(&quot;tr.k-master-row&quot;).first());
            }
        });

        var dropDown = grid.find(&quot;#requestType&quot;).kendoDropDownList({
            dataTextField: &quot;text&quot;,
            dataValueField: &quot;value&quot;,
            autoBind: false,
            optionLabel: &quot;All&quot;,
            dataSource: [
                    { text: &quot;Electronics&quot;, value: &quot;2&quot; },
                    { text: &quot;Machinery&quot;, value: &quot;1&quot; }
                ],
            change: function () {
                var value = this.value();
                if (value) {
                    grid.data(&quot;kendoGrid&quot;).dataSource.filter(
                        { field: &quot;ProductType&quot;, operator: &quot;eq&quot;, value: parseInt(value) });
                } else {
                    grid.data(&quot;kendoGrid&quot;).dataSource.filter({});
                }
            }
        });
    });

I’ve highlighted some of the major changes we made to our configuration which include setting up the Grid for server side actions: paging, sorting, filter, unsort and surfacing the filteration capabilities to the UI. Lines 54-72 is for setting up a Grid Toolbar which will contain a Kendo UI DrownDownList so that we can filter the Grid on ProductTypes which we will come back around to later on.

Now that we have the Grid configured for server side processing let’s take a quick look at what’s going down the wire in terms of pay loads for each of these actions so that we can mock up our models for these requests. When loading up IE Developer Tools (hit F12 or Tools > Developer Tools) and clicking on the Network Tab to start capturing network traffic we can see the actual pay load request for each of these actions.

So we can see that the pay load that is coming down the wire when a user performs a filter and sort on the grid is:


{&quot;take&quot;:5,&quot;skip&quot;:0,&quot;page&quot;:1,&quot;pageSize&quot;:5,&quot;group&quot;:[],&quot;filter&quot;:{&quot;filters&quot;:[{&quot;field&quot;:&quot;ProductType&quot;,&quot;operator&quot;:&quot;eq&quot;,&quot;value&quot;:&quot;3&quot;}],&quot;logic&quot;:&quot;and&quot;},&quot;sort&quot;:[{&quot;field&quot;:&quot;Name&quot;,&quot;dir&quot;:&quot;desc&quot;}]}

From this we can start mocking up our models needed for these types of Grid Actions for our Controller.


namespace MvcApplication3.Models
{
    public class GridFilter
    {
        public string Operator { get; set; }
        public string Field { get; set; }
        public string Value { get; set; }
    }

    public class GridFilters
    {
        public List&lt;GridFilter&gt; Filters { get; set; }
        public string Logic { get; set; }
    }

    public class GridSort
    {
        public string Field { get; set; }
        public string Dir { get; set; }
    }
}

Making changes to our Controller Action

We need to make changes to our existing Action on our Controller to support these new Grid objects that is being posted from our Grid when a user does a server side sort, filter, etc..


    public class ProductsController : Controller
    {
        [HttpPost]
        public JsonResult GetAll(int skip, int take, int page, int pageSize, 
            List&lt;GridSort&gt; sort = null, GridFilters filter = null)
        {
            var myDatabaseContext = new MyDatabaseContext();

            var products = myDatabaseContext.Products.AsQueryable();
            var totalCount = myDatabaseContext.Products.AsQueryable();

            if (filter != null &amp;&amp; (filter.Filters != null &amp;&amp; filter.Filters.Count &gt; 0))
            {
                string whereClause = null;
                var parameters = new List&lt;object&gt;();
                var filters = filter.Filters;

                for (var i = 0; i &lt; filters.Count; i++)
                {
                    if (i == 0)
                        whereClause += string.Format(&quot; {0}&quot;, 
                            BuildWhereClause&lt;Product&gt;(i, filter.Logic, filters[i], 
                            parameters));
                    else
                        whereClause += string.Format(&quot; {0} {1}&quot;, 
                            ToLinqOperator(filter.Logic), 
                            BuildWhereClause&lt;Product&gt;(i, filter.Logic, filters[i], 
                            parameters));
                }

                products = products.Where(whereClause, parameters.ToArray());
                totalCount = products.Where(whereClause, parameters.ToArray());
            }

            if (sort != null &amp;&amp; sort.Count &gt; 0)
                foreach (var s in sort)
                {
                    s.Field = (s.Field == &quot;ProductType&quot;) ? &quot;ProductTypeId&quot; : s.Field;
                    products = products.OrderBy(s.Field + &quot; &quot; + s.Dir);
                }

            products = products.Skip(skip).Take(take);

            List&lt;Product&gt; productList = products.ToList();

            var productViewModels = new List&lt;ProductViewModel.Product&gt;();

            foreach (var p in productList)
            {
                productViewModels.Add(new ProductViewModel.Product
                                            {
                                                Completed = p.Completed.Date,
                                                CompletedBy = p.CompletedBy,
                                                Created = p.Created.Date,
                                                CreatedBy = p.CreatedBy,
                                                Name = p.Name,
                                                ProductId = p.ProductId,
                                                ProductType = p.ProductType.Name,
                                                ProductDetails = p.ProductDetails,
                                                Status = p.Status,
                                                Updated = p.Updated.Date,
                                                UpdatedBy = p.UpdatedBy
                                            });
            }

            return Json(
                new ProductViewModel
                    {
                        Products = productViewModels,
                        TotalCount = totalCount.Count()
                    });
        }

        public static string BuildWhereClause&lt;T&gt;(int index, string logic, 
            GridFilter filter, List&lt;object&gt; parameters)
        {
            var entityType = (typeof(T));
            var property = entityType.GetProperty(filter.Field);

            switch (filter.Operator.ToLower())
            {
                case &quot;eq&quot;:
                case &quot;neq&quot;:
                case &quot;gte&quot;:
                case &quot;gt&quot;:
                case &quot;lte&quot;:
                case &quot;lt&quot;:
                    if (typeof(DateTime).IsAssignableFrom(property.PropertyType))
                    {
                        parameters.Add(DateTime.Parse(filter.Value).Date);
                        return string.Format(&quot;EntityFunctions.TruncateTime({0}){1}@{2}&quot;, 
                            filter.Field, 
                            ToLinqOperator(filter.Operator), 
                            index);
                    }
                    if (typeof(int).IsAssignableFrom(property.PropertyType))
                    {
                        parameters.Add(int.Parse(filter.Value));
                        return string.Format(&quot;{0}{1}@{2}&quot;, 
                            filter.Field, 
                            ToLinqOperator(filter.Operator), 
                            index);
                    }
                    parameters.Add(filter.Value);
                    return string.Format(&quot;{0}{1}@{2}&quot;, 
                        filter.Field, 
                        ToLinqOperator(filter.Operator), 
                        index);
                case &quot;startswith&quot;:
                    parameters.Add(filter.Value);
                    return string.Format(&quot;{0}.StartsWith(&quot; + &quot;@{1})&quot;, 
                        filter.Field, 
                        index);
                case &quot;endswith&quot;:
                    parameters.Add(filter.Value);
                    return string.Format(&quot;{0}.EndsWith(&quot; + &quot;@{1})&quot;, 
                        filter.Field, 
                        index);
                case &quot;contains&quot;:
                    parameters.Add(filter.Value);
                    return string.Format(&quot;{0}.Contains(&quot; + &quot;@{1})&quot;, 
                        filter.Field, 
                        index);
                default:
                    throw new ArgumentException(
                        &quot;This operator is not yet supported for this Grid&quot;, 
                        filter.Operator);
            }
        }

        public static string ToLinqOperator(string @operator)
        {
            switch (@operator.ToLower())
            {
                case &quot;eq&quot;: return &quot; == &quot;;
                case &quot;neq&quot;: return &quot; != &quot;;
                case &quot;gte&quot;: return &quot; &gt;= &quot;;
                case &quot;gt&quot;: return &quot; &gt; &quot;;
                case &quot;lte&quot;: return &quot; &lt;= &quot;;
                case &quot;lt&quot;: return &quot; &lt; &quot;;
                case &quot;or&quot;: return &quot; || &quot;;
                case &quot;and&quot;: return &quot; &amp;&amp; &quot;;
                default: return null;
            }
        }

        public JsonResult GetProductDetails(int skip, int take, int page, 
            int pageSize, string group)
        {
            var myDatabaseContext = new MyDatabaseContext();

            var productDetails = myDatabaseContext.ProductDetails
                .OrderBy(p =&gt; p.ProducDetailtId);

            return Json(
                new ProductDetailsViewModel
                    {
                        ProductDetails = productDetails.Skip(skip).Take(take),
                        TotalCount = productDetails.Count()
                    },
                JsonRequestBehavior.AllowGet);
        }
    }

Note: Instead of downloading the LINQ Dynamic Query Library, you may want to actually download the sample application for this post because the DynamicQueryable.cs class from the Linq Dynamic Libray has been slightly modified to handle EntityFunctions to support string search actions from our Grid such as Contains, StartsWidth and EndsWith string searches.

A few quick notes in regards to our changes to our Action on our Controller to now support complete server side processing of paging, sorting and filtering.

  • BuildWhereClause<T>(int index, string logic, GridFilter filter, List parameters)
    This helper method will build our our where clauses and predicates so tha we can chain them up and pass them into Dynamic LINQ.

  • ToLinqOperator(string @operator)

    This helper method will convert operators that are sent from our Grid to C# operators that Dynamic LINQ will understand and convert them for us

  • Lines 48-64, here we are iterating through the results to trim off the timestamp off of any properties that are of type datetime, so that when we do any grouping or filtering from the grid the timestamp of these fields are ignored.

                foreach (var p in productList)
                {
                    productViewModels.Add(new ProductViewModel.Product
                                                {
                                                    Completed = p.Completed.Date,
                                                    CompletedBy = p.CompletedBy,
                                                    Created = p.Created.Date,
                                                    CreatedBy = p.CreatedBy,
                                                    Name = p.Name,
                                                    ProductId = p.ProductId,
                                                    ProductType = p.ProductType.Name,
                                                    ProductDetails = p.ProductDetails,
                                                    Status = p.Status,
                                                    Updated = p.Updated.Date,
                                                    UpdatedBy = p.UpdatedBy
                                                });
                }
    
  • Lines 88-103, here we are checking against the type of the column (property) that we are searching against so that we can convert the search criteria to the appropriate type. Currently we are supporting searches against types of string, datetime and int. If you need to add more types simply enhance this section of the implementation.

                        if (typeof(DateTime).IsAssignableFrom(property.PropertyType))
                        {
                            parameters.Add(DateTime.Parse(filter.Value).Date);
                            return string.Format(&quot;EntityFunctions.TruncateTime({0}){1}@{2}&quot;, 
                                filter.Field, 
                                ToLinqOperator(filter.Operator), 
                                index);
                        }
                        if (typeof(int).IsAssignableFrom(property.PropertyType))
                        {
                            parameters.Add(int.Parse(filter.Value));
                            return string.Format(&quot;{0}{1}@{2}&quot;, 
                                filter.Field, 
                                ToLinqOperator(filter.Operator), 
                                index);
                        }
                        parameters.Add(filter.Value);
    
    
  • Lines 109-123, here we are just framing up the different queries for string searches from the Grid. The Grid supports StartsWith, Contains, and EndsWith.

                    case &quot;startswith&quot;:
                        parameters.Add(filter.Value);
                        return string.Format(&quot;{0}.StartsWith(&quot; + &quot;@{1})&quot;, 
                            filter.Field, 
                            index);
                    case &quot;endswith&quot;:
                        parameters.Add(filter.Value);
                        return string.Format(&quot;{0}.EndsWith(&quot; + &quot;@{1})&quot;, 
                            filter.Field, 
                            index);
                    case &quot;contains&quot;:
                        parameters.Add(filter.Value);
                        return string.Format(&quot;{0}.Contains(&quot; + &quot;@{1})&quot;, 
                            filter.Field, 
                            index);
    

    As you can see in the screenshot right below these are the current string search capabilites that the Grid has.

Great, let’s run a few searches from the Grid now.

  • Search on ProductId: 3

  • Search on Created Date: 04/20/2013

  • Search on Created Date >= 04/15/2012 and Name containing “sample product 3”

    Voila! Our controller only returns 3 records which assert our test case and are all of Created date >= 04/20/2012 and all contain the string “sample product 3” in the name.

Update: 04/24/2012

Added recursion to the filters processing to support multiple search criterias on the same field while at the same time supporting searches across multiple fields.

 

        [HttpPost]
        public JsonResult GetAll(int skip, int take, int page, int pageSize, 
            List&lt;GridSort&gt; sort = null, GridFilter filter = null)
        {
            var myDatabaseContext = new MyDatabaseContext();

            var products = myDatabaseContext.Products.AsQueryable();
            var totalCount = myDatabaseContext.Products.AsQueryable();

            if (filter != null &amp;&amp; (filter.Filters != null &amp;&amp; filter.Filters.Count &gt; 0))
            {
                ProcessFilters(filter, ref products);
                totalCount = products;
            }

            if (sort != null &amp;&amp; sort.Count &gt; 0)
                foreach (var s in sort)
                {
                    s.Field = (s.Field == &quot;ProductType&quot;) ? &quot;ProductTypeId&quot; : s.Field;
                    products = products.OrderBy(s.Field + &quot; &quot; + s.Dir);
                }

            products = products.Skip(skip).Take(take);

            List&lt;Product&gt; productList = products.ToList();

            var productViewModels = new List&lt;ProductViewModel.Product&gt;();

            foreach (var p in productList)
            {
                productViewModels.Add(new ProductViewModel.Product
                                            {
                                                Completed = p.Completed.Date,
                                                CompletedBy = p.CompletedBy,
                                                Created = p.Created.Date,
                                                CreatedBy = p.CreatedBy,
                                                Name = p.Name,
                                                ProductId = p.ProductId,
                                                ProductType = p.ProductType.Name,
                                                ProductDetails = p.ProductDetails,
                                                Status = p.Status,
                                                Updated = p.Updated.Date,
                                                UpdatedBy = p.UpdatedBy
                                            });
            }

            return Json(
                new ProductViewModel
                    {
                        Products = productViewModels,
                        TotalCount = totalCount.Count()
                    });
        }

        public static void ProcessFilters(GridFilter filter, ref IQueryable&lt;Product&gt; queryable)
        {
            var whereClause = string.Empty;
            var filters = filter.Filters;
            var parameters = new List&lt;object&gt;();
            for (int i = 0; i &lt; filters.Count; i++)
            {
                var f = filters[i];

                if (f.Filters == null)
                {
                    if (i == 0)
                        whereClause += BuildWhereClause&lt;Product&gt;(f, i, parameters) + &quot; &quot;;
                    if (i != 0)
                        whereClause += ToLinqOperator(filter.Logic) + 
                            BuildWhereClause&lt;Product&gt;(f, i, parameters) + &quot; &quot;;
                    if (i == (filters.Count - 1))
                    {
                        CleanUp(ref whereClause);
                        queryable = queryable.Where(whereClause, parameters.ToArray());
                    }
                }
                else
                    ProcessFilters(f, ref queryable);
            }
        }

Looks like our server side paging, sorting and filteration is golden!

Update: 05/11/2012- Added support for querying objects with child properties


    public static class GridHelper
    {
        public static void ProcessFilters&lt;T&gt;(GridFilter filter, ref IQueryable&lt;T&gt; queryable)
        {
            var whereClause = string.Empty;
            var filters = filter.Filters;
            var parameters = new List&lt;object&gt;();
            for (int i = 0; i &lt; filters.Count; i++)
            {
                var f = filters[i];

                if (f.Filters == null)
                {
                    if (i == 0)
                        whereClause += BuildWherePredicate&lt;T&gt;(f, i, parameters) + &quot; &quot;;
                    if (i != 0)
                        whereClause += ToLinqOperator(filter.Logic) + BuildWherePredicate&lt;T&gt;(f, i, parameters) + &quot; &quot;;
                    if (i == (filters.Count - 1))
                    {
                        TrimWherePredicate(ref whereClause);
                        queryable = queryable.Where(whereClause, parameters.ToArray());
                    }
                }
                else
                    ProcessFilters(f, ref queryable);
            }
        }

        public static string TrimWherePredicate(ref string whereClause)
        {
            switch (whereClause.Trim().Substring(0, 2).ToLower())
            {
                case &quot;&amp;&amp;&quot;:
                    whereClause = whereClause.Trim().Remove(0, 2);
                    break;
                case &quot;||&quot;:
                    whereClause = whereClause.Trim().Remove(0, 2);
                    break;
            }

            return whereClause;
        }

        public static string BuildWherePredicate&lt;T&gt;(GridFilter filter, int index, List&lt;object&gt; parameters)
        {
            var entityType = (typeof(T));
            PropertyInfo property;
            
            if(filter.Field.Contains(&quot;.&quot;))
                property = GetNestedProp&lt;T&gt;(filter.Field);
            else 
                property = entityType.GetProperty(filter.Field);
            
            var parameterIndex = parameters.Count;

            switch (filter.Operator.ToLower())
            {
                case &quot;eq&quot;:
                case &quot;neq&quot;:
                case &quot;gte&quot;:
                case &quot;gt&quot;:
                case &quot;lte&quot;:
                case &quot;lt&quot;:
                    if (typeof(DateTime).IsAssignableFrom(property.PropertyType))
                    {
                        parameters.Add(DateTime.Parse(filter.Value).Date);
                        return string.Format(&quot;EntityFunctions.TruncateTime(&quot; + filter.Field + &quot;)&quot; + ToLinqOperator(filter.Operator) + &quot;@&quot; + parameterIndex);
                    }
                    if (typeof(int).IsAssignableFrom(property.PropertyType))
                    {
                        parameters.Add(int.Parse(filter.Value));
                        return string.Format(filter.Field + ToLinqOperator(filter.Operator) + &quot;@&quot; + parameterIndex);
                    }
                    parameters.Add(filter.Value);
                    return string.Format(filter.Field + ToLinqOperator(filter.Operator) + &quot;@&quot; + parameterIndex);
                case &quot;startswith&quot;:
                    parameters.Add(filter.Value);
                    return filter.Field + &quot;.StartsWith(&quot; + &quot;@&quot; + parameterIndex + &quot;)&quot;;
                case &quot;endswith&quot;:
                    parameters.Add(filter.Value);
                    return filter.Field + &quot;.EndsWith(&quot; + &quot;@&quot; + parameterIndex + &quot;)&quot;;
                case &quot;contains&quot;:
                    parameters.Add(filter.Value);
                    return filter.Field + &quot;.Contains(&quot; + &quot;@&quot; + parameterIndex + &quot;)&quot;;
                default:
                    throw new ArgumentException(&quot;This operator is not yet supported for this Grid&quot;, filter.Operator);
            }
        }

        public static string ToLinqOperator(string @operator)
        {
            switch (@operator.ToLower())
            {
                case &quot;eq&quot;:
                    return &quot; == &quot;;
                case &quot;neq&quot;:
                    return &quot; != &quot;;
                case &quot;gte&quot;:
                    return &quot; &gt;= &quot;;
                case &quot;gt&quot;:
                    return &quot; &gt; &quot;;
                case &quot;lte&quot;:
                    return &quot; &lt;= &quot;;
                case &quot;lt&quot;:
                    return &quot; &lt; &quot;;
                case &quot;or&quot;:
                    return &quot; || &quot;;
                case &quot;and&quot;:
                    return &quot; &amp;&amp; &quot;;
                default:
                    return null;
            }
        }

        public static PropertyInfo GetNestedProp&lt;T&gt;(String name)
        {
            PropertyInfo info = null;
            var type = (typeof(T));
            foreach(var prop in name.Split('.'))
            {
                info = type.GetProperty(prop);
                type = info.PropertyType;
            }
            return info;
        }
    }

Happy Coding…! 🙂

Download sample application: https://skydrive.live.com/redir.aspx?cid=949a1c97c2a17906&resid=949A1C97C2A17906!465&parid=949A1C97C2A17906!361

https://drive.google.com/file/d/0B91gwLeUpEWBU2tnN0dNeVhSTzA/view?usp=sharing

Setting Telerik’s HTML5 Kendo UI Grid’s Height to 100% when Binding to Remote Data

Update 06/24/2013 – Telerik, Kendo UI Team (Dimo Dimov) has reached out to me (see block quote below), and has taken the time to provide the best practice on how to handle the 100% height for the Kendo UI Web Grid http://jsfiddle.net/dimodi/SDFsz/, please use this approach vs. the approach in the blog post, thanks for visiting…!

I am Dimo Dimov from the Telerik / Kendo UI team. A client recently provided this page as an example of a 100% high Kendo UI Grid. I reviewed the code and the comments and it seems that a lot of people would benefit from some clarifications how percentage heights work.

First of all, please take a look at the following jsFiddle demo, which is similar to the jsFiddle demo above, but is better and simpler in a couple of ways:

http://jsfiddle.net/dimodi/SDFsz/

The example discussed in this blog post is also similar, but has one drawback – it uses hard-coded “magic numbers” in order to workaround the presumably disobeyed web standard requirement regarding percentage heights. Let me explain this requirement in more detail using the new jsFiddle demo as a starting point.

We have the following DOM tree:

First of all, the rule of thumb is that an element with a percentage height should have a parent with explicit height. The rule applies recursively until an element with a pixel height is reached, or until the HTML element is reached, which should have a height as well. This is because percentage heights are always calculated with regard to the set height of the parent element.

Now let’s get back to our example. The Grid should be 100% high and we set such a style. This implies that the parent element () should have an explicit pixel or percentage height. It has none of those, so we apply a 100% height to it. Now we go up and reach the element. It should also have an explicit height, otherwise the and Grid percentage heights will be ignored. So we apply a 100% height to the element as well. Now we have three nested elements that are 100% high and these heights start working. Since 100% high elements should not have vertical borders, margins or paddings, we remove those, as shown in the jsFiddle demo.

With more complex DOM trees the logic is the same – you start with the given element that needs to expand and go up the tree and apply 100% height styles (or any other values) until an element with a pixel height is reached, or until the element is reached. If you have a sequence of nested 100% high elements, removing just one of those heights will cause all the others to stop working. The most common reason for 100% heights to not work is that there is an element with no height, which breaks the sequence of 100% high nested elements.

One thing more to keep in mind is that 100% high elements cannot have siblings, otherwise they will overflow their parent.

This blog post uses the opposite approach compared to the one I describe – the height is calculated from “the outside to the inside”. The only case, in which such an approach makes sense is when the Grid or some of its parent elements have siblings, so 100% heights cannot be applied that easily. Also, this approach can work with parent elements with missing heights, because it relies on the height of the browser window, but I would not recommend this appoach.

Thank you.

I’ve been somewhat wresting with how to set the KendoUI Grid’s height to 100% so that it could fill a user’s screen as much as possible so that it would only show it’s vertical scrollbar only when absolutely needed e.g. when a user’s actual browser real estate is shorter in height than the grid’s content (rows). Those of us that have wired up the Grid binding to remote data e.g. OAuth, JSON, etc.. have been required to explicitly set the Grid’s height.

I’ve visited the Telerik’s forums and googled, unfortunately no working real solution, hence this blog post.

http://www.kendoui.com/forums/ui/grid/dynamic-grid-height.aspx
http://jsfiddle.net/dimodi/4eNu4/33/

If we don’t set the height, the Grid will render as follows:

Note: No height was set for the Grid configuration and it appears as if the Grid has no records when indeed our service is returning records it’s just the Grid’s content div’s height is 0px because we did not explicity set the height, and if we try to set it to a percent 100%, the Grid does not render at all.


    $(document).ready(function () {
        $(&quot;#grid&quot;).kendoGrid({
            dataSource: {
                type: &quot;json&quot;,
                serverPaging: true,
                pageSize: 5,
                transport: { read: { url: &quot;Products/GetAll&quot;, dataType: &quot;json&quot;} },
                schema: { data: &quot;Products&quot;, total: &quot;TotalCount&quot; }
            },
            pageable: true,
            columns: [
                    { field: &quot;ProductId&quot;, title: &quot;ProductId&quot; },
                    { field: &quot;ProductType&quot;, title: &quot;ProductType&quot; },
                    { field: &quot;Name&quot;, title: &quot;Name&quot; },
                    { field: &quot;Created&quot;, title: &quot;Created&quot; }
                ],
            detailTemplate: kendo.template($(&quot;#template&quot;).html()), detailInit: detailInit,
            dataBound: function () {
                this.expandRow(this.tbody.find(&quot;tr.k-master-row&quot;).first());
            }
        });
    });

If explicity set the height, the Grid will render as follows:

Note: Line 10, where we are explicitly setting the height, however notice that we have plenty of screen real estate under grid, and if we could get the Grid could take advantage of that empty space we wouldn’t need the scrollbar.


    $(document).ready(function () {
        $(&quot;#grid&quot;).kendoGrid({
            dataSource: {
                type: &quot;json&quot;,
                serverPaging: true,
                pageSize: 5,
                transport: { read: { url: &quot;Products/GetAll&quot;, dataType: &quot;json&quot;} },
                schema: { data: &quot;Products&quot;, total: &quot;TotalCount&quot; }
            },
            height: 500,
            pageable: true,
            columns: [
                    { field: &quot;ProductId&quot;, title: &quot;ProductId&quot; },
                    { field: &quot;ProductType&quot;, title: &quot;ProductType&quot; },
                    { field: &quot;Name&quot;, title: &quot;Name&quot; },
                    { field: &quot;Created&quot;, title: &quot;Created&quot; }
                ],
            detailTemplate: kendo.template($(&quot;#template&quot;).html()), detailInit: detailInit,
            dataBound: function () {
                this.expandRow(this.tbody.find(&quot;tr.k-master-row&quot;).first());
            }
        });
    });

So a quick way we can remedy this is to calculate how much vertical space we have and set the maximum height of the Grid ourselves. By taking the Html document height and offsetting it factoring in our layout e.g. header and footer.


    $(document).ready(function () {
        $(&quot;#grid&quot;).kendoGrid({
            dataSource: {
                type: &quot;json&quot;,
                serverPaging: true,
                pageSize: 5,
                transport: { read: { url: &quot;Products/GetAll&quot;, dataType: &quot;json&quot;} },
                schema: { data: &quot;Products&quot;, total: &quot;TotalCount&quot; }
            },
            height: $(document).height() - 350,
            pageable: true,
            columns: [
                    { field: &quot;ProductId&quot;, title: &quot;ProductId&quot; },
                    { field: &quot;ProductType&quot;, title: &quot;ProductType&quot; },
                    { field: &quot;Name&quot;, title: &quot;Name&quot; },
                    { field: &quot;Created&quot;, title: &quot;Created&quot; }
                ],
            detailTemplate: kendo.template($(&quot;#template&quot;).html()), detailInit: detailInit,
            dataBound: function () {
                this.expandRow(this.tbody.find(&quot;tr.k-master-row&quot;).first());
            }
        });
    });

Great! Our Grid now renders and takes up the full vertical real estate within the browser on the intial load

Last Problem, now when we resize the browswer, the Grid is NOT automatically resizing itself

No problem, we will add a method for resizing the grid and wire this up to the browser resize event with jQuery.


  $(document).ready(function () {
        $(&quot;#grid&quot;).kendoGrid({
            dataSource: {
                type: &quot;json&quot;,
                serverPaging: true,
                pageSize: 5,
                transport: { read: { url: &quot;Products/GetAll&quot;, dataType: &quot;json&quot;} },
                schema: { data: &quot;Products&quot;, total: &quot;TotalCount&quot; }
            },
            height: 250,
            pageable: true,
            columns: [
                    { field: &quot;ProductId&quot;, title: &quot;ProductId&quot; },
                    { field: &quot;ProductType&quot;, title: &quot;ProductType&quot; },
                    { field: &quot;Name&quot;, title: &quot;Name&quot; },
                    { field: &quot;Created&quot;, title: &quot;Created&quot; }
                ],
            detailTemplate: kendo.template($(&quot;#template&quot;).html()), detailInit: detailInit,
            dataBound: function () {
                this.expandRow(this.tbody.find(&quot;tr.k-master-row&quot;).first());
            }
        });
    });
    
    function resizeGrid() {
        var gridElement = $(&quot;#grid&quot;);
        var dataArea = gridElement.find(&quot;.k-grid-content&quot;);

        var newGridHeight = $(document).height() - 350;
        var newDataAreaHeight = newGridHeight - 65;

        dataArea.height(newDataAreaHeight);
        gridElement.height(newGridHeight);

        $(&quot;#grid&quot;).data(&quot;kendoGrid&quot;).refresh();
    }
    
    $(window).resize(function () {
        resizeGrid();
    });

Now, when grid first loads

Note: Looks great, Grid occupies all the available vertical real estate it can

When user resizes Grid (maximizes browser vertically)

Note: Still Looks great, Grid automatically resizes itself to occupy the new vertical space within the browser after the resize.

Happy Coding…!

Download sample application:

https://skydrive.live.com/redir.aspx?cid=949a1c97c2a17906&resid=949A1C97C2A17906!375&parid=949A1C97C2A17906!361