Click here to Skip to main content
14,458,335 members

Implementation of Single Page Application using Client Side Framework: Durandal

Rate this:
4.80 (5 votes)
Please Sign up or sign in to vote.
4.80 (5 votes)
7 Jan 2016CPOL
This is a single page application. Technologies and version used for these articles are MVC 5.0 and Durandal 2.1( client side framework)

Introduction

SPA as the name suggests, is an application where all associated pages of any application are laid down over a single web page. In another words, the entire necessary resources such as HTML page, stylesheet, JavaScript files, of any application are loaded in the browser after the first request and rendered when requested.

There are many frameworks available in the market to build SPA but here in this article, we are going to cover the Durandal SPA framework.

Durandal

In simple words, it is a combination of different standard JavaScript libraries such as Knockout, RequireJS, jQuery, which helps us to build less time taking, responsive UI for better user experience.

Background

When I was implementing client side framework, I faced a number of problems with the configuration of view with view models, knockout.js, require.js, CSS, and many more things. Then I found Durandal, a framework which provides all the necessary configurations which are required to start a new project without putting in much effort with respect to configuration.

Implementations

In general, Single Page Application, client side data model connects to the service layer for sending and receiving JSON data by using Ajax call.

In this demo, we are not going to touch any server side component; rather we would be completely focused on client side component.

Summary of Implementation

Going to make a simple menu controls with sub menu options; which will load view dynamically on each submenu click.

Going to Learn

  • Import Durandal framework to Web app
  • Create view and view model
  • Bind view to the respective view model
  • Bind view and view model with dynamic data
  • Load views dynamically when necessary
  • Interact different to view with single page

Setup the Solution

Step 1

Create a solution named Demo.SPA.Solution.

Step 2

Add a new project ASP.NET Web Application and select empty- MVC template.

Image 1

The solution will appear as below:

Image 2

Step 3

Add Durandal starter kit to web App:

Image 3

It will add some additional folders such as App, App\Views, App\ViewModels and dependent files, durandalconfid.cs, durandalBundle.config, DurandalController.

My solution will appear as:

Image 4

Image 5

Note: shell.js under App\ViewModels will be used to define routing.

Step 4

Build and run the solution.

Since durandalController is the default controller, browse the url as:

http://localhost:XXXX/durandal

The webpage will appear as:

Image 6

Welcome and flicker tabs comes with default Durandal framework.

To remove this table, you need to comment the below lines in shell.js.

{ route: '', title:'Welcome', moduleId: 'viewmodels/welcome', nav: true },

{ route: 'flickr', moduleId: 'viewmodels/flickr', nav: true }

Using the Code

Going forward to our sample application.

Step 5

Add view (home.html, menu.html, productlist.html, productdetails.html, addnewproduct.html) inside App/views folder.

Step 6

Add viewmodels with same name as HTML (e.g.: home.js, menu.js..) inside app/viewmodels folder.

I have created a folder called Repository, which will take care of the data part, since we are not including any server side implementation in this demo.

Inside Repository folder, I have created two JS files, name BookRepository.js and MenuRepository.js.

Inside the script folder, I have created globaldata.js file, where I will declare global scope variables.

And bundle it to DurandalBundle.config.cs file by using the following line:

Include("~/Scripts/globaldata.js")

Hence, my solution is appearing now as:

Image 7

Using the Code

For getting the data:

In BookRepository.js and MenuRepository.js, I have defined some variables and methods which we will use further down the application.

BookRepository.js

