Click here to Skip to main content
14,031,218 members
Click here to Skip to main content
Add your own
alternative version

Stats

7.5K views
132 downloads
3 bookmarked
Posted 7 Jan 2016
Licenced CPOL

Implementation of Single page application using client side framework:- Durandal

, 7 Jan 2016
Rate this:
Please Sign up or sign in to vote.
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 name suggests, it’s an application where all associated pages of any application are lay down over a single web page. In another words the entire necessary resources such as html page, style sheet, java script files, of any application is loaded in the browser after first request and rendered when requested.

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

Durandal:-

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

Background

when I was implementaing clien side framework then faced a number of problems with the configuration of view with view models, knockout.js, require.js, css and many more thing. Then found durandal, a framework which provides all nacessary configurations which is require to start a new project without putting much effort with respect to configuration.

Implementations

In generally Single page application, client side data model connect 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 focus 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.

Step1. Create a solution name Demo.SPA.Solution.

Step2. Add new project ASP.NET Web Application and select empty- mvc template 

Solution will appear as below:

Step3. Add Durandal starter kit to web App

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

My solution will appear as 

 

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

Step4. Build and run the solution

Since durandalController is default controller so browse the url as

http://localhost:XXXX/durandal

webpage will appear as

Welcome and flicker tabs comes with default durandal framework

To remove this tabe 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.

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

Step6. Add viewmodels with same name as html (eg: home.js, menu.js..) inside app/viewmodels folder.

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

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

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

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

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

Hence my solution is appearing now as

Using the code:

For getting the data

In BookRepository.js and MenuRepository.js  I have defined some variable and method 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; }
    }
})

Step7. 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 declare 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 obserbable 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 follow some life cycle. Here I am using composition life cycle (activate\attach according to the my requirement)

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

The same way  we are going to create productlsit 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 I have used two data-bind, in the first case compose is using static defined view and view model , however in the second one its observable which load view\view model dynamically whenever observable variable  value changes (check in home.js).

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

 

Step8. Now come to the routing section

I want to browse 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.

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

Hope this article will be fruitful for you.

Source code

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

use following url to run the applcation

http://localhost:XXXX/durandal

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

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 1:29
memberMember 1221085911-Jan-16 1: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.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web06 | 2.8.190419.4 | Last Updated 7 Jan 2016
Article Copyright 2016 by Snx.sahay
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid