How to use ValueConverter with Custom Element

I created a Custom Element – a calendar view date picker that allows you to pick multiple non-contiguous days. I designed the Custom Element to work with a @bindable property of type Date[].

However, the data that I’m receiving from the server lists dates as a comma-separated list of string. (e.g. “02-03-2020,02-04-2020,…”)

So, I thought I’d use a value converter to try to convert the string into a Date[] and back. However, what I’ve tried isn’t working – the original string (in the view model) never gets updated when changes are made to the Date[] (in the custom element).

I’m a little confused as to how/when the “conversion” takes place and what that means in terms of what is actually being passed to and from my custom element.

Here is the basics of what I have:

value converter

import * as moment from "moment";

export class DateListValueConverter 
{
   public toView(value : string)
   {
       if(!value) return [];

       let temp = string.split(",");
       return temp.map(d => new Date(d));
   }

   public fromView(value : Date[])
   {
        if(!value) return "";

        let temp = value.map(d => moment(d).format("MM-DD-YYYY"));
        return temp.toString();
   }
}

date picker custom element

export class DatePickerCustomElement
{
    @bindable({defaultBindingMode: bindingMode.twoWay }) 
       public selectedDates : Date[] = [];
    ...

    public selectDateClick(date: DateItem, e: MouseEvent)
    {
        if(!e.ctrlKey)  //If user doesn't CTRL + Click
        {
             this.selectedDates = []; 
             //This push does not trigger value converter
             this.selectedDates.push(date.date);  
        }
       else
       {
            let index = this.selectedDates
                   .findIndex(d => moment(d).isSame(date.date, 'day'));
            if(index < 0)
                this.selectedDates.push(date.date);
            else
                //This splice does not trigger value converter
                this.selectedDates.splice(index,1);  
       }
}

view model

export class Example 
{
     public myDates : string = "02-03-2020,02-05-2020";
     ...
}

view

<date-picker selected-dates.bind="myDates | dateList"></date-picker>

If I do the following:

view model

export class Example
{
    public myDates : string = "02-03-2020,02-05-2020";
    public dateList : Date[] = [];
  
    activate()
    {
        this.dateList = this.myDates.split(",").map(d => new Date(d));
    }
}

view

<date-picker selected-dates.bind="dateList"></date-picker>

Then the data-binding actually works and the push and splice in the date-picker element actually cause an update to the property in the vm.

Is there a way to get this working with a value converter?

1 Like

My guess is your value converter won’t fire because you aren’t modifying the property it’s been bound to, you are pushing/splicing items in it.
Instead of pushing/splicing in your custom element, try assigning a whole new array to this.selectedDates and see if that helps.
Ideally, you should be able to ‘tell’ a value converter it’s being bound to a collection type, and it would then observe the collection for changes as well, but I don’t think that’s currently possible.

1 Like

For

<date-picker selected-dates.bind="myDates | dateList"></date-picker>

The issue is because it doesn’t observe the mutation of myDates by default. You can do this:

<date-picker selected-dates.bind="myDates | dateList :myDates.length"></date-picker>

ah yes, totally forgot about that hack :smiley:

1 Like

Nice! Thanks guys – @bigopon for the hack and @arnederuwe for the initial info. I had already changed the push and splice code to something like this (prior to seeing the hack):

let temp = this.selectedDates.slice(0) //make a copy of the current list
if(index < 0)
    temp.push(date.date);
else
   temp.splice(index,1);
this.selectedDates = temp.slice(0); //copy the list back
1 Like