External Templates vs. ko.compose vs. Durandal Widgets in a Durandal 2.0 Dashboard

2,759 views
Skip to first unread message

gsr...@gmail.com

unread,
Aug 12, 2013, 6:00:25 PM8/12/13
to duran...@googlegroups.com
We’re developing a SPA using Durandal 2.0.  The application is a dashboard-type of application.  That is, there could be multiple dashboards, and, on each dashboard, there could be multiple widgets (our application’s widgets, not Durandal widgets – although that’s part of the question later).

From a viewmodel, we make an ajax call to the server to get the data about dashboards and widgets, something like:

data: [
{"dashboardId": 1, "dashboardName": "First Dashboard",
 "widgets": [{"widgetId": 1, "dashboardId": 1, "widgetType": "Alarms", "widgetName": "abc"},{"widgetId": 2, "dashboardId": 1, "widgetType": "Links", "widgetName": "xyz"},{ next widgets }, ... ]},
{"dashboardId": 2, "dashboardName": "Second Dashboard",
 "widgets": [{"widgetId": 9, "dashboardId": 2, "widgetType": "Reports", "widgetName": "123"},{ next widgets }, ... ]},
{ next dashboards and widgets }]

As you can see, each widget could be of a different type (e.g., Alarms, Reports, etc.), which means different styling and data inside the widget.

Currently, we have the view/viewmodel building the multiple dashboards (i.e., basically containers for widgets), but we’re not sure what is the best way to handle the individual widgets.

We initially thought of using external templates (i.e., a separate template for each widget type), but then we thought we read somewhere that Durandal doesn’t support external templating.  We then thought maybe just use Durandal’s composition engine or Durandal widgets.

So, given our scenario, what is the recommended approach for dynamically building widgets on our view?

External templates (does Durandal even support external templates)?

Using the ko.compose binding?  Something like:

<!-- ko if: widgetIsAlarms() -->
    <!-- ko compose: { view: 'widgets/alarms/alarmswidget' } --><!-- /ko -->
<!-- /ko -->

Or, should we try to create a “Durandal widget” for each widget type and then somehow dynamically place the Durandal widget on the view depending on the type?

Or, is there some other recommended way?

Thanks!


gsr...@gmail.com

unread,
Aug 19, 2013, 7:41:57 PM8/19/13
to duran...@googlegroups.com
Anyone with any thoughts???

I have it working (sort of) using just standard knockout in-line templates, something like:

                    <!-- ko if: widgetType() === 'Alarms' -->
                    <div>
                        <div class="widget-header">
                            <div class="widget-header-title">
                                <p data-bind="text: widgetFullTitle"></p>
                            </div>
                        </div>
                        <div class="widget-body">
                            <div class="widget-details">
                                widget html and data-bind stuff here
                            </div>
                        </div>
                    </div>
                    <!-- /ko -->

                    <!-- ko if: widgetType() === 'Values' -->
                    <div>
                        all other html and data-bind stuff here
                    </div>
                    <!-- /ko -->

                    <!-- ko if: widgetType() === 'nextone' -->
                    <div>
                        all other html and data-bind stuff here
                    </div>
                    <!-- /ko -->

                    ...

But this doesn't work so well.  One, I have a lot of similar code repeated over and over for each widget type.  Also, when I add a new widget (just pushing a new element into an observableArray), it's exactly like any other widget of that same type that's already on the display (e.g., if there are 2 alarms widgets, both get all the same data and respond to all the same binding updates).

What we really need is to add a widget instance to the display and have it be separate entity from any other widget.  It's like we need a base class for a widget, which contains properties and methods common to all widgets, then we need specific widgets for each type that inherits from the base class, and then when the widget is "instantiated" and put on the display, it's a separate object from any other widget instances that get created.

We're thinking we have to go down to the lowest common denominator of jquery and code everything ourselves and append to the DOM, but we wanted to see if there were some slick Durandal/Knockout facilities within the Durandal framework to do something like what we need.

Any thought would be greatly appreciated.

Thanks!

Tyrsius

unread,
Aug 19, 2013, 8:41:28 PM8/19/13
to duran...@googlegroups.com
There is a very simple solution to this using Durandal's composition. Composition automatically locates the view for a viewmodel using its moduleId (the filename of a viewmodel). This is essentially external templates. Assuming you have an application directory like this

/app
   /viewmodels
        /dashboard.js
        /widgetA.js
        /widgetB.js
        /widgetC.js
   /views
        /dashboard.html
        /widgetA.html
        /widgetB.html
        /widgetC.html

