gvas / knockout-jqueryui

Knockout bindings for the jQuery UI widgets.
http://gvas.github.com/knockout-jqueryui/
MIT License
103 stars 38 forks source link

datepicker doesn't update when observable changes #8

Closed DenisPitcher closed 11 years ago

DenisPitcher commented 11 years ago

Hello,

I'm experimenting with your library however I can't seem to get the datepicker to update when the observable changes. How do I specifically bind the datepicker to update?

I've put together a jsfiddle example to illustrate what I mean. http://jsfiddle.net/EZD3k/

If you select a date, the span and both inputs correctly represent it. However, if you go to the "change my observable" input and change the date in it, the input corresponding to the datepicker isn't updated.

How do you bind the datepicker such that it correctly updates when the observable changes?

Thanks for your help as well as your excellent library,

Denis

gvas commented 11 years ago

Hello,

you should update the input via knockout's built-in 'value' binding: http://jsfiddle.net/EZD3k/1/

The datepicker widget keeps the selected date and the input's value in sync, and knockout's 'value' binding keeps the input's value and the viewmodel's observable in sync.

Regards, Gábor

DenisPitcher commented 11 years ago

Hi Gábor,

Thank you for your quick response. The value binding works when you have an input but doesn't work when you have a div. I've been trying to get a calendar inline with a secondary popout calendar that shows the next 6 months but thought it'd be best to use the example you placed on your website.

Here's an updated jsfiddle with the div which doesn't work. http://jsfiddle.net/EZD3k/3/

Köszönöm szépen,

Denis

gvas commented 11 years ago

I see.

I've managed to create a working fiddle, but it's a hack. The viewmodel shouldn't know about the datepicker's id.

Anyway, here is the link, I hope it helps http://jsfiddle.net/EZD3k/5/

Üdvözlettel, Gábor

DenisPitcher commented 11 years ago

Szia Gábor,

I'm not sure if there's a different way around ensuring it updates. I haven't touched javascript in years so I'm basically learning it all over again and lots has changed.

I've been experimenting with alternatives to knowing the datepicker's id and would like your thoughts on what I've come up with.

My thought is to expose the widget elements to the viewmodel. At the moment, you store the widget instance in the widget observable.

                    // store the widget instance in the widget observable
                    if (ko.isWriteableObservable(value.widget)) {
                        value.widget($(element)[widgetName]('widget'));
                    }

I added an additional function to store the element in the viewmodel.

                    // store the widget instance in the viewmodel's widget observable
                    if (ko.isWriteableObservable(viewModel.widgets)) {
                        viewModel.widgets.push($(element));
                    } 

Thus, my viewmodel has an observeable array var widgets = ko.observableArray();

Which then allows me to reference the elements and call the setDate method

var onSelectedChanged = function(newValue) {
    //var $datepicker = $('#dp');
    //$datepicker.datepicker('setDate', newValue);

    ko.utils.arrayForEach(widgets(), function (widget) {
        var upwrappedWidget = ko.utils.unwrapObservable(widget)[0];
        var $datepicker = $(upwrappedWidget);
        $datepicker.datepicker('setDate', newValue);
    });

};

Is there a better way to do it? Perhaps a means to extend your library to allow a binding to be added to a function in the viewmodel to the setDate method itself?

I'm still exploring but would welcome your thoughts.

Köszönöm szépen,

Denis

gvas commented 11 years ago

It seems that the datepicker's widget() method returns a reference to a helper div instead of the element the datepicker is instantiated on.

So basically you suggest to initialize the bindings' widget option with $(element) instead of $(element)[widgetName]('widget') (I don't think that adding a new 'widgets' option to the bindings is necessary, let's fix the 'widget' option instead).

This fix seems to be OK to me, but I have to check that this also works for the other bindings.

Thanks Gábor

DenisPitcher commented 11 years ago

Hi Gábor,

I was rather confused as to why the widget returned a different div so it's helpful to know that you think it's a bug.

I'm wondering if there may be a better approach than what I've suggested regarding exposing the widget. I'm presently investigating whether it would be possible to expose the methods for each widget similarly to how you've exposed the options and events. I think it'd be great to be able to specify a method binding in the options for a control so that you could wire up methods for any of the widgets but I'm still trying to figure out if it is possible.

