MVCoconut / coconut.data

Observable Data Layer.
The Unlicense
20 stars 8 forks source link

[RFC] Interim value container for editor UIs #74

Closed kevinresol closed 3 years ago

kevinresol commented 3 years ago

This is a very common problem which I think coconut should provide a standard solution for.

The problem

While user is inputting, there is often a interim value that is invalid, and we want to be able to provide feedback for that value. For example, a date input presented as a plain old html <input>.

and the interim values can be:

and for example for the first two cases we want to provide visual feedback to indicate that it is invalid (e.g. a red border).

At first thought one may naively tackle it with a controlled view like so:

class DateInput extends View {
  @:controlled var date:Date;

  function render() '<input value=${date.toString()} onchange=${e -> date = Date.fromString(e.src.value}/>';
}

This is not ideal as we cannot handle the interim invalid states (it will actually crash).

We can also use an uncontrolled view but that is not always desired.

The solution (RFC part)

We should introduce a general data structure to support such use cases:

class InterimValue<Raw, Result> implements Model {
    @:shared var raw:Raw;
    @:constant var parse:Raw->Outcome<Result, Error>;
    @:computed var parsed:Outcome<Result, Error> = parse(raw);

    public static function compound<Raw, Result>(source:Observable<Raw>, update:Raw->Void, parse:Raw->Outcome<Result, Error>):Value<Raw, Result>;
    public function transform<SubRaw, SubResult>(read:Raw->SubRaw, write:(Raw, SubRaw)->Raw, parse:SubRaw->Outcome<SubResult, Error>):Value<SubRaw, SubResult>;
}

The two functions are basically the same as the counterparts in State

and an example use case:

class App {
    static function main() {
        final interim = new InterimValue<String, Date>({raw: new State(''), parse: v -> Error.catchExceptions(Date.fromString.bind(v))})

        hxx('
            <>
                <input value=${interim.raw} onchange=${e -> interim.raw = e.src.value}/>
                <div>Raw: ${interim.raw}</div>
                <div>Parsed: ${Std.string(interim.parsed)}</div>
            </>
        ');
    }

}

Since we know the parsed result which is an outcome one can use it to prepare visual feedback as mentioned.