Making a Well Structured Kendo UI Mobile App in Icenium with Require.js


kendo-scripts-final

The first time you start trying to use Kendo UI Mobile, you may get the feeling that you need to global-scope all your JavaScript. Yes, Kendo looks for a lot of things in the global JS scope, but that is no excuse to make your app an unorganized mess! We can use require.js to make a well structured application that won’t make the app maintainers or you want to cry out in horror. Here is how!

Setting Up

I am doing this in Icenium, which is Telerik’s new cloud based mobile development environment. When you make a new project in Icenium you can choose a Kendo Mobile project template. From there, I delete the scripts\hello-world.js file, and clear out most of the sample HTML.

The default Icenium index.html imports these JavaScript files for us:

        <script src="cordova.js"></script>
        <script src="kendo/js/jquery.min.js"></script>
        <script src="kendo/js/kendo.mobile.min.js"></script>
        <script src="http://maps.google.com/maps/api/js?sensor=true"></script>
        <script src="scripts/hello-world.js"></script>

For now, you can just delete all these script imports, except I tend to leave cordova.js, so the head of our html will only include 1 script:

        <script src="cordova.js"></script>

If you are not using Icenium all you really need to do is make yourself an index.html file, download Kendo Mobile and jQuery, and put them in a folder next to your HTML.
This guide should help you get started.

Get Require.js

Hop over to requirejs.org and download the require.js file.
Add it to your Kendo Mobile app.

Structure your Scripts

I like to use the \scripts folder created by Icenium, but under that I like to separate the JS files into mine, and 3rd party. I usually do this by creating \app and \lib folders under \scripts. Since require.js is a 3rd party lib, I put it in \scripts\libs.

kendo-scripts-folder-layout

If you want to be a purist, you can move the Kendo and jQuery files that were included by Icenium into \scripts\libs too, but I usually just leave them where they are. It is up to you.

Setup Require

To set up require.js, we need to start by making a main.js file. This will server as the config and start point for our scripts. I usually put this in the \scripts folder, not in \scripts\app, because the loading of the rest of our scripts will be based relative to this file.

Within main.js, we first need to configure require. We can tell it how to include jQuery and Kendo Mobile here by specifying the paths to those scripts, as well as defining shims for them. If you aren’t sure what that means, please read the require.js API documentation on the configuration options. Keep in mind that the paths will be relative to this main.js file, so you may need to go up a folder if you didn’t move Kendo and jQuery into \scripts\lib.

kendo-scripts-with-main

Here is the configuration for require.js to load jQuery and Keno Mobile, placed in main.js:

require.config({
    paths: {
        jQuery: "libs/jquery.min",
        kendo: "libs/kendo.mobile.min"
    },
    shim: {
        jQuery: {
            exports: "jQuery"
        },
        kendo: {
            deps: ["jQuery"],
            exports: "kendo"
        }
    }
});

Note that the paths do not contain the “.js” extension.

Include require.js in the HTML

Next we can import require.js, and point it to main.js as the initial script to run. This is done by including this import in the <head>:

<script data-main="scripts/main" src="scripts/libs/require.js"></script>

Note that the path to main.js does not contain the “.js” extension.

Create the Application module

We are going to define one primary module named “app” to serve as our one object exposed in global scope. Everything else will hang off of app.

Start by creating a new file: /scripts/app/app.js

Within this file, define the app module, telling require.js that it is dependent on “jQuery” and “kendo” which were defined in the configuration shims.

Also include an “init” function in the module. This will be called to initialize our app, and in it we can place the standard Kendo Mobile startup code.

Contents of app.js:

define(["jQuery", "kendo"], function ($, kendo) {
    var _kendoApplication;

    return {
        init: function () {
            _kendoApplication = new kendo.mobile.Application(document.body, { transition: "slide" });
        }
    }
});

Initialize the Application

At this point, require.js will start up and run the code in main.js. In that file, after the initial require configuration, we can now initialize our app module, thereby initializing the Kendo Mobile application. We also take the object returned by the app module and place it in the global JS scope.

In main.js:

require.config({
    // ... code omitted for brevity ...
});

// Expose the app module to the global scope so Kendo can access it.
var app;

require(["app/app"], function (application) {
    app = application;
    app.init();
});

Now when we run the application, require loads the app.js module and runs the init() function. Our Kendo Mobile application is fully operational at this point.

Add the first view

Now lets take car of adding the app code for our first view. In the /scripts/app folder, if you plan on having a lot of views, you may want to add a /scripts/app/views subfolder, and create a .js file for your view.

kendo-scripts-main-view

Within the new home.js file, we can define the code for that specific view. For example, we can add functions for the init, beforeShow, and show events:

define([], function () {
    return {
        init: function (initEvt) {
            // ... init event code ...
        },

        beforeShow: function (beforeShowEvt) {
            // ... before show event code ...
        },

        show: function (showEvt) {
            // ... show event code ...
        }
    }
});

Next we need to expose this view in our “app” module, making it accessible from the global scope:

In app.js:

