在 BackboneJs 上实现 RequireJs





5.00/5 (4投票s)
使用 RequireJS 在 BackboneJs 上更新 MVC 音乐商店应用。
引言
在本教程中,我们将会在我们的 BackboneJs应用 中实现 RequireJs 异步模块定义模式。
背景
什么是AMD以及为什么使用AMD?
视频比文字更能说明问题。以下链接将肯定能帮助你理解AMD和RequireJs。
使用代码
由于我们现有的代码 BackboneJs应用 将backbone模型、视图和集合放在一个脚本文件中,为了引入AMD,我们需要对其进行模块化。因此,显而易见的答案是将视图、模型和集合移动到不同的js文件中。为了更好地理解,让我们将javascript文件组织成如下文件夹:
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
如果你已经下载了源代码,你可能会想知道以下脚本是做什么的
1, Scripts/RequireUtil/RequireJquery.js
2, Scripts/RequireUtil/RequireJqueryUI.js
3, Scripts/RequireUtil/RequireUnderscore.js
4, Scripts/RequireUtil/RequireBackbone.js
这里的问题是,最新版本的jQuery、jQueryUI、Underscore和Backbone不再模块化,简单来说,它们不支持RequireJs或AMD。我们所做的是一个简单的变通方法,将jQuery等转换为命名模块,并将其作为依赖项传递给我们的其他模块。如果你想跳过此步骤,可以在Google上搜索模块化的Jquery、Underscore和Backbone.js。它们可以完美地工作。
//RequireJquery.js
define(['order!../jquery'], function () {
return $;
});
//RequireJqueryUI
define(['order!../jquery-ui'], function () {
return $;
});
//RequireUnderscore
define(['order!../underscore'], function () {
return _;
});
//RequireBackbone
define(['order!../backbone'], function () {
_.noConflict();
$.noConflict();
return Backbone.noConflict();
});
//GenreApp - This is the main file we will be loading on the Genre.shtml
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();
}
});
});
});
//GenreApplicationView
define(['order!jQuery', 'order!Underscore', 'order!Backbone', 'GenreView', 'GenreEditView', 'GenreModel', 'GenreCollection'],
function ($, _, Backbone, GenreView, EditView, Genre, GenreCollection) {
// Actual App view
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;
});
// Genre View - el returns the template enclosed within a tr
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;
});
//Genre-Edit view
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 () {
// Directly bind the name change to the model, best use of back bone.
// Otherwise set the data during save which ever you prefer
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;
});
// Genre Delete View
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;
});
// Genre Model
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;
});
// GenreCollection
define(['jQuery', 'Underscore', 'Backbone', 'GenreModel'], function ($, _, Backbone, Genre) {
var GenreCollection = Backbone.Collection.extend({
model: Genre,
url: '/StoreManager/GenreList/'
});
return GenreCollection;
});
请记住,以上代码不在单个js文件中,它们位于不同的js文件中。现在我们需要在我们的Genre.shtml中引用Require.js,并使其加载GenreApp.js(注意脚本标签上的data-main属性)及其依赖项。
@{
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>
关注点
要体会RequireJs的重要性及其优势,必须在你的代码中大量使用它。此外,当你的项目不断增长时,RequireJs的优势会变得非常明显,管理依赖关系可能会成为一个难题。 此外,RequireJs将帮助你更有效地测试你的javascript。我们将在后续教程中看到这一点,例如实现jasmine或qunit。 在此之前,尝试一下RequireJs。以下是一些我希望你感兴趣的链接。
https://requirejs.node.org.cn/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/
-- 匿名用户