define(function (require) {
    return {
        _books: [
                    { id: 0, title: 'The Low Land', writter: 'Jumpa Lahiri', 
                    price: '12', description: 'Test low land description' },
                    { id: 1, title: 'The Story of time being', writter: 'Ruth Ozeki', 
                    price: '13', description: 'Test Story of time being description' },
                    { id: 2, title: 'Alchemist', writter: 'Paulo', 
                    price: '14', description: 'Test Story of time being description' },
                    { id: 3, title: 'The Narrow Road to the Deep North', 
                    writter: 'Richard Flanagan', price: '10', 
                    description: 'Test Narrow Road to the Deep North description' },
                    { id: 4, title: 'Luminaries', writter: 'Eleanor Catton', 
                    price: '11', description: 'Test Luminaries description' },
                    { id: 5, title: 'Sense of an Ending', writter: 'Julian Barnes', 
                    price: '12', description: 'Test Sense of an Ending description' } ],

        listBooks: function () { return this._books; },
        getBooksById: function (id) {
            for (var i = 0; i < this._books.length; i++) {
                if (this._books[i].id == id) {
                    return this._books[i];
                    break;
                }
            }
        }
    };
});

MenuRepository.js

define(function () {
    return {
        _menusItems: {
            menu: [
                    { name: 'Home', link: '0', sub: null },
                    {
                        name: 'Products', link: '1', 
                                          sub: [{ name: 'List of Products', sub: null },
                                          { name: 'Register New Product', sub: null },
                                          { name: 'Enquiry Product', sub: null }]
                    },
                    {
                        name: 'About US', link: '2', sub: [{ name: '', sub: null },
                                                         { name: '', sub: null }]
                    },
                    {
                        name: 'Contact', link: '3', 
                                          sub: [{ name: 'Corporate Office', sub: null },
                                          { name: 'Home Office', sub: null }]
                    }
            ]
        },
        menuItems: function () { return this._menusItems; }
    }
})

Step 7

Now we are going to work on our menu view model.

Since we are taking menu data from MenuRepository.js, so for fetching menu data in menu view model, first we need to load MenuRepository, which we will do by declaring it inside define box such as:

