具有多个视图的Durandal Widget

时间:2013-09-16 19:21:17

标签: single-page-application durandal

我正在使用Durandal开发SPA,我创建了一个用于显示特定页面组件的小部件。在Durandal Documentation之后,小部件位于app/widgets/my-widget中,由viewmodel.jsview.html组成。

现在,我想为同一个小部件添加一个不同的视图 - 实际上,要有一个“基本”视图和一个“高级”视图,在ViewModel中有一个标志,将使用它。

我不想创建两个不同的小部件,因为ViewModel完全相同,我想避免不必要的代码重复。我也不想将视图的两个版本都放到view.html中,只是根据ViewModel中的标志显示一个或另一个版本,因为随着功能稍后添加到窗口小部件中,这将很快成为维护的噩梦上。

我认为一个答案是做某种View Composition,其中要根据标志从ViewModel返回要编写的视图的名称,但我不确定该怎么做这一点。

有没有人有办法做到这一点?我应该采用不同的方式解决这个问题吗?


更新以下是我想要做的一个示例。有一个小部件workitem,用于显示“工作项”。在ViewModel中,工作项具有属性status,可以使用值readyin-progresscompleteinvalid。根据工作项的状态,需要显示的信息(以及它的视图)完全不同。我想要做的是建立这样的东西:

-widgets
|
|-workitem
|  |-viewmodel.js
|  |-view-ready.html
|  |-view-in-progress.html
|  |-view-complete.html
|  |-view-invalid.html

...然后根据ViewModel中的属性自动选择其中一个视图。

3 个答案:

答案 0 :(得分:3)

我们对多视图小部件采用的方法是简单地使用ifvisible绑定,并将其绑定到保存视图类型的viewmodel上的observable(例如'compact') ,'扩展','分组'等。)。

对于我们的datepicker小部件,我们的高级视图是这样的(仅显示两个视图):

</div>
    <div data-bind="visible: viewMode() === 'months'">
        <div data-bind="compose: {model: 'widgets/datepicker/datepickerMonths', view: 'widgets/datepicker/datepickerMonths', activate: true, activationData: messageChannel }">    </div>
    </div>
    <div data-bind="visible: viewMode() === 'years'">
        <div data-bind="compose: {model: 'widgets/datepicker/datepickerYears', view: 'widgets/datepicker/datepickerYears', activate: true, activationData: messageChannel }">    </div>
    </div>
</div>

viewMode observable允许我们在视图之间切换。

我们在这种情况下使用visible绑定有两个原因:

  1. 性能:布局仍然在DOM中加载,只是隐藏;
  2. jQuery clickoutside插件被if绑定混淆了(if绑定从用户的鼠标指针下踢出DOM,可以这么说,并引导clickoutside插件相信用户已经点击外面)。
  3. 通过上述方法,您的窗口小部件的单个view.html文件可以引用多个可动态组合的视图。

    我提供了一个链接到我的日期选择器的公共SkyDrive(现在是OneDrive)文件夹,完全是为了利用Durandal的组合和小部件系统,以及KnockoutJS:video of datepicker as I switch from one view to another。使用上面概述的技术进行视图切换。

    日期选择器的特征:

    • datepicker是一个多视图小部件
    • 它组成了一个输入字段和我们的popover小部件(换句话说,一个小部件内的小部件)
    • 高级视图动态组合三个不同的视图,在用户交互时在它们之间切换
    • 它依赖于postal.js作为客户端消息总线,用于在视图之间路由选择,与父视图协调,以及更新输入控件(如果您愿意,可以使用Durandal的内置pub / sub) )
    • 它使用jwerty.js进行键盘管理

答案 1 :(得分:2)

使用area,小部件视图无法像普通视图一样轻松切换,因为它们始终为partial类型。

为了更改它们,您最有可能需要覆盖widget.convertKindToModulePathwidget.convertKindToViewPath

以下是https://github.com/BlueSpire/Durandal/issues/217

的示例
var oldConvert = widget.convertKindToModulePath;
widget.convertKindToModulePath: function(kind) {
    if (typeof(kind) == 'function') {
        return widget.mapKindToModuleId(kind());
    }
    return oldConvert(kind);
}

<强>更新 小部件可以通过使用例如可观察量或计算值,例如在你看来:

<!-- ko widget: getWidgetSettings() -->
<!-- /ko -->

getWidgetSettings可以是ko.computed(取决于状态),而不是{kind: 'workitem'}返回{kind: { id : 'workitem', status: 'statusId'}}之类的内容。

现在您需要相应地调整widget.convertKindToModulePathwidget.convertKindToViewPath,因为OOTB Durandal希望类型为string,但现在它是object

以下内容可以帮助您入门:

var oldconvertKindToModulePath = widget.convertKindToModulePath;
widget.convertKindToModulePath = function( kind ) {
    if ( typeof(kind) == 'object' ) {
        return  'widgets/' + kind.id + '/viewmodel';
    }
    return oldconvertKindToModulePath(kind);
};

var oldconvertKindToViewPath = widget.convertKindToViewPath;
widget.convertKindToViewPath = function( kind ) {
    if ( typeof(kind) == 'object' ) {
        return 'widgets/' + kind.id + '/' + statusViewMap[statusId] ;
    }
    return oldconvertKindToViewPath(kind);
};

作为将其作为小部件实现的替代方法,您可以考虑将其实现为标准模块并利用getView方法。这是一个例子

http://dfiddle.github.io/dFiddle-2.0/#view-composition/getView

define(['knockout'], function(ko) {

  var roles = ['default', 'role1', 'role2'];
  var role = ko.observable('default');

  var getView = ko.computed(function(){
        var roleViewMap = {
          'default': 'viewComposition/getView/index.html',
          role1: 'viewComposition/getView/role1.html',
          role2: 'viewComposition/getView/role2.html'
        };

        this.role = (role() || 'default');

        return roleViewMap[this.role];
  });


  return {
    showCodeUrl: true,
    roles: roles,
    role: role,
    getView: getView,
    propertyOne: 'This is a databound property from the root context.',
    propertyTwo: 'This property demonstrates that binding contexts flow through composed views.'
  };


});

该示例使用的是单例,它不能满足具有多个独立工作项的要求,因此您必须使用构造函数模式重写它。

随意分叉,我正在接受拉动请求:)。

答案 2 :(得分:2)

正如您所建议的那样,您也可以使用合成并返回相应的视图。 可能类似于以下内容(使用view location strategy):

localViewStrategy - 确定您可以为此提出一个更好的名称:)

define(['durandal/system', 'durandal/viewLocator'], function (system, viewLocator) {
    return function(settings) {
            var moduleId = system.getModuleId(settings.model);
            var viewName = settings.viewName;
            var path = moduleId.substring(0, moduleId.lastIndexOf('/') + 1) + viewName + '.html';

        return viewLocator.locateView(path, settings.area , settings.parent);
    };
});

小工具视图模型

define(function(){
    var ctor = function() {
        //assuming this is your default view?
        this.view = ko.observable('view-ready');
    };

    ctor.prototype.activate = function (settings) {
        var self = this;

        if(settings.view !== null){
            this.view(settings.view);
        }
    }
    return ctor;
});

<强> view.html

<span data-bind="compose: { model: $data, strategy: 'path/to/strategy/localViewStrategy', 'viewName' : view, activate: false, preserveContext: true, cacheViews : false }">

</span>

不确定这是不是最佳方式!