2
votes

I have a widget that has a drop down list, that needs to be refreshed every time I go to the view. Is there a callback that I can use in the widget's viewmodel, that is called everytime the widget is used? Something that behaves exactly like a regular viewmodel's activate callback.

The widget's activate function is not called everytime the parent view is loaded:

ctor.prototype.activate = function (settings) {
    this.settings = settings;

};

But the parent viewmodel's activate function is called everytime the view is called:

    var activate = function (routeData) {
        var id = parseInt(routeData);
        return datacontext.getEntityByID('ProductionRun', id, productionRun)
                          .then(loadProducts)
                          .fail(queryFailed);
    };

I would like to move the logic about loadProducts from the parent viewmodel, to the widget's viewmodel, and in the widget's view, use the collection from the widget's viewmodel, not the parent's viewmodel. Change from:

<select data-bind="options: $parent.products, optionsText: 'name', optionsValue: 'productID', value: settings.productionRun().productID"></select>

To:

<select data-bind="options: products, optionsText: 'name', optionsValue: 'productID', value: settings.productionRun().productID"></select>

Thanks

====== Update on 2013-10-29 ======

Here's the code from App/widgets/productionRunDetails/viewmodel.js:

define([
    'durandal/app',
    'durandal/composition',
    'messaging',
    'services/datacontext',
    'services/datacontext.product'
], function (app, composition, messaging, datacontext, datacontext_product) {

    function myObject() {
        var productionRunStatuses = ko.observableArray(datacontext.lookups.productionRunStatuses);
        var isSaving = ko.observable(false);
        var hasChanges = ko.computed(function () {
            return datacontext.hasChanges();
        });
        var canSave = ko.computed(function () {
            return hasChanges() && !isSaving();
        });
        var saveProductionRunDetailsCommand = function () {
            isSaving(true);
            return datacontext.saveChanges().fin(complete);

            function complete() {
                isSaving(false);
                app.trigger(messaging.messages.productionRunDetailsWidget_saved, this);
            }
        }
        var cancelProductionRunDetailsCommand = function () {
            datacontext.cancelChanges();
        }

        var closeProductionRunDetailsCommand = function () {
            datacontext.cancelChanges();
        }


        var self = this;
        self.canSave = canSave;
        self.productionRunStatuses = productionRunStatuses;
        self.saveProductionRunDetailsCommand = saveProductionRunDetailsCommand;
        self.cancelProductionRunDetailsCommand = cancelProductionRunDetailsCommand;
        self.closeProductionRunDetailsCommand = closeProductionRunDetailsCommand;
    }

    myObject.prototype.activate = function (settings) {
        this.settings = settings; //This gets called once, during the whole life cycle of the widget
    };


    //myObject.prototype = (function () {
    //    var activate = function () {
    //    };
    //    return {
    //        activate: activate
    //    }
    //})(); // As soon as I add this code (myObject.prototype), the widget does not work.


    var myObj = new myObject();

    return myObj;
});

The widget is implement this way in the parent view:

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

I'm passing a ko observable called productionRun from the parent view to the widget (which I have available in the settings parameter within the viewmodel). There's a dropdown list in the widget that gets loaded in the parent viewmodel. I'd like to move the load operation from the parent viewmodel, to the widget's viewmodel, and change the binding from:

<select data-bind="options: $parent.products, etc

To:

<select data-bind="options: products

But in order to do that, I need to have an activate callback that gets executed everytime the parent viewmodel is activated (through navigating to that parent view).

Is this possible? I followed Durandal's example about creating widgets, but the activate callback is only called once ( http://durandaljs.com/documentation/Creating-A-Widget/ ). I'm new to Hot Towel + Durandal + Breeze. Thank you.

2
Are you sure that your activate is not getting called? Because in a newly created Durandal 2.0 project it seems it is working fine... how do you load your parent view?nemesv
Hello nemesv, the function ctor.prototype.activate is called only once, unlike var activate = function (routeData), that is called everytime I go to the view. I followed these instructions to set up the widget ( durandaljs.com/documentation/Creating-A-Widget ) , everything is working, etc, it's just that the activate of the widget is called once during the whole life-cycle of the SPA. Should this work this way? Or is there another callback that I use? I need a callback that I can trust that will be executed everytime that widget is called.dlabarca

2 Answers

0
votes

By the looks of it you need to double check how you have gone about implementing the prototype pattern.

As you are using durandal you will need to use the revealing prototype pattern so binding and activation can occur.

Furthermore prototyped methods need to be in a self executing function in order to be attached before the binding is about to occur so they are accessible.

Here I will show you an example of this:

function myObject(data){
    var self = this;
    self.prop1 = ko.observable(data.prop1);
    self.prop2 = ko.observable(data.prop2);
    self.joinProps = function(){
        return self.prop1() + self.prop2();
    };
}

myObject.prototype = (function(){
    var activate = function(){
        return this.prop1() + this.prop2();
    };
    return {
        activate: activate,
        joinProps: this.joinProps
    }
})(); // See the () and the end so they get executed. 

Make sure you return your parent object methods also like I have in the prototype as if you return them there then thats the end and your prototyped method won't get attached.

Finally you can test this using:

var myObj = new myObject({ 'prop1' : 1, 'prop2' : 2 });

ko.applyBindings(myObj);

alert(myObj.joinProps());

Thanks to durandal you don't need to call applyBindings. Just make sure you return your equivalent of myObj.

Hope this helps

0
votes

Going to have to retract the previous answer, I don't think that is a good solution as the event will update all instances of the widget. A similar but better idea is to use observables to update the view. In your case, passing the id in as an observable and subscribing to it will let you update the widget when the id changes.

this.routeData.id.subscribe(function () {
    //refresh data here
});