define(["jQuery", "kendo", "app/views/home"], function ($, kendo, homeView) {
    var _kendoApplication;

    return {
        init: function () {
            _kendoApplication = new kendo.mobile.Application(document.body, { transition: "slide" });
        },
        views: {
            home: homeView
        }
    }
});

Finally, we can bind the view HTML element to the events defined in the home view module. This is done by starting with the “app” variable in global scope:

        <div data-role="view" id="home-view" data-title="Hello World!"
            data-init="app.views.home.init"
            data-before-show="app.views.home.beforeShow"
            data-show="app.views.home.show">
        </div>

Adding ViewModels for MVVM

We can also use this arrangement to expose view models to each of our views, by simply adding a “viewModel” property to the view’s module.
For example, back in app/views/main.js:

define(["kendo"], function (kendo) {
    return {
        init: function (initEvt) {},
        beforeShow: function (beforeShowEvt) {},
        show: function (showEvt) {},

        viewModel: kendo.observable({
            message: "This rocks!"
        })
    }
});

And in the HTML, we use the data-model attribute on the view:

        <div data-role="view" id="home-view" data-title="Hello World!"
            data-init="app.views.home.init"
            data-before-show="app.views.home.beforeShow"
            data-show="app.views.home.show"
            data-model="app.views.home.viewModel">

            <h1 data-bind="text: message"></h1>

        </div>

Conclusion

kendo-scripts-final

Now we have a place to organize all our core code and view code! I usually like to add a /scripts/app/data folder too, and put all my Kendo DataSource implementations there.
Maybe this overkill for your project, or maybe it is just what you need to get your huge JavaScript files under control, and most of all keep the global scope uncluttered!