You could easily load them all with a dashboard that looked like this:

define(['knockout', 'someService', 'viewmodels/widgetA', 'viewmodels/widgetB', 'viewmodels/widgetC'], 
function (ko, someService, WidgetA, WidgetB, WidgetC) {  
    var widgets = ko.observableArray();
    return {
        widgets: widgets,
        activate: function() {
            someService.loadWidgets().then(function(result) {
                result.forEach(function(item) {
                    if (item.type === "A")
                        widgets.push(new WidgetA(item.data));
                    else if (item.type === "B")
                        widgets.push(new WidgetA(item.data));
                    else if (item.type === "C")
                        widgets.push(new WidgetA(item.data));
                    else
                        throw new Error("Unknown widget type");
                });
            });
        }
    }
});

and a dashboard.html view like this

<div data-bind="foreach: widgets">
<div data-bind="compose: $data"></div>
</div>

The compose binding would go pull in the view (the html file) for each widget, and display it. It's really that simple.


gsr...@gmail.com

unread,
Aug 23, 2013, 9:47:36 AM8/23/13
to duran...@googlegroups.com
Hello again, and thank you for the response.  I tried your suggestion and got it working.  You were correct, the implementation you provided is fairly simple.  However, my situation is a bit more complex.

In your solution, the widget viewmodels are basic constructor functions that bind up some data to the widget view (which is partially what I need).  But, my "custom widgets" (note I’m using the phrase “custom widget” to differentiate from a Durandal widget) need to be much more full-featured than that.

My custom widgets, once composed into the dashboard view, need to be separate entities in and of themselves and have more functionality.  It’s kind of like they need to be “full” view/viewmodel implementations without, of course, the routing and url changing.

After being composed into the dashboard view, they need to be able to make their own ajax calls back to the server and updated their data separately from any other widget on the view.  Each instance needs to be configurable separately from any other widget on the page.  They need to be movable and resizable, separately from any other widget on the view.  They need to be able to minimize (slide toggle) separately from any other widget on the page.  There needs to be the ability to add multiple of the same type of widget to the same dashboard (multiple WidgetAs in your example), and have each one be it’s own entity.  In runtime, I need to be able to add more widgets of all the different types onto the dashboard’s view and have the newly added widget have all the custom functionality as just described.

Anyway, I’ve tried many different variations (different ways of building up model data, building viewmodels and trying to compose them – like the example data-bind="compose: { model:'shell', 'myView.html' }", etc.), and nothing has really worked.

So, I’m back here asking for more Durandal guidance.  Given my situation, is there a better way of building up these full-featured custom widgets using Durandal’s capabilities?  Maybe a Duranadal widget that builds all our custom widgets?  Or some other ideas?

Thanks for the help.

Rob Eisenberg

unread,
Aug 23, 2013, 9:53:58 AM8/23/13
to gsr...@gmail.com, duran...@googlegroups.com
It's really quite simple. You should have an observable array or your widget models. Then use a foreach binding to ityerate the array. The inside the foreach binding have a compose binding. That allows each of your custom widgets to be completely encapsulated. I've got a customer doing exactly what you are describing with great success. You will need to do a bit more work to get the drag drop in there, but you might be able to do that pretty easily with this: https://github.com/rniemeyer/knockout-sortable  Simply replace your foreach binding with that, but use a compose binding still internally to binding each widget. Something like this:

<div data-bind="sortable: widgets">
  <div data-bind="compose: $data"></div>
</div>

--
You received this message because you are subscribed to the Google Groups "DurandalJS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to durandaljs+...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.



--
Rob Eisenberg,
President - Blue Spire
www.durandaljs.com

gsr...@gmail.com

unread,
Aug 23, 2013, 10:46:39 AM8/23/13
to duran...@googlegroups.com, gsr...@gmail.com, r...@bluespire.com
Thanks Rob!  It makes sense what you’re saying (and thanks for the heads up about the knockout-sortable).  But, I guess since I’m fairly new to Durandal (well, new to the whole javascript world, in general), I’m not seeing the light yet.

I’m getting an observable array of widgets and using the compose binding the way you describe, and my widgets get composed on the dashboard view.  But then they seem limited to just binding the data that was passed in the compose: $data binding.  I’ll try to explain a little more.

In my dashboard’s viewmodel (in the activate() event), I’m getting the data from the server:

