Search code examples
htmlknockout.jstypescript2.0knockout-2.0knockout-3.0

UI is not updated by updating observable from the custom binding method update


I'm working on a knockout application in which there is a drop-down for selecting a day of a month, it's values changes as per the month selection (ex: May- 1 to 31, Nov- 1 to 30), I successfully rendered UI, but my problem is the selected value (this.dayValue) is not showing as updated on UI when I tried to update from the update of the knockout binding handler. I've given the sample code below, please let me know. I guess it is a problem of rebinding even though I'm not sure. please help. Thanks in advance.

HTML:

<script type="text/html" id="person-template">
    <select data-bind="options: months, value: monthValue, event:{change: $data.noOfDays}></select>
    **<select data-bind="options: days, value: dayValue, event:{change: $data.dateModifiy}></select>**
    <select data-bind="options: years, value: yearValue, event:{change: $data.dateModifiy}></select>
</script>

Typescript:

export class MyView{
  private dayValue: Observable<string>;
   constructor(opt: Iclass){
     this.dayValue = opt.current().getDate().toString();
  }
  private noOfDays(): void {
    let temp: string[] = [];
    let month: number = this.months().indexOf(this.monthValue());
    let noOfDays: number = new Date(this.yearValue(), month + 1, 0).getDate();
    for (let i: number = 1; i <= noOfDays; i++) {
      temp.push(i.toString());
    }
    this.days(temp);
  }
}

ko.bindingHandlers.MyDate = {
    init(element: HTMLElement, valueAccessor: any, allBindingsAccessor: any, viewModel: any, bindingContext: any): any {
    let options: Iclass = ko.utils.unwrapObservable(valueAccessor()) || {};
    let myView: Myview = new MyView(options);

    ko.cleanNode(element);
    ko.applyBindingsToNode(element, {
        template: {
            name: "person-template",
            data: myView
        }
    });

    return { controlsDescendantBindings: true };
},

update(element: HTMLElement, valueAccessor: any, allBindingsAccessor: any, viewModel: any, bindingContext: any): any {
    let options: Iclass = ko.utils.unwrapObservable(valueAccessor()) || {};
    let myView: Myview = new MyView(options);
}

Sample Code:

<div data-bind="MyDate:{ name: 'sample', current: newDate}"></div>

Solution

  • Your question is a bit unclear. However, I guess you're trying to achieve that whenever the selected month changes, the array of selectable days gets updated.

    While I don't exactly see your concrete issue, I see kind of a design issue, which may be the root cause of having issues.

    In my opinion, such a logic should be implemented at the viewmodel level (in your case your MyView class), as it's not related to the view, but purely to the model. In other words, to handle changes of an observable data, you should not wire it through the view using things like change events, but directly handle the observable's notifications.

    I would implement something like this in the MyView constructor.

    export class MyView{
      private dayValue: Observable<string>;
    
      constructor(opt: Iclass) {
         this.dayValue = opt.current().getDate().toString();
    
         // this will create an on-the-fly computed, which will gather all the observables you read inside, and run whenever each changes (like the selected month)
         ko.computed(() => {
           // something like updateSelectableDays() would be a better name
           this.noOfDays();
         });
      }
    }
    

    This way you can omit all the change events, and pass the state maintenance responsibility to the viewmodel. Note, that basically this is a key point of the so-called MVVM pattern.

    Hope this helps, and solves your issues.