I'll let you know if I come up with anything.

Thanks for your help and quick responses,

Denis

gvas commented 11 years ago

The widget option's new implementation works with all of the bindings. I've included it in the new release. Here is the updated jsfiddle: http://jsfiddle.net/EZD3k/7/

Best regards, Gábor

DenisPitcher commented 11 years ago

Hi Gábor,

Thanks for this, I think it'll help me accomplish what I'm aiming to do.

Just as an FYI, I've been working on it over the weekend to see what I can figure out. I've found it rather useful as a means to get up to speed with javascript and knockout. I had been pursuing the idea that perhaps it would be possible to bind the function to an observable.

for example, say you have a datepicker defined as such.

You map setDate to an observable of setDate on the viewmodel

var setDate = ko.observable();

which can then be called, for example in a method subscribed to the onSelectedChanged function of a secondary calendar.

var onSelectedChanged = function(newValue) {
    var dateSetter = ko.utils.unwrapObservable(setDate);
    dateSetter(newValue);
};.

Here is how I've bound the function to the viewmodel thus far, but it only works with a setDate function name. I've tried binding the multi month calendar to its own value but am not having any luck figuring out how to setup the binding.

// bind the widget events to the viewmodel unwrappedEvents = unwrapProperties(widgetEvents); $.each(unwrappedEvents, function (key, value) { unwrappedEvents[key] = value.bind(viewModel); });

                    var func;
                    for (func in unwrappedFunctions) {

                        if(ko.isWriteableObservable(viewModel[func])) {
                            //this needs to support multiple parameters, how?
                            var $func = function (param) {
                                $(element)[widgetName](func, param);
                            }
                            viewModel[func]($func);
                        }
                    }

How are events setup and bound to a function? I haven't seen any samples in your documentation so I'm rather confused how the unwrapProperties function returns a value you can bind to. I think if that were possible I could get it working the way I envisioned.

Thanks,

Denis

gvas commented 11 years ago

The unwrapProperties() function simply unwraps the passed in object's observable properties. But you won't need it, use some code like this instead (didn't test it):

// options.methods is an array of the names of the widget's methods, defined in the binding handlers'
// config (src/accordion.js, src/autocomplete.js, etc)
ko.utils.arrayForEach(options.methods, function(methodName) {
    if (ko.isWriteableObservable(value[methodName]) {
        var func = function() {
            // convert this function's arguments into an array
            var args = Array.prototype.slice.call(arguments);
            // jQuery UI expects the method name as the first argument
            args.unshift(methodName);
            // invoke the widget method
            return $(element)[widgetName].apply($element[widgetName], args);
        };
        // write the function into the view model's observable
        value[methodName](func);
    })
});

That being said I don't see much gain from exposing the widgets' methods as bindable options. I think that the 'widget' option is enough, but I'm open to discussion.

juuxstar commented 11 years ago

Hello gents,

as it so happens, I've had to add a datePicker control to my app and upon seeing this discussion I decided to write up a value binding for the date picker control. Simply, use like the native 'value' binding and it reads and writes a Date object to the bound property.

/**
 * Binds a view model field with the date picker's date value.
 * The bound field should be a Date object.
 * Used with the JQueryUI DatePicker widget.
 */
ko.bindingHandlers.datepickerValue = {
    init : function(element, valueAccessor, allBindingsAccessor) {
        $(element).datepicker('option', 'onSelect', function(newValue) {
            var value = $(element).datepicker('getDate');
            ko.expressionRewriting.writeValueToProperty(valueAccessor(), allBindingsAccessor, 'datepickerValue', value);
        });
    },
    update : function(element, valueAccessor) {
        $(element).datepicker('setDate', ko.utils.unwrapObservable(valueAccessor()));
    }
};

I've updated your fiddle: http://jsfiddle.net/EZD3k/9/ Works both on input elements and divs and unlike the native value binding accounts for the dateFormat option for the DatePicker widget.

The only caveat is that this binding must come AFTER the regular datepicker binding so that the date picker is setup by the time this binding executes.

Enjoy.

Tomas

gvas commented 11 years ago

@juuxstar: Cool, I'm going to add a new 'value' option to the datepicker binding based on your code.

Thanks Gábor

juuxstar commented 11 years ago

We could probably close this issue off.