define(['Repository/MenuRepository'], function (mRepository)

By doing this, we can access the variables and functions of MenuRepository.js to this page.

Menu data we will store in it observable array and bind it to the view.

Hence, respective data will appear on the web.

Menu.js

define(['Repository/MenuRepository'], function (mRepository) {
    var menuConstructor = function () {
        var self = this;
        g_menuItemObservable = ko.observable("");
        self.menuItems = ko.observableArray([]);
        self._menus = [];
        self.getMenu = function () {
            self._menus = mRepository.menuItems();
        }
        self.attached = function (view) {
            self.getMenu();
            self.menuItems(self._menus.menu);
        }   
    }
    return new menuConstructor();
})

Menu.html

<div class="menu-style" style=" height:80%; width:70%; margin-left:10%; margin-top:2%;">
    <nav class="navbar navbar-inverse">
        <div class="container-fluid">
            <div>
                <ul class="nav navbar-nav" data-bind="foreach:menuItems">
                    <li class="dropdown">
                        <a class="dropdown-toggle" 

                        data-toggle="dropdown" data-bind="text:name"></a>
                        <ul class="dropdown-menu" data-bind="foreach: sub">
                            <li data-bind="text:name"></li>
                        </ul></li>
                </ul>
            </div></div>
    </nav>
</div>

View model follows some life cycle. Here, I am using composition life cycle (activate\attach according to my requirement).

For life cycle, please check durandal docs.link (http://durandaljs.com/documentation/Hooking-Lifecycle-Callbacks.html).

In the same way, we are going to create productlist view and view model.

Productlist.js

define(['Repository/BookRepository'], function (repository) {
   var productConstructorViewModel;
   var productConstructor = function () {
       var self = this;
       productDetailObservable = ko.observable("");
        self.products = ko.observableArray([]);
        self._products = []
        self.getProducts = function () {
            self._products = repository.listBooks();
            self.products(self._products);
        };
        self.activate = function (data) {
            self.getProducts();
        }
        self.removeProfile = function (list) {
            if (confirm("Are you sure you want to delete this profile?")) {
                self.products.remove(list);
            }
        }
        self.editProfile = function (list) {
            // Implement your logic
        }
        self.getList = function (data, event) {
            g_productID = data.id;
            $('#divProductdetails').css("display", "block");
        }
     }
    return new productConstructor();
})

Productlist.html

<div><div>     
        <table class="table table-striped table-bordered table-condensed" >
            <tr><th>Title</th><th>Author</th></tr>
            <tbody data-bind="foreach:products">
                <tr><td><a data-bind="text:title"></a></td>
                    <td data-bind="text:writter"></td>
                    <td><button class="btn btn-mini btn-danger" 

                    data-bind="click: function(data, event) {$root.getList(data, event); 
                    return true;}">Details</button></td>
                    <td><button class="btn btn-mini btn-danger" 

                    data-bind="click: $parent.removeProfile">Remove</button></td>
                    <td><button class="btn btn-mini btn-danger" 

                    data-bind="click: $parent.editProfile">Edit</button></td></tr>
            </tbody>
        </table>
    </div>

Now, I am going to bind menu with home and productlist with menu.

Here, we are using an important knockout binding concept called compose.

Home.js

define([], function () {
    var homeconstructorViewModel;
    homeconstructorViewModel = function () {
        self = this;
        self.menuItemObservable = ko.observable("");
        self.categories = ["Product List"];
        self.productListObservable = ko.observable("");
        self.loadProductList = function (data, event) {
            self.productListObservable({ view: 'views/productlist.html', 
                                         model: 'viewmodels/productlist' });
            $('#divProducts').css("display", "block")
        }
        $(document).on("click", ".dropdown-menu li", function (e) {
            var ctrl = $(this).text();
            if (ctrl == "List of Products") {
                self.menuItemObservable({ view: 'views/productlist.html', 
                                          model: 'viewmodels/productlist' })
            }
            if (ctrl == "Register New Product") {
                self.menuItemObservable({ view: 'views/addnewproduct.html', 
                                          model: 'viewmodels/addnewproduct' })
            }
        })
    }
    return homeconstructorViewModel;
});

Home.html

<div>
    <div style="height:80%; width:70%"><div>
            <div data-bind="compose:
                         { model:'viewmodels/menu', view:'views/menu.html'}">
            </div> </div>    </div>
    <div>
        <div id="divMenuItemBody" style="margin-left:10%; 
                                  margin-top:2%; width:70%; height:40%">
            <div data-bind="compose: menuItemObservable" style="height:100%;"></div>
        </div> </div>
</div>

Here, in HTML, you can see that I have used two data-bind, in the first case compose is using static defined view and view model, however in the second one, it's observable which loads view\view model dynamically whenever observable variable value changes (check in home.js).

I have also designed the rest of the pages by using the same kind of logic, you can check by downloading the sample.

Step 8

Now come to the routing section.

I want to browse the home page on the page load so will define the routing as below in shell.js file.

{ route: 'home', title: 'Demo', moduleId: 'viewmodels/home', nav: true }

Points of Interest

In this article, we have discussed about how to use durandal in web app.

I am writing this article by keeping beginners in my mind.

Hope this article will be fruitful for you.

Source Code

I have uploaded this sample project with some more features along with this article. You can just download this sample code and make your understanding better on these concepts and implementation further.

Use the following URL to run the application.

http://localhost:XXXX/durandal

XXXX is the port number of your local system on which the application is running.

History

  • 7th January, 2016: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Snx.sahay
Software Developer (Senior)
India India
I am a programmer by my passion and senior developer by profession. It always makes me feel better to play with programming codes and new technologies. I have worked on a number of Microsoft technologies such as Biztalk Server, SSIS,SSRS,ASP.Net,WCF. Upcoming Microsoft technologies always encourage me learn and play more with them.

Comments and Discussions

 
PraiseNice post Pin
Member 1221085911-Jan-16 2:29
MemberMember 1221085911-Jan-16 2:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Article
Posted 7 Jan 2016

Stats

8.9K views
142 downloads
3 bookmarked