Introduction
In this tutorial we will be implementing RequireJs Asynchronous Module definition pattern on our BackboneJs application.
Background
What is AMD n why AMD?
Well a video speaks more than words. Here are the links which is definitely going to help you understand AMD and RequireJs.
Why AMD? - Part 1
Why AMD? - Part 2
Using the code
Since our existing code BackboneJs application has the backbone model, view and collections on one script file, to induce AMD we would need to modularize them. So the obvious answer would be to take the views, models and collections to different js files. For better understanding lets folderise the javascript files as follows.
1, Scripts/Model/GenreModel.js
2, Scripts/Collection/GenreCollection.js
3, Scripts/View/StoreManager/GenreApplicationView.js
4, Scripts/View/StoreManager/GenreView.js
5, Scripts/View/StoreManager/GenreEditView.js
6, Scripts/View/StoreManager/GenreDeleteView.js
Well if you have downloaded the source code, you would be thinking what are these following scripts for
1, Scripts/RequireUtil/RequireJquery.js
2, Scripts/RequireUtil/RequireJqueryUI.js
3, Scripts/RequireUtil/RequireUnderscore.js
4, Scripts/RequireUtil/RequireBackbone.js
The problem here is latest version of jQuery, jQueryUI, Underscore and Backbone are not modularised anymore, in simple sense they do not support RequireJs or AMD. What we are doing is a simple workaround, convert jQuery et all into a named module and pass it as a dependency to our other modules. If you want to skip this step google for Modularised Jquery, Underscore and Backbone.js. They perfectly work fine.
define(['order!../jquery'], function () {
return $;
});
define(['order!../jquery-ui'], function () {
return $;
});
define(['order!../underscore'], function () {
return _;
});
define(['order!../backbone'], function () {
_.noConflict();
$.noConflict();
return Backbone.noConflict();
});
require.config({
paths: {
jQuery: '../RequireUtil/RequireJquery',
jQueryUI: '../RequireUtil/RequireJqueryUI',
order: "../RequireUtil/order",
Underscore: '../RequireUtil/RequireUnderscore',
Backbone: '../RequireUtil/RequireBackbone',
GenreModel: '../Model/GenreModel',
GenreCollection: '../Collection/GenreCollection',
GenreApplicationView: '../View/StoreManager/GenreApplicationView',
GenreView: '../View/StoreManager/GenreView',
GenreDeleteView: '../View/StoreManager/GenreDeleteView',
GenreEditView: '../View/StoreManager/GenreEditView'
}
});
require(['order!jQuery', 'order!jQueryUI', 'GenreCollection', 'GenreApplicationView'], function ($, jqueryui, GenreCollection, AppView) {
$(function () {
$("#GenreEditDialog").dialog({
autoOpen: false,
show: "blind",
hide: "explode",
modal: true,
height: 250,
width: 600,
open: function (event, ui) {
$('.ui-dialog-titlebar', ui.dialog || ui).hide();
}
});
var genres = new GenreCollection();
var view = new AppView({ collection: genres });
genres.fetch({ success: function () {
view.render();
}
});
});
});
define(['order!jQuery', 'order!Underscore', 'order!Backbone', 'GenreView', 'GenreEditView', 'GenreModel', 'GenreCollection'],
function ($, _, Backbone, GenreView, EditView, Genre, GenreCollection) {
var AppView = Backbone.View.extend({
initialize: function () {
this.collection.bind('add', this.AppendGenre, this);
},
el: '#Genre_Container',
events: {
"click #btnCreateNew": "AddNewGenre"
},
AddNewGenre: function () {
var newGenre = new Genre();
var editView = new EditView({ model: newGenre, collection: this.collection });
editView.render();
},
AppendGenre: function (genre) {
var genreView = new GenreView({ model: genre });
$(this.el).find('#Genre_List').append(genreView.render().el);
},
render: function () {
if (this.collection.length > 0) {
this.collection.each(this.AppendGenre, this);
}
$("input:button", "#Genre_List").button();
}
});
return AppView;
});
define(['order!jQuery', 'order!Underscore', 'order!Backbone', 'order!GenreEditView', 'order!GenreDeleteView'], function ($, _, Backbone, EditView, DeleteView) {
var GenreView = Backbone.View.extend({
tagName: "tr",
initialize: function () {
this.template = _.template($('#Genre-Template').html());
this.model.bind('change', this.render, this);
this.model.bind('remove', this.unrender, this);
},
render: function () {
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
unrender: function () {
$(this.el).remove();
return this;
},
events: {
"click .Edit": 'EditGenre',
"click .Delete": 'DeleteGenre'
},
EditGenre: function () {
var editView = new EditView({ model: this.model });
editView.render();
},
DeleteGenre: function () {
var deleteView = new DeleteView({ model: this.model });
deleteView.render();
}
});
return GenreView;
});
define(['order!jQuery', 'order!jQueryUI', 'order!Underscore', 'order!Backbone'], function ($, jqueryui, _, Backbone) {
var GenreEditView = Backbone.View.extend({
tagName: "table",
container: "#Genre_Edit",
initialize: function () {
this.template = _.template($('#Genre-Edit-Template').html())
},
events: {
"change .gName": 'NameChange',
"change .gDesc": 'DescChange',
"click .Update": 'UpdateGenre',
"click .Cancel": 'CancelGenre'
},
NameChange: function () {
this.model.set({ Name: $(".gName").val() });
},
DescChange: function () {
this.model.set({ Description: $(".gDesc").val() });
},
render: function () {
$(this.container).append($(this.el).html(this.template(this.model.toJSON())));
$("input:button", $(this.el)).button();
$("#GenreEditDialog").dialog("open");
var title = 'Create Genre';
if (this.collection == null)
title = 'Edit Genre - ' + this.model.get('Name');
$(this.container).find('legend').html(title);
$('.ui-dialog-titlebar').hide();
$('#valSum').hide("slow");
return this;
},
unrender: function () {
$(this.el).remove();
return this;
},
UpdateGenre: function () {
if (this.model.validateModel()) {
var self = this;
if (this.collection != null) {
this.collection.add(this.model);
}
this.model.save(this.model, { success: function () {
self.unrender();
$("#GenreEditDialog").dialog("close");
var genreId = self.model.get('GenreId');
$("input:button", '#Genre_List').button();
}
});
}
$("input:button", '#Genre_List').button();
},
CancelGenre: function () {
this.model.fetch({ success: function () {
$("input:button", '#Genre_List').button();
}
});
this.unrender();
$("#GenreEditDialog").dialog("close");
}
});
return GenreEditView;
});
define(['order!jQuery', 'order!jQueryUI', 'order!Underscore', 'order!Backbone'], function ($, jqueryui, _, Backbone) {
var GenreDeleteView = Backbone.View.extend({
container: "#Genre_Edit",
initialize: function () {
this.template = _.template($('#Genre-Delete-Template').html());
},
events: {
"click .Update": 'DeleteGenre',
"click .Cancel": 'CancelGenre'
},
render: function () {
$(this.container).append($(this.el).html(this.template(this.model.toJSON())));
$("input:button", $(this.el)).button();
$("#GenreEditDialog").dialog("open");
$('.ui-dialog-titlebar').hide();
var title = 'Delete Genre - ' + this.model.get('Name');
$(this.container).find('legend').html(title);
return this;
},
unrender: function () {
$(this.el).remove();
return this;
},
CancelGenre: function () {
this.unrender();
$("#GenreEditDialog").dialog("close");
},
DeleteGenre: function () {
var self = this;
this.model.destroy({ success: function () {
self.unrender();
$("#GenreEditDialog").dialog("close");
}
});
}
});
return GenreDeleteView;
});
define(['jQuery', 'Underscore', 'Backbone'], function ($, _, Backbone) {
var GenreModel = Backbone.Model.extend({
url: function () {
var base = '/StoreManager/GenreList/';
if (this.isNew())
return base;
return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + this.id;
},
initialize: function () {
this.bind("change:GenreId", function () {
var genreId = this.get('GenreId');
this.set({ id: genreId });
});
},
defaults: {
GenreId: 0,
Name: '',
Description: ''
},
validateModel: function () {
var validate = true;
var divHtml = "<div class='ui-state-error ui-corner-all' style='display: block; padding-top: 0px; padding-bottom: 0px; width: 500px; padding-left: 0.7em; padding-right: 0.7em;'>";
divHtml += "<p><span class='ui-icon ui-icon-alert' style='float: left; margin-right: .3em;'></span><strong>Please correct these Errors</strong> </p><div class='validation-summary-errors' data-valmsg-summary='true'>";
divHtml += "</div><p></p></div>";
var ulHtml = "<ul></ul>";
var div = $(divHtml);
var ul = $(ulHtml);
if (this.get('Name') == "") {
ul.append("<li>Name is Mandatory</li>");
validate = false;
}
if (this.get('Description') == "") {
ul.append("<li>Description is Mandatory</li>");
validate = false;
}
div.find(".validation-summary-errors").append(ul);
if (!validate) {
$('#valSum').show("slow");
$('#valSum').html(div);
}
else {
$('#valSum').hide("slow");
$('#valSum').html("");
}
return validate;
}
});
return GenreModel;
});
define(['jQuery', 'Underscore', 'Backbone', 'GenreModel'], function ($, _, Backbone, Genre) {
var GenreCollection = Backbone.Collection.extend({
model: Genre,
url: '/StoreManager/GenreList/'
});
return GenreCollection;
});
Remember the above code is not in a single js file, they are in different js files. Now we need to refer Require.js in our Genre.shtml and make it load the GenreApp.js (note the data-main attribute on the script tag) with its dependencies.
@{
ViewBag.Title = "Genres";
}
<script data-main="@Url.Content("~/Scripts/App/GenreApp")" src="@Url.Content("~/Scripts/require.js")" type="text/javascript"></script>
<div class="styler">
<fieldset class="ui-widget">
<legend class="ui-state-legend-default ui-corner-top ui-corner-bottom">Genre List -
Using Backbone</legend>
<div id="Genre_Container">
<input type="button" value="Create New" class="Add" id="btnCreateNew" />
<table id="Genre_List">
<tr>
<th>
Name
</th>
<th>
Description
</th>
<th>
</th>
</tr>
</table>
<div id="GenreEditDialog" style="height: 350">
<div class="styler">
<fieldset class="ui-widget" id="Genre_Edit" style="height: 350">
<legend class="ui-state-legend-default ui-corner-top ui-corner-bottom"></legend>
<br />
<div id="valSum" >
</div>
</fieldset>
</div>
</div>
</div>
<script id='Genre-Template' type='text/template'>
<td><%=Name%></td> <td><%=Description%></td>
<td><input type="button" value= "Edit" class="Edit" /> | <input type="button" value= "Delete" class="Delete" /> </td>
</script>
<script id='Genre-Edit-Template' type='text/template'>
<tr>
<td>
<div class="editor-label">
Name</div>
<div class="editor-field">
<input type="text" class="gName" value='<%=Name%>' />
</div>
</td>
<td>
<div class="editor-label">
Description</div>
<div class="editor-field">
<input type="text" class="gDesc" value='<%=Description%>' />
</div>
</td>
</tr>
<tr>
<td colspan="2">
<input type="button" value="Save" class="Update" /> | <input type="button" id="btnCancel" value="Cancel" class="Cancel" />
</td>
</tr>
</script>
<script id='Genre-Delete-Template' type='text/template'>
<div class="ui-state-highlight ui-corner-all" style="margin-top: 20px; padding: 0 .7em;
width: 500px">
<p>
<span class="ui-icon ui-icon-info" style="float: left; margin-right: .3em;"></span>
<strong>Confirm</strong><br />
Are you sure you want to delete this Album - <%=Name%> ?</p>
</div><br/>
<table>
<tr>
<td>
<div class="editor-label">
Name</div>
<div class="editor-field">
<%=Name%>
</div>
</td>
<td>
<div class="editor-label">
Description</div>
<div class="editor-field">
<%=Description%>
</div>
</td>
</tr>
<tr>
<td colspan="2">
<input type="button" value="Delete" class="Update" /> | <input type="button" id="btnCancel" value="Cancel" class="Cancel" />
</td>
</tr>
</table>
</script>
</fieldset>
</div>
Points of Interest
To appreciate the importance and advantages of RequireJs one has to use it profusely in his code. Also the advantages of RequireJs becomes really apparent when your project is growing, managing dependencies can be a headache. Also RequireJs will help you test your javascript more effectively. We will see that in later tutorials like implementing jasmine or qunit. Until then give RequireJs a shot. Also here are some links which I hope will interest you.
http://requirejs.org/docs/jquery.html
http://developer.teradata.com/blog/jasonstrimpel/2011/12/part-1-backbone-js-require-js
http://www.bennadel.com/blog/2287-Using-jQuery-As-A-Named-Module-In-RequireJS.htm
http://www.davidgranado.com/2012/01/getting-jquery-1-7-andor-backbone-to-play-nicely-with-requirejs/
-- Anon