Advertisements
Tagged with: , , , ,
Posted in KendoUI, Programming
38 comments on “Making a Well Structured Kendo UI Mobile App in Icenium with Require.js
  1. HK.Lee says:

    I’m just starting to create mobile app based on icenium and Kendo UI. Your blog shows me how to start and which way I head to. Thank you,Jeff.

  2. David Sheardown says:

    Yes cool example.. have been using Knockout/Require for “normal” web apps, however was a little un-sure if there was anything really different using Kendo (and more importantly Icenium). I tried AppMobi a few times, but even though no doubt it is an excellent product, it just looked cluttered and difficult to get your head around.. so thanks again for this post!!

  3. Shion says:

    This is really really cool. Going to use this as a base for a new project soon. 😉
    Thanks

  4. Thanks for the post Jeff. Pretty much exactly what I was looking for after wondering how I could organise the kendo code a bit better to suit my apps.

  5. amol khot says:

    Nicely Described….

  6. natecorvus says:

    Hi Jeff, thanks again for this article and method. I’m using it verbatim for an app I recently published in the app store using Icenium and KendoUI. I’m updating the app to work with Android now and one thing I was wondering is why you used the Body.HasClass(km-ios) method to see if we’re not on andriod as opposed to using this:
    kendo.support.mobileOS.device === “iphone” || kendo.support.mobileOS.device === “ipad”

    I’m probably being anal but I wasn’t sure if there was a performance gain from using your method instead?

    Thanks again!
    Nathan

  7. Richard Tang says:

    Jeff,
    Why am I getting “Uncaught Error: Mismatched anonymous define() module: function ($, kendo, roomView) ” after I added the second view. The error is from app.js:

    define([“jQuery”, “kendo”, “app/views/home”], function ($, kendo, homeView) {
    var _kendoApplication;

    return {
    init: function () {
    _kendoApplication = new kendo.mobile.Application(document.body, { transition: “slide” });
    },
    views: {
    home: homeView
    }
    }
    });

    define([“jQuery”, “kendo”, “app/views/room”], function ($, kendo, roomView) {

    return {
    init: function () {
    _kendoApplication = new kendo.mobile.Application(document.body, { transition: “slide” });
    },
    views: {
    room: roomView
    }
    }
    });

    It seems that I can’t have more than one “define” statement in app.js.

    In index.htm file, I have only two views:

    Thanks in advance!

    Richard

    • rally25rs says:

      You can not have more than one anonymous define() in a single file because then require.js does not know what the name of that module is. You should be able to put more than 1 define() in a file by providing a name for the module, which becomes the 1st parameter. For example define(“moduleName”, [dependencies], function () {}); If that still does not work, then refer to the require.js documentation.

      • Richard Tang says:

        Jeff,
        You are right. If I give a name to one of the modules, it will not give the error. But I don’t know how to reference the events with the named module. It will give an “Uncaught TypeError: Cannot call method ‘init’ of undefined”. I believe it no longer recognizes “app.views.home.init”. Sorry, I’m new to both Icenium and Required.js. It could be something very basic in Required.js. Can you enhance the example to include at least two views so it will demonstrate the multi-view use case?

        Thanks!

        Richard

      • rally25rs says:

        My blog post separates each view into its own file (home.js for the home view in my example) so you shouldn’t need to have more than 1 define() in a file. Then app.js tells require.js that each of the views is a dependency by adding each view to the array that is the first parameter. So in app.js: define([“views/home”, “views/about”, “views/account”, etc…], …); You might want to do a search for require.js tutorials to get a better understanding of what it is doing to resolve dependencies.

  8. natecorvus says:

    I’m making some additions to my initial app and everything’s going well for the most part… I do notice that a lot of the time after I make some changes to my js files I have to completely uninstall/re-install the app on my ipod. Sometimes it seems to fix it if I just close the app on the ipod, other times a complete uninstall seems to do the trick.

    Do you think this is because of the way require.js caches the js files or loads them using the async method? Did you run into this issue at all with the music store app?

    Thanks!
    Nathan

    • rally25rs says:

      I definitely had similar issues when working on the Kendo UI Music Store. If I remember correctly, there was a bug in Icenium Ion for a while that caused it to cache .js files, but killing Ion and restarting it would fix that. When I was deploying as a real app, not in Ion, I seem to remember similar caching issues where I would occasionally have to reinstall the app if killing it didn’t fix it. I was never sure what caused this, if it was iOS, or Cordova/PhoneGap, or what… When the process is closed/killed, the entire web browser should get removed from memory, so I don’t think it would be require.js caching the modules. Though you could be right, I’m not sure.

      • natecorvus says:

        That’s cool thanks for the response… Yeah, there’s quite a few things there that could be causing it… That’s good to know that killing/closing the process removes the entire browser/web view from cache… I’ll keep plugging away… Thanks!

  9. bart says:

    Great article. Just what I needed. Thanks. I added to this a little bit by modularizing the HTML as well (getting HTML for views out of index.html): https://gist.github.com/bartlewis/6072603

  10. Inhji says:

    Thanks Jeff for this article.
    I’m writing an app in Icenium and my first attempt polluted the global scope like crazy.
    It’s really nice if you get some structure you can build on.

  11. Eduard says:

    What’s this…
    Followed step by step, it doesn’t work.
    I don’t think we have the same version of Icenium though…

  12. Shawn says:

    Do you happen to have this example used in this article in a zip file that could be downloaded? I am new to RequireJS and jQuery and that would be most helpful. I have what I would guess is 95% of the code working but just can’t get over that last 5%.
    Thanks.

  13. Shawn says:

    Did you know the link in the “This guide should help you get started.” line leads to a blank page?

  14. Bella says:

    Best tutorial I’ve found so far. Thank you.
    I did get lost on one of the steps though.
    In the last step in section ‘Add the first view’ where you say ‘Finally, we can bind the view HTML…’. In which file are we putting that code?

  15. Nabu says:

    Thanks very much for that post. Helped me a lot to get my kendo app structured.
    Have tried to use cordova in your app? I ran into problems when trying to use it in a requirejs structured app.

    Thanks in adnvance and keep it up!

  16. Ed says:

    Thanks for this. If I have some function (not specific to any one view, in this case whenever my app menu opens), then where should I put this?

    If the function is just put in the global scope, I can do this: `data-show=”menuShow”`, but obviously that goes against the whole point of structuring it in this way.

    • rally25rs says:

      Typically if I have a function that all the ViewModels share, I will either put it in a “utils” file that all the viewmodels can take in and use, or make a base type for the ViewModels to inherit from that contains the function. It sort of depends on what the function does and where i feel it should fall in the layout of the files. It sounds like “menuShow” is probably view logic, so making a base type for the ViewModels is probably what I would do. If you aren’t sure how to do inheritance in JavaScript (prototypal inheritance is weird) then just Google it. There are some pretty good articles explaining how to do it. Alternatively, Kendo also has its own inheritance model that you can use:

      • Ed says:

        Thanks for the response: makes sense, I just need to wrap my head properly around prototypical inheritance.

        Apologies if this is really obvious then, but assuming I have everything set up as you do, how can I access the built in functions of Kendo, like programmatic navigation between views? i.e., in home.js I want to call app.navigate(‘#my-view’), but I get Object # has no method ‘navigate’.

      • rally25rs says:

        In my app.js file, I have a variable named _kendoApplication but I didn’t expose it anywhere. You could expose that in the object returned in app.js, then reference it from global scope as app.kendoApplication.navigate(). There is actually a way I like better that avoids using global scope, but I am typing this on an iPad so typing the code would not be fun right now 🙂

      • Ed says:

        Cheers man – if you get time, could you give me an example of how I should best expose that in the returned object? I’m trying to follow best practices with this as I learn as much as possible. It is much appreciated 🙂

  17. […] Kendo Mobile application with RequireJS that shows this off. Also my teammate, Jeff Valore, has a nice explanation of doing this as well. To make it easier, I updated our starter template at jsFiddle to show how we can get Kendo UI […]

  18. Hi!, it posible to lazy load the view and templates on demand?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

CodingWithSpike is Jeff Valore. A professional software engineer, focused on JavaScript, Web Development, C# and the Microsoft stack. Jeff is currently a Software Engineer at Virtual Hold Technologies.


I am also a Pluralsight author. Check out my courses!

%d bloggers like this: