当在MVC .NET应用程序中使用像knockout,kendo,angular ...这样的库应用MVVM模式时,我们可能需要处理一些问题:
以下是解决这些问题的方法:
首先,我们需要一些核心函数允许在继承结构中扩展JS对象。基于jquery,它可以在app.core.js这样的文件中完成:
(function ($) {
AppGlobal = function () { };
AppGlobal.prototype = {
DOT: '.',
init: AppGlobal,
namespace: function (namespace, context) {
context = context || window;
var index = namespace.indexOf(this.DOT);
if (index > 0) {
var subNamespace = namespace.substring(index + 1);
var subContextName = namespace.substring(0, index);
context[subContextName] = context[subContextName] || {};
return this.namespace(subNamespace, context[subContextName]);
}
context[namespace] = context[namespace] || {};
return context[namespace];
},
extend: function (protobj, skipBaseConstructor) {
protobj = protobj || {};
var subClass = null;
var baseConstructor = this;
if (typeof (baseConstructor) != "function") {
baseConstructor = this.init;
}
if (protobj.init) {
subClass = function () {
if (!skipBaseConstructor) {
baseConstructor.apply(this, arguments);
}
protobj.init.apply(this, arguments);
};
}
else {
subClass = function () {
if (!skipBaseConstructor) {
baseConstructor.apply(this, arguments);
}
};
protobj.init = baseConstructor;
}
subClass.prototype = subClass.prototype || {};
$.extend(true, subClass.prototype, this.prototype, protobj);
subClass.extend = this.extend;
return subClass;
},
};
app = new AppGlobal();
app.Core = app.extend({
});
})(jQuery);
将该文件放在〜\ Scripts文件夹中,并在服务器端的捆绑配置中注册:
bundles.Add(new ScriptBundle("~/js/app/core").Include("~/Scripts/app*"));
然后我们可以在布局中引用核心脚本,以便在基于该布局的视图中重复使用:
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/knockout")
@Scripts.Render("~/js/app/core")
@RenderSection("Scripts", required: false)
其次,我们需要一个特定于视图的脚本对象的基础。它至少需要保持对HTML元素的引用作为视图中的容器。这将是与视图模型绑定的元素,并且可以是使用jquery选择器来避免重复id,意外匹配元素等任何错误非常重要的范围......从jquery和knockout中获得促进,它可以像这样:
(function ($, ko) {
app.namespace("app.view").ViewBase = app.Core.extend({
init: function (options) {
this.element = options.element;
}
});
app.namespace("app.view").KoView = app.view.ViewBase.extend({
init: function (options) {
var model = options.data;
if (model) {
for (var prop in model) {
model[prop] = ko.observable(model[prop]);
}
$.extend(this, model);
}
ko.applyBindings(this, this.element);
}
});
})(jQuery, ko);
要在核心脚本包中呈现,脚本应命名为app.view.js并放在〜\ Scripts文件夹中。 "数据"和"元素"通过参数"选项"构造函数将在稍后介绍。
接下来,我们将使用某个视图的标记应用MVVM,例如,查看模型Customer的编辑:
@model CustomerModel
@{
ViewBag.Title = "Edit";
}
<h2>
Edit</h2>
@using (Html.BeginForm())
{
<fieldset>
<legend>Customer</legend>
<div class="editor-label">
@Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
<input type="text" name="Name" data-bind="value: Name" />
@Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Phone)
</div>
<div class="editor-field">
<input type="text" name="Phone" data-bind="value: Phone"/>
@Html.ValidationMessageFor(model => model.Phone)
</div>
<p>
<input type="button" value="Hello" data-bind="click: hello"/>
<input type="submit" value="Save" />
</p>
</fieldset>
}
此视图包含一些HTML元素绑定(使用knockout),其中包含从Edit操作返回的CustomerModel属性:
public class CustomerModel
{
public string Name { get; set; }
public string Phone { get; set; }
}
public class CustomerController : Controller
{
public ActionResult Edit(int id)
{
var model = new CustomerModel {Name = "a name", Phone = "a number"};
return View(model);
}
}
和按钮的事件处理程序&#34; Hello&#34;。
通常,我们必须在客户端定义一个视图模型对象,它具有各自的属性和处理按钮事件的函数。但是,采用这种方法,我们并不需要。为此,请在app.core.js中添加更多功能:
(function ($) {
//...
AppGlobal.prototype = {
//...
VIEW_OPTIONS: 'view-options',
evaluate: function (expression) {
expression = expression.replace(/(\w+ *):/g, '"$1":');
if (!/^({.*})$/.test(expression)) {
expression = '({' + expression + '})';
}
return eval(expression);
},
initView: function (element, data) {
var viewOptions = $(element).data(this.VIEW_OPTIONS);
if (viewOptions) {
viewOptions = viewOptions.replace(/data *: *Model\.(.+)/i, 'data: this.$1');
var options = this.evaluate.apply(data, [viewOptions]);
var settings = { element: element, data: options.data || data };
if (options.options) {
$.extend(settings, options.options);
}
return new options.type(settings);
}
}
};
//...
})(jQuery);
然后扩展jquery函数(放入app.jquery.extensions.js并放在〜\ Script文件夹中):
(function ($) {
$.fn.extend({
initViews: function (data) {
var viewOptionsAttr = 'data-' + app.VIEW_OPTIONS
var elements = this.find('div[' + viewOptionsAttr + '], fieldset[' + viewOptionsAttr + ']');
var views = [];
$(elements).each(function (index, element) {
var view = app.initView(element, data);
if (view) {
views.push(view);
}
});
return views;
}
});
$.initViews = $.fn.initViews;
})(jQuery);
在使用&#34; initViews&#34;之前函数,我们需要特定于视图的脚本Edit.js:
(function ($) {
app.namespace("app.view.customer").Edit = app.view.KoView.extend({
init: function (options) {
this.title = options.title;
this.Phone('12345');
},
hello: function (e) {
alert('Hello' + this.Name());
}
});
})(jQuery);
并使用此HTML扩展程序:
public static IHtmlString RegisterViewScripts(this HtmlHelper helper)
{
var bundles = BundleTable.Bundles;
const string virtualPath = "~/js/app/view";
var view = helper.ViewContext.View.GetViewName();
var path = string.Format("~/Scripts/{0}/{1}.js",
(string)helper.ViewContext.RouteData.GetRequiredString("controller"), view);
bundles.Add(new ScriptBundle(virtualPath).Include(path));
return Scripts.Render(virtualPath);
}
除了将Edit.js放在〜\ Script \ Customer中之外,我们可以在客户的视图中将其呈现为如下所示:
@section Scripts {
@Html.RegisterViewScripts()
}
之后,修改视图标记以与&#34; initViews&#34;:
绑定<fieldset data-view-options="type: app.view.customer.Edit, options: {title: '@ViewBag.Title'}">
最后,请致电&#34; initViews&#34;在布局中:
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/knockout")
@Scripts.Render("~/js/app/core")
@RenderSection("Scripts", required: false)
<script type="text/javascript">
$(function(){
$.initViews(@(Html.Raw(Json.Encode(Model))));
});
</script>
使用〜\ Scripts \ Customer \ Edit.js中定义的JS对象绑定〜\ Views \ Customer \ Edit.cshtml视图中的容器元素,而不在客户端视图模型对象中创建任何属性。