I am working on a SPA using Durandal, and I have created a widget for displaying a particular page component. Following the Durandal Documentation, the widget is in app/widgets/my-widget
and is composed of viewmodel.js
and view.html
.
Now, I want to add a different view to the same widget - in effect, to have a "Basic" view and an "Advanced" view, with a flag in the ViewModel for which one will be used.
I do not want to create two different widgets, because the ViewModel is exactly the same and I want to avoid unnecessary code duplication. I also do not want to put both versions of the view into view.html
and just display one or the other based on the flag in the ViewModel, because that will quickly become a nightmare to maintain as features are added to the widget later on.
I would think that one answer is to do some kind of View Composition, where the name of the view to be composed is returned from the ViewModel based on the flag, but I'm not really sure how to do that.
Does anyone have a way to do this? Is there a different way I should be approaching this problem?
UPDATE Here is an example of what I want to do. There is a widget workitem
that is used to display "Work Items". In the ViewModel, a Work Item has a property status
which can take the values ready
, in-progress
, complete
, or invalid
. Depending on the status of a Work Item, the information that needs to be displayed about it (and therefore its View) is completely different. What I want to do is set up something like this:
-widgets
|
|-workitem
| |-viewmodel.js
| |-view-ready.html
| |-view-in-progress.html
| |-view-complete.html
| |-view-invalid.html
... and then automatically select one of these views based on the property in the ViewModel.
The approach we take to multi-view widgets is to simply use the
if
or thevisible
binding, and bind it to an observable on the viewmodel that holds the view type (e.g. 'compact', 'expanded', 'grouped', etc.).For our datepicker widget, our high-level view is this (showing only two of the views):
The
viewMode
observable allows us to switch between views.We use the
visible
binding in this case for two reasons:if
binding (theif
binding kicks the DOM out from under the user's mouse pointer, so to speak, and leads the clickoutside plugin to believe the user has clicked outside).With the approach above, your widget's single view.html file can reference multiple, dynamically composable views.
I have provided a link to my public SkyDrive (now OneDrive) folder of our datepicker, written entirely to leverage Durandal's composition and widget systems, along with KnockoutJS: video of datepicker as I switch from one view to another. The switching of views occurs using the technique outlined above.
Characteristics of the datepicker:
As you have suggested you can also do this using the composition and returning the appropriate view. Maybe something like the following (using a view location strategy):
localViewStrategy - sure you can come up with a better name for this :)
Widget View Model
view.html
Not sure if this is the best way to go about it though!
Widgets views can't be easily switched like normal views by using
area
as they are always of typepartial
.In order to change them you most probably have to overwrite
widget.convertKindToModulePath
andwidget.convertKindToViewPath
Here's an example from https://github.com/BlueSpire/Durandal/issues/217
Update Widget can be constructed by using e.g. observables or computeds e.g. in your view:
getWidgetSettings
could be ako.computed
(depended on status) that instead of{kind: 'workitem'}
returns something like{kind: { id : 'workitem', status: 'statusId'}}
.Now you'd need to adjust
widget.convertKindToModulePath
andwidget.convertKindToViewPath
accordingly as OOTB Durandal expects kind to be of typestring
but now it's anobject
.Something along the following should get you started:
As an alternative to implementing this as a widget you might consider implementing this as standard module and leveraging the
getView
method. Here's an examplehttp://dfiddle.github.io/dFiddle-2.0/#view-composition/getView
The example is using a singleton, which wouldn't fulfill the requirement having multiple independent workitems, so you'd have to rewrite it using the constructor pattern.
Feel free to fork and I'm taking pull requests :).