3

我正在使用 Durandal 进行 SPA,并且我创建了一个小部件来显示特定的页面组件。在Durandal 文档之后,小部件位于并由和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 中的属性自动选择其中一个视图。

4

3 回答 3

3

我们对多视图小部件采取的方法是简单地使用iforvisible绑定,并将其绑定到视图模型上的可观察对象,该视图模型包含视图类型(例如,'compact'、'expanded'、'grouped' 等)。

对于我们的 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>

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

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

  1. 性能:布局仍然加载在 DOM 中,只是隐藏;
  2. jQuery clickoutside 插件被if绑定弄糊涂了(可以说,if绑定将 DOM 从用户的鼠标指针下踢出,并导致 clickoutside 插件相信用户点击了外部)。

使用上述方法,您的小部件的单个 view.html 文件可以引用多个动态组合视图。

我已经提供了一个指向我的日期选择器的公共 SkyDrive(现在是 OneDrive)文件夹的链接,完全是为了利用 Durandal 的组合和小部件系统以及 KnockoutJS:当我从一个视图切换到另一个视图时的日期选择器视频。使用上述技术进行视图切换。

日期选择器的特点:

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

正如您所建议的,您也可以使用合成并返回适当的视图来执行此操作。可能类似于以下内容(使用视图位置策略):

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;
});

视图.html

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

</span>

不确定这是否是最好的方法!

于 2013-09-17T03:21:17.787 回答
2

小部件视图不能像使用普通视图一样轻松切换,area因为它们始终是 type 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);
}

更新 小部件可以通过使用例如 observables 或计算来构造,例如在您的视图中:

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

getWidgetSettings可能是一个ko.computed(取决于状态)而不是{kind: 'workitem'}返回类似{kind: { id : 'workitem', status: 'statusId'}}.

现在您需要进行相应的调整widget.convertKindToModulePathwidget.convertKindToViewPath因为 OOTB Durandal 期望 kind 是类型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.'
  };


});

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

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

于 2013-09-16T21:27:25.083 回答