Saturday, August 20, 2016

Mapping default value when using the datetimepicker with knockout

If you are using the bootstrap datetimepicker from http://eonasdan.github.io/bootstrap-datetimepicker/Installing/ with knockout and facing issues for mapping the default value this post may help you.

Below is the code I was using for the custom binding.

ko.bindingHandlers.datepicker = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        //initialize datepicker with some optional options
        var options = {
            format:'DD/MM/YYYY hh:mm A',
            defaultDate : valueAccessor()(),
            sideBySide:true
        };
        if (allBindingsAccessor() !== undefined) {
            if (allBindingsAccessor().datepickerOptions !== undefined) {
                options.format = allBindingsAccessor().datepickerOptions.format !== undefined ? allBindingsAccessor().datepickerOptions.format : options.format;
            }
        }
        $(element).datetimepicker(options);
        //when a user changes the date, update the view model
        ko.utils.registerEventHandler(element, "dp.change", function (event) {
            var value = valueAccessor();
            if (ko.isObservable(value)) {
                value(event.date);
            }
        });
        var defaultVal = $(element).val();
        var value = valueAccessor();
        value(moment(defaultVal, options.format));
    },
    update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var thisFormat = 'DD/MM/YYYY hh:mm A';

        if (allBindingsAccessor() !== undefined) {
            if (allBindingsAccessor().datepickerOptions !== undefined) {
                thisFormat = allBindingsAccessor().datepickerOptions.format !== undefined ? allBindingsAccessor().datepickerOptions.format : thisFormat;
            }
        }

        var value = valueAccessor();
        var unwrapped = ko.utils.unwrapObservable(value());

        if (unwrapped === undefined || unwrapped === null) {
            element.value = new moment(new Date());
        } else {
            element.value = unwrapped.format(thisFormat);
        }
    }
};

When initiating the model I was passing the values to match the format I have set, but the datetimepicker was returning NaN exception when the date value is more than 12 and then I got to know option parameter format passed when initializing the datetimepicker was only used for formatting the input text and when reading from the input box the defaultDate option should have a valid date which is understandable by moment.js. So for resolving the issue the other option I found is when initializing the observable can pass the moment object and moment object can be initialized with the any given format when format is passed. So here below is the code to initialize the observable which is used with the datetimepicker binding.

 function TestModel(data){
  self.test_date = ko.observable(moment().add('1','week'));
    if(data.test_date != undefined){
        isNumeric = /^(\d+)$/;
        //if data comes from mySQL then use unix timestamp
        //or if data comes from posted value when reloading use custom format
        if(isNumeric.test(data.test_date )){
            self.whencantake_date(moment.unix(data.test_date )); 
        } else{
            self.whencantake_date(moment(data.test_date ,'DD/MM/YYYY hh:mm A'));     
        }
    }

Hope this helped you.

Knockout observable array options binding issue with AJAX loading

I had an issue where when I loaded all the element using jQuery AJAX synchronous option the binding worked perfectly but it didn't work when I loaded the array using asynchronously. Here is my finding in case if any one else come up with the same issue.

    function ViewModel()
    {
        var self = this;
        self.AllSubjects = ko.observableArray();
        $.ajax({
            url: all_subject_url,
            type: 'post',
            data: '',
            async: false,
            success: function (result) {
                var response = JSON.parse(result);
                if (response.status == 'success')
                {
                    var mapped = ko.mapping.fromJS(response);
                    if (mapped.Subjects()) {
                        self.AllSubjects = mapped.Subjects;
                    }
                }
            }
        });

        <select class="form-control" name="subject" data-bind="options:$root.AllSubjects,optionsText:'name',optionsCaption:'Select Subject',optionsValue:'id',value:subject">
        </select>

The above code work fine when loading values synchronusly, but won't work if you remove the async=false option in the $.ajax option. The issue with one the AJAX data is loaded we are replacing the AllSubjects observable array with new observable array. The correct way to map it as below.

self.AllSubjects(mapped.Subjects());