We’ve got Ignite UI Grids shining in a Durandal SPA for you

Alexander Todorov / Thursday, August 1, 2013

   Long time ago, the web was all about static HTML content, and a bit of inline styles. JavaScript was primarily used for alert boxes and falling snowflake scripts (that websites used to load around Christmas). Then came AJAX, and things started to look more serious and promising… Then JavaScript and HTML started to evolve quite quickly, we’ve got into the HTML5, jQuery and Node.JS era, and … Single Page Apps started to become more and more common.

   Nowadays, it’s hard to think that a Hi-Fi web app would go without things like jQuery, Bootstrap, Knockout, Backbone, Ember, Angular, etc. There are multiple JavaScript Single Page Application frameworks out there, giving you benefits like MVC/MVVM support, routing and two-way data-binding. But what I like about Durandal, in particular, is the great ease of use, rich functionality, and the fact that it doesn’t try to implement every single SPA concept from scratch. It relies on jQuery, KnockoutJS for the data-binding, requireJS for all of the modules management, and Bootstrap for lots of the styling.

   Considering that our Ignite UI grids are already also fully integrated with Knockout and we ship Knockout extensions as part of the toolset – yes, including complete two-way data-binding support! – Durandal opens up a lot of possibilities for the Ignite user. There is one problem, though, stopping you to use most of the grid features in a Durandal SPA at the moment. This is due to the fact that Durandal applies the knockout bindings to the view before the view contents are attached to the DOM, and the IgniteUI grids (and most grid features) make frequent use the ID jQuery selector a lot – that’s primarily because the ID selector provides a reliable and fast way of retrieving matched elements. But since the id selector (http://api.jquery.com/id-selector/) requires that the DOM is attached to the document, it doesn’t really work if the elements you’re trying to retrieve are not attached to it.

   We’ve worked very hard during the past couple of days, though, and this is already a problem of the past! I can happily say that those integration issues are already gone. For the rest of this blog post I will outline how you can easily and naturally add an Ignite grid to any of your Durandal views – no hacks or glue code required. The attached sample application is self-contained, so you just need to unzip it and run index.html (on Firefox, otherwise you’ll need to put the contents on a web server and load from there). The fixes we’ve done will be officially available in the next 13.1 service release which is going to be out in about a week.  By the way, if you aren’t very familiar with the Durandal basics, this is a great general starting point:

http://durandaljs.com/pages/get-started/

   So, basically, the first thing we need to do after we download Durandal, is add a view in the App/views folder. We are going to use the default project template which comes with Durandal once you download and uznip the Durandal.zip. So you can add an iggrid.html file, with the following contents:

<section>
	<table id="grid1" data-bind="igGrid: {dataSource: data, caption: 'Food from Heaven', primaryKey: 'id',height: 300, width: 800, features: [{name: 'Sorting'}, {name: 'Updating'}]}"></table>
</section>

   Note that we are not defining any javascript initialization code. ID is also not required. Durandal works in collaboration with knockout in order to apply the view model bindings which will trigger the initialization of our Ignite grid (we have custom knockout extensions implemented for both grids as well as the data source component).

   The code above also enables a couple of features, and specifies the dataSource, which is the “data” property from our view model. Now, we need to define a view model, which should be done in the App/viewmodels subfolder. You can create an iggrid.js file, with the following contents:

define(['durandal/http', 'durandal/app'], function (http, app) {
    
    return {
        data: ko.observableArray([]),
		buttonText: "Update!",
		labelText: "Hit the button to update the model 'desc' value of the first record with the value of 'some custom description & watch out the grid!",
		buttonClick: function () {
			// update data (the model for the IGnite Grid)
			this.data()[0].desc("some custom description");
		},
        activate: function () {
            //the router's activator calls this function and waits for it to complete before proceding
            if (this.data().length > 0) {
                return;
            }
			function Item(id, desc, productName, createdDate) {
				return {
					 id: ko.observable(id),
					 desc: ko.observable(desc),
					 productName: ko.observable(productName),
					 createdDate: ko.observable(createdDate)
				};
			};
			var myData = [
				new Item(1, "Best Italian bread", "Bread", new Date(2013, 10, 5, 1)),
				new Item(2, "Quaker oats for your taste", "Old Fashioned Quaker Oats", new Date(2012, 10, 5, 2)),
				new Item(3, "peeled and processed", "Canned tomatoes", new Date(2010, 10, 5, 2)),
				new Item(4, "The best tuna from Norway", "Canned Tuna", new Date(1999, 10, 5, 5)),
				new Item(5, "Greek olive oil", "Olive oil", new Date(2013, 10, 5, 5)),
				new Item(6, "heated beverage", "Hot chocolate", new Date(2011, 10, 5, 8)),
				new Item(7, "black beans from Guatemala", "Beans", new Date(2005, 10, 5, 6)),
				new Item(8, "Green Equador bananas", "Bananas", new Date(2004, 10, 5, 5)),
				new Item(9, "raw avocado", "Avocados", new Date(2004, 10, 5, 5)),
				new Item(10, "brown sugar from the Netherlands", "Brown sugar", new Date(2013, 10, 5, 4)),
				new Item(11, "Peru 100% cocoa", "Cocoa", new Date(2013, 10, 5, 4)),
				new Item(12, "Bulgarian natural honey", "Honey", new Date(2012, 10, 5, 9)),
				new Item(13, "Brought to you by Heinz", "Apple cider vinegar", new Date(1994, 10, 5, 7)),
				new Item(14, "grass-fed", "Boneless chicken ***", new Date(1998, 10, 2, 7)),
				new Item(15, "Salmon from Scotland", "Salmon", new Date(2013, 2, 5, 8)),
				new Item(16, "Uruguay's best ribeye steaks", "Ribeye steaks", new Date(2013, 3, 5, 8)),
				new Item(17, "turkey lean meat", "Ground turkey", new Date(2013, 4, 5, 11)),
				new Item(18, "fresh bio lettuce", "Lettuce", new Date(2013, 7, 5, 11)),
				new Item(19, "Pami peanut butter", "Peanut Butter", new Date(2014, 7, 3, 2)),
				new Item(20, "Fresh Asparagus", "Asparagus", new Date(2013, 10, 2, 1)),
				new Item(21, "raw almonds, non salted", "Almonds", new Date(2009, 8, 5, 1)),
				new Item(22, "White feta cheese", "White cheese", new Date(2013, 10, 5, 3)),
				new Item(23, "hot peppers", "Black peppers", new Date(2013, 10, 5, 6)),
				new Item(24, "baked and salted pumpkin seeds", "Pumpkin seeds", new Date(2013, 10, 5, 5))
			];
            return this.data(myData);
        },
        canDeactivate: function () {
            //the router's activator calls this function to see if it can leave the screen
            return app.showMessage('Are you sure you want to leave this page?', 'Navigate', ['Yes', 'No']);
        }
    };
});

   You can see from the above code that we are defining a data property and initialize it to an empty observable knockout array. Then in the activate function, which gets called as part of Durandal’s lifecycle, we are adding some data to the knockout observable array and return it to the view. By the way, our data objects are also comprised of observable properties.

   Now, in order to hook the Ignite grid view that we’ve just created to durandal’s startup project, we need to configure the app routes and modify the contents of the App/main.js file, which serves as a starting point for the structure of our app:

//configure routing
router.useConvention();
router.mapNav('welcome');
router.mapNav('flickr');
router.mapNav('iggrid');

Last but not least, we need to add the Ignite UI required references (including jQuery UI and the knockout extensions to index.html (Durandal/index.html):

<link rel="stylesheet" href="http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css" />
<link rel="stylesheet" href="Content/infragistics/css/themes/metro/infragistics.theme.css" />
<link rel="stylesheet" href="Content/infragistics/css/structure/infragistics.css" />
<script src="Scripts/knockout.mapping-latest.debug.js"></script>
<script src="Scripts/infragistics.core.js"></script>
<script src="Scripts/infragistics.lob.js"></script>
<script src="Scripts/extensions/infragistics.datasource.knockoutjs.js"></script>
<script src="Scripts/extensions/infragistics.ui.grid.knockout-extensions.js"></script>

   Now if you open index.html and navigate to the iggrid tab, you’ll see a nice looking grid with the Metro Theme applied.

   Note that I’ve modified the grid additionally by adding a button and some text and I’ve bound them to additional view properties – so when you hit the “Update!” button, you will experience the two-way data-binding functionality of the Ignite grid – by updating the model, the grid’s cell which is bound to that model property will be instantly updated in real-time, without re-binding or re-rendering any other part of the grid.

   And that’s really everything you need to do to get the full power of Ignite in the context of a Durandal SPA! Stay tuned for new related posts, and please remember that your feedback is really valuable to us - any suggestion, question or general comment you may have is greatly appreciated!

Ignite_Durandal.zip