index.js (for the dashboard view/viewmodel)
return dataservicedashboards.getDashboards()
    .done(function (data) {
        var mappedData = ko.utils.arrayMap(data, function (dashboard) {
            return new Dashboard(dashboard);
        });
        dashboards(mappedData);
    })
    .fail(function (jqXHR, textStatus, errorThrown) {
        system.log('Error retrieving the data from the server.  textStatus: ' + textStatus + ' | errorThrown: ' + errorThrown);
    });
}

model.dashboard.js (constructor function when I new up a dashboard – new Dashboard(dashboard))
define(['knockout' 'dashboards/widgets/alarms/alarmsWidget', 'dashboards/widgets/links/linksWidget', ... (other widgets)],
    function (ko, _, AlarmsWidget, LinksWidget, ...) {
        "use strict";
        var Dashboard = function (dashboard) {
            var self = this;
            self.dashboardId = ko.observable(dashboard.dashboardId);
            self.dashboardName = ko.observable(dashboard.dashboardName);
            self.widgets = ko.observableArray(ko.utils.arrayMap(dashboard.widgets, function (widget) {
                if (widget.widgetType === 'Alarms') {
                    return new AlarmsWidget(widget);
                }
                else if (widget.widgetType === 'Links') {
                    return new LinksWidget(widget);
                }
                ...

                else {
                    throw new Error("Unknown widget type");
                }
            }));
        };
        return Dashboard;
    });

alarmsWidget.js (all widgets follow this same idea)
define(['knockout'],
    function (ko) {
        "use strict";
        var AlarmsWidget = function (widget) {
            var self = this;
            self.widgetId = ko.observable(widget.widgetId);
            self.dashboardId = ko.observable(widget.dashboardId);
            self.widgetType = ko.observable(widget.widgetType);
            self.widgetName = ko.observable(widget.widgetName);
            self.widgetBaseName = ko.observable(widget.widgetBaseName);
            ...
        };
        return AlarmsWidget;
    });

And this all works nicely.  I get the widgets on the dashboard view and they have the data that’s been bound to them.

However, for example, my alarmsWidget.js viewmodel needs to be more functional than this simple constructor function that returns the widget.  Let’s say there’s a button in the alarms widget’s view (alarmWidget.html) and, on clicking that button, I need to send off an ajax request to the server and then update data only in the alarms widget’s view.  How do I do that?

In the simple example, I can’t see how the data-bind=”click: handleTheClick” to the button and have the handleTheClick event handler in the alarmsWidget.js viewmodel to process that action because the alarmsWidget.js viewmodel is a simple constructor function.

I hope this makes sense, but I need the alarmsWidget.js viewmodel to not only data-bind the data as it is now, but also have more functionality to handle various user and programmatic runtime interactions.

Any other suggestions?  And, let me know if you have any questions for further clarification.


One last thing, if I wanted to discuss paying for support options, what would I do?  Send you an email?

Thanks!

Rob Eisenberg

unread,
Aug 23, 2013, 1:01:07 PM8/23/13
to gsr...@gmail.com, duran...@googlegroups.com
You can send me an email about support yes. Regarding your problem, the solution is simple...it's a standard JavaScript pattern...you may not be familiar with it yet.

AlarmsWidget.prototype.handleThClick = function(){ 
   ...do something here...
};

gsr...@gmail.com

unread,
Aug 23, 2013, 7:20:47 PM8/23/13
to duran...@googlegroups.com, gsr...@gmail.com, r...@bluespire.com
Thanks again.  That helps.  I tried a little prototype similar to what you showed, and it worked.  Then I tried to add additional knockout computeds (and the computeds have reference to observables in the main constructor function) to the prototype and it doesn’t work.  I get an error something like: Uncaught TypeError: Property 'alarmId' of object #<Object> is not a function.  I’m sure you already knew this, but, after Googling around for a while and trying all sorts of different things (I didn’t want to put all the different attempts in this post, unless you want me to), I couldn’t get it to work.

Now, I don’t know if I’ll need this functionality (where the prototype is needing to use observables from the constructor function), but I thought I’d just post it here to see if you had any thoughts.

Thanks!

Rob Eisenberg

unread,
Aug 23, 2013, 9:30:21 PM8/23/13
to gsr...@gmail.com, duran...@googlegroups.com
You create your computeds inside the constructor, assigning them to this. Then you can bind to them or access them from the prorotype functions.
Reply all
Reply to author
Forward
0 new messages