我正在使用Durandal开发SPA,我创建了一个用于显示特定页面组件的小部件。在Durandal Documentation之后,小部件位于app/widgets/my-widget
中,由viewmodel.js
和view.html
组成。
现在,我想为同一个小部件添加一个不同的视图 - 实际上,要有一个“基本”视图和一个“高级”视图,在ViewModel中有一个标志,将使用它。
我不想创建两个不同的小部件,因为ViewModel完全相同,我想避免不必要的代码重复。我也不想将视图的两个版本都放到view.html
中,只是根据ViewModel中的标志显示一个或另一个版本,因为随着功能稍后添加到窗口小部件中,这将很快成为维护的噩梦上。
我认为一个答案是做某种View Composition,其中要根据标志从ViewModel返回要编写的视图的名称,但我不确定该怎么做这一点。
有没有人有办法做到这一点?我应该采用不同的方式解决这个问题吗?
更新以下是我想要做的一个示例。有一个小部件workitem
,用于显示“工作项”。在ViewModel中,工作项具有属性status
,可以使用值ready
,in-progress
,complete
或invalid
。根据工作项的状态,需要显示的信息(以及它的视图)完全不同。我想要做的是建立这样的东西:
-widgets
|
|-workitem
| |-viewmodel.js
| |-view-ready.html
| |-view-in-progress.html
| |-view-complete.html
| |-view-invalid.html
...然后根据ViewModel中的属性自动选择其中一个视图。
答案 0 :(得分:3)
我们对多视图小部件采用的方法是简单地使用if
或visible
绑定,并将其绑定到保存视图类型的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
绑定有两个原因:
if
绑定混淆了(if
绑定从用户的鼠标指针下踢出DOM,可以这么说,并引导clickoutside插件相信用户已经点击外面)。通过上述方法,您的窗口小部件的单个view.html文件可以引用多个可动态组合的视图。
我提供了一个链接到我的日期选择器的公共SkyDrive(现在是OneDrive)文件夹,完全是为了利用Durandal的组合和小部件系统,以及KnockoutJS:video of datepicker as I switch from one view to another。使用上面概述的技术进行视图切换。
日期选择器的特征:
答案 1 :(得分:2)
使用area
,小部件视图无法像普通视图一样轻松切换,因为它们始终为partial
类型。
为了更改它们,您最有可能需要覆盖widget.convertKindToModulePath
和widget.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.convertKindToModulePath
和widget.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>
不确定这是不是最佳方式!