Jay Bienvenu
Jay Bienvenu

Reputation: 3293

Injecting data into a Durandal widget

I have made a "simple" alert widget called statusAlert to display messages when data fails to arrive as expected. Thus far I'm unable to get data into it. I figure that I could inject an observable into the widget, and display and populate the widget when the observable is set to an object. But I haven't figured out how to get this concept working.

statusAlert/view.html

<div class="alert alert-danger" role="alert" data-bind="visible: content">
    <button type="button" class="close" aria-label="Close" data-bind="click: hide"><span aria-hidden="true"><i class="fa fa-times"></i></span></button>
    <button type="button" class="close" aria-label="Show Details" data-bind="click: showDetails"><span aria-hidden="true"><i class="fa fa-info-circle"></i></span></button>
    <p class="lead" data-bind="text: header"></p>
    <p data-bind="text: message"></p>
    <p data-bind="visible: detailsIsVisible"><pre data-bind="text: details"></pre></p>
</div>

statusAlert/viewmodel.js

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

    var ctor = function () {
        this.content = ko.observable();
        this.detailsIsVisible = ko.observable(false);
    };

    // Methods.

    // Activate event handler.
    ctor.prototype.activate = function () {
       //TODO What goes here?
    };

    // Destroying the content will result in the alert becoming invisible.
    ctor.prototype.hide = function () {
        this.content(null);
    };

    ctor.prototype.showDetails = function () {
        this.detailsIsVisible(true);
    };

    // Data fields: Computed observables that extract data from 'content.'

    ctor.prototype.header = ko.computed(function () {
        if (this.content) {
            return this.content() ? this.content().header : null;
        }
        return null;
    }, this);

    ctor.prototype.message = ko.computed(function () {
        if (this.content) {
            return this.content() ? this.content().message : null;
        }
        return null;
    }, this);

    ctor.prototype.details = ko.computed(function () {
        if (this.content) {
            return this.content() ? this.content().details : null;
        }
        return null;
    }, this); 

    return ctor;
});

Pertinent code from parent viewmodel

var statusAlertObservable = ko.observable();

$.ajax({
    success: function (data, status, request) {
        if (data.ResponseStatus.ErrorCode) {
            statusAlertObservable({
                header: "Service reports error.",
                message: data.ResponseStatus.Message,
                details: data.ResponseStatus
            });
        } else {
        // (do something with data)
        }
    },
    error: function (data, status, error) {
        statusAlertObservable({
            header: "Error getting application data.",
            message: error,
            details: data
        });
    }
});

return {
    statusAlertObservable: statusAlertObservable,
    //...
}

Pertinent code from parent view

<div data-bind="statusAlert: 0"></div>

Upvotes: 0

Views: 1153

Answers (1)

user3174746
user3174746

Reputation:

First of all, your parent data-binding need to be adjusted. It should be the following:

<div data-bind="widget: {kind: 'statusAlert', ...}"></div>

The ellipses represents properties that you might pass into your widget:

<div data-bind="widget: {kind: 'statusAlert', config: {alertObservable: statusAlertObservable}}"></div>

Note that the config property is arbitrary. I could have named it anything. I could also include any other custom properties I wish.

There is a subtlety here: In the code immediately above, I'm passing in the observable itself, not the observable's value. That means that in my widget's activate handler, I'll need to de-reference the observable to obtain it's value. Alternatively, you could pass in the value itself, this way:

<div data-bind="widget: {kind: 'statusAlert', config: {alertObservable: statusAlertObservable()}}"></div>

Note the parentheses on statusAlertObservable. I wouldn't need to de-reference this observable within the widget's viewModel as it is de-referenced already.

A Few Other Things

Make sure you place your widget's view and viewModel under a folder in the widgets folder that carries the name of the widget. Here's a screenshot of ours:

widget folder structure

In your case, the name of the folder under the widgets folder will be statusAlert.

Secondly, make sure you name your widget's viewModel viewModel.js, and your widget's view view.html. Other names are not supported without writing a custom viewLocator. So, it is the name of the containing folder--in this case statusAlert--that is controlling the naming convention.

Thirdly, add an activation handler called activate (it must be called this) into the viewModel of your widget, where you pass your widgetSettings:

ctor.prototype.activate = function (widgetSettings) {
    this.statusAlertObservable = widgetSettings.config.alertObservable;
    //alternatively: this.statusAlertObservable = widgetSettings.config.alertObservable();
};

Durandal automatically passes in your widget binding's properties as "settings" on the activate handler.

Fourthly, it is not a good idea to name your widget's constructor ctor. This makes debugging very difficult, especially if they're all named that way. In your case, name it StatusAlert (in Pascal case, since it's a constructor).

Finally, take a look at Durandal's documentation on widgets, here.

UPDATE

As the OP pointed out in his comments, you can register your widget bindings, and then simply bind them with a vanilla data-bind.

I don't recommend that approach. When you have a lot of widget bindings, particularly in a large application, it makes it difficult to visit them with search. Also, it's difficult for someone else to distinguish between custom Knockout bindings and widgets, since there's no apparent difference. In a large codebase, the programmer himself might even start to have doubts about which is which, and then have to visit his main.js file or his custom Knockout bindings to determine which is which.

At a minimum, if you insist on registering your widgets, at least give them names that end in, say, Widget. So, in the OP's case, we would call the widget StatusAlertWidget.

Upvotes: 1

Related Questions