一个定义良好的结构,用于javascript对象模型和组织,用于ASP .NET MVC应用程序中的javascript文件

时间:2014-10-09 16:34:15

标签: javascript asp.net-mvc mvvm knockout.js

当在MVC .NET应用程序中使用像knockout,kendo,angular ...这样的库应用MVVM模式时,我们可能需要处理一些问题:

  • 当这些脚本仅需要此视图但该视图(特定于视图的脚本)时,如何有效地组织和呈现脚本?
  • 如何在服务器端模型与客户端的视图模型之间关联数据,而无需手动声明视图模型?
  • 如何促进MVVM API将特定于视图的脚本与视图中的HTML元素绑定?

以下是解决这些问题的方法:

  • 首先,我们需要一些核心函数允许在继承结构中扩展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视图中的容器元素,而不在客户端视图模型对象中创建任何属性。

0 个答案:

没有答案