Using Durandal & Knockout with jQuery UI Spinner

218 views
Skip to first unread message

Uebyn

unread,
Jan 14, 2015, 9:23:20 AM1/14/15
to duran...@googlegroups.com

Hi all,

I am using jQuery UI's spinner with KnockoutJS and Durandal right now. I followed the answer/example here to create a custom binding that takes care of initializing the spinner and of observing the changes made to the spinner.

Now, I have saved that custom binding as a common module to be loaded in the lib folder, to be invoked by whichever modules that need to use spinners in the future. My problem here is I want to, on this particular page, to retrieve the value of the spinner/<input> to make an ajax call each time the spinner and thus the quantity is updated. I know I can't possibly be putting my custom ajax code in the common module, and that I should be putting it in the relevant viewmodel, but I don't know how to invoke it there.

Here's the view (partial):

<p class="qty">
    <input class="spinner" data-bind="spinner: quantity, spinnerOptions: { min: 1, numberFormat: 'n' }" />
    <a class="remove" href="#">Remove</a>
</p>

And this is the corresponding viewmodel (partial):

function Item(data) {
    (...)
    self.updateQuantity = function(){
    $.ajax(apiUrl + self.id, {
        data: self.quantity,
        type: 'patch',
        contentType: 'application/json',
        success: function(result) {
            alert(result);
        }
        });
    };
}

(...)

     return {
    checkedOutItems: checkedOutItems, //checkedoutItems is the array of Item that's passed to the view, which can call Item's updateQuantity method
(...)
}

Here's the custom binding I've built for the jQuery UI spinner and which I require in my shell.js:

define(function(require) {

    var system = require('durandal/system'),
    app = require('durandal/app'),
    ko = require('knockout');

    ko.bindingHandlers.spinner = {

    init: function(element, valueAccessor, allBindingsAccessor) {

        //initialize datepicker with some optional options
            var options = allBindingsAccessor().spinnerOptions || {};
            $(element).spinner(options);

            //handle the field changing
            ko.utils.registerEventHandler(element, 'spinchange', function () {
        var observable = valueAccessor();
        observable($(element).spinner('value'));
            });

            //handle disposal (if KO removes by the template binding)
            ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
        $(element).spinner('destroy');
            });

    },

    update: function(element, valueAccessor) {
            var value = ko.utils.unwrapObservable(valueAccessor()),
        current = $(element).spinner('value'),
        msg = 'You have entered an Invalid Quantity. \n Please enter at least 1 or remove this item if you do not want to include it in the shopping cart.';

        system.log(value);//shows the new quantity as changed by the user via spinner

        if(isNaN(parseInt(value))) {
        alert(msg);
        }

            if (value !== current && !isNaN(parseInt(value))) {
        $(element).spinner("value", value);
            }
    }
    };

});

As far as I know, I should have something like data-bind="value: quantity, click: updateQuantity" to call the updateQuantity function as soon as the observable quantity changes. However, now that I'm using the spinner custom binding via the custom binding in the lib folder, I'm no longer sure how to call the updateQuantity function anymore. Each change caused by the spinner will be captured by the code in the custom binding (as the system.log shows in Chrome inspector), but in my viewmodel, I don't know where and how to call the function that captures the new quantity to be used in the ajax call.I don't even know the event that's supposed to bind to the method, since everything from click to onFocus doesn't seem to match the event. I am now wondering if I should write custom code for the ajax call for this page only to the custom binding (under update: function), which however means I will have a good chunk of repeated code next time I want to use the spinner.

I realize my understanding of KnockoutJS and Durandal is still fuzzy (just started few weeks ago), so I will be most glad if someone would enlighten me as to where I should go from here. Thank you in advance.

Message has been deleted

mlde...@gmail.com

unread,
Jan 14, 2015, 11:19:43 AM1/14/15
to duran...@googlegroups.com
Here's one way to do it, not sure if its the best.
In your view model, define a callback function to be called when the spinner updates:

var callbackOnUpdate = function (v) {
             alert(v);
}

then in the binding handler, check to see if that function exists, and if so call it.

update: function (element, valueAccessor, allBindings, vm) {

                 
                    var value = valueAccessor();
                    var valueUnwrapped = ko.unwrap(value);

                    // do stuff

                    if (vm.callbackOnUpdate) {
                        vm.callbackOnUpdate(valueUnwrapped);
                    }

                }

I don't like this approach because it hard codes the function name in the binding handler.
It would be nice to pass in a function as a parm so any vm can pass it whatever it wants as a callback.


Even better would be able to put the name of the callback in the binding declaration
<input class="spinner" data-bind="spinner: quantity, spinnerOptions: { min: 1, numberFormat: 'n' }, callbackFunction: callbackOnUpdate" />
Then you would be able to pick out the function from the allBindings parameter.
var cb = allBindings.get('
callbackFunction');
if (cb){
cb(
valueUnwrapped);
}

AT

unread,
Jan 16, 2015, 9:24:01 AM1/16/15
to duran...@googlegroups.com
Thank you very much for your reply. 

I went with your second suggestion in the end, because it is clean and can be reused again in the future. It also helped me understand the parameters passed in to the init and update functions. :)
Reply all
Reply to author
Forward
0 new messages