Coder Social home page Coder Social logo

vinyl-ui's People

Contributors

mattstermiller avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

vinyl-ui's Issues

Support mutable properties

Overview

Currently, VinylUI does not support bindings for non-structural-equatable types. This is not part of its primary design goals, but it should be supported for working with C# types that do not override Equals and may not be open to modification.

Examples

Consider a POCO property type on a model:

type MyModel = {
    Address: Address // this is a C# class with plain properties and no overload for Equals
}

You can create bindings on Address, but they would only fire if the model property is given a different Address instance and never when the Address is mutated (the Address properties changed). This is because the Address type's Equals uses reference equality, which is not what we want for the purposes of binding.

To take this a step further, consider this scenario that Acadian actually has in our application:

type MyModel = {
    Addresses = Address list // list of C# POCOs
}

In this case, Addresses is an F# list which uses structural equality, so if a new list is given with a different set of Address instances, any bindings on this property are fired as expected. However, if the only change is one or more instances inside the list have been mutated, even if they are put into a new F# list instance, the binding is not fired.

Workarounds

For the singular property (first example), you have a few options, none of them particularly nice:

  • Override Equals on the type to manually implement structural equality
  • Use a record type instead
  • Copy the instance when bindings should be triggered

For the collection property, the above workarounds also apply, but there is another arguably simpler solution:

  • Use a non-structural-equatable collection type such as ResizeArray as the collection type. When an instance in the collection is modified and an event handler wants to trigger bindings on Addresses, it copies the instances into a new ResizeArray. This will always trigger bindings since it is a new reference.

Proposed Changes

Framework.start would create a list of the model type hierarchy's mutable properties, where a "mutable property" is a property that:

  • Has a public setter
  • Is a value type or a reference type without public setters

It would also create a list of "mutable sequence properties", defined as a property that:

  • Is a type implementing IEnumerable<T> (seq)
  • The contained type T has at least one mutable property as defined above

Consider the following Model type hierarchy:

type MyClass() =
    member val Id = 0 with get, set
    member val Name = "" with get, set

type SubModel = {
    Class: MyClass
}

type MyContainerClass() =
    member val Class = MyClass() with get, set

type MyModel = {
    Name: string
    Sub: SubModel
    Container: MyContainerClass
    Classes: MyClass list
    Values: int ResizeArray
}

The mutable property chains would be:

  • MyModel.Sub.Class.Id
  • MyModel.Sub.Class.Name
  • MyModel.Container.Class.Id
  • MyModel.Container.Class.Name

The only mutable sequence property would be MyModel.Classes. MyModel.Values would not be included - detecting changes in mutable collections will not be supported due to the complexity.

Note that MyModel.Container.Class would not be considered a mutable property in this context since its type is a reference type with public setters. The reason for excluding this is that differences in this property should be computed from its properties, not from the object itself.

Initialization

  • Create a "mutable property values" mapping of the mutable property chains to their current values.
  • Create a "mutable seq property values" mapping of each mutable sequence property to a list of each enumerated item and its property-value mapping (its type would be something like Map<PropertyChain, (obj * (PropertyInfo * obj) list) list>).

Diff

  • Model.diff will need to be changed to recurse into properties of types that have mutable properties, but unlike record types, still do the normal comparison and yielding of a change if unequal.
  • When comparing a mutable property, the mutable property values would be used for the previous model instead of using the mutated value.
  • When comparing a mutable seq property and it passes the normal equality check, each item in the new model's list would then be compared (by property values) for equality to the corresponding item in the mutable seq property values. If any changes are found in any property on any item, the mutable seq property would yield a change.

Change Processing

  • The mutable property values would be updated with the changes detected by the diff.
  • The mutable seq property values would be recomputed for the properties that changed.
  • Bindings to mutable properties would be triggered when there is a change to its parent property.

Optimize change detection in Framework

Currently, the change detection in Framework.fs first enumerates all values of all properties for both before and after model instances to find all changes. It then iterates over the changes, looking for a matching binding. This is wasteful since not all properties will be bound.

This enhancement would change the algorithm to only look for changes between the models for properties that are actually bound.

Support binding to nested model properties

The binding system currently only supports binding to properties directly on the model type. Support for nested properties would allow binding to properties of properties of the model type, to any depth. For instance:

type Name =
    First: string
    Middle: string
    Last: string

type Model =
    Name: Name

// ... in binding function:
Bind.view(<@ view.FirstNameBox.Text @>).toModel(<@ model.Name.First @>)

Off the top of my head, this doesn't seem like it would be too hard. The quotation processing will need to be changed to be recursive, the binding info types will need PropertyInfo list instead of just PropertyInfo, and the change detection in Framework will need to follow the properties recursively to get the value for comparison.

Add API for binding radio buttons

Binding radio buttons to a model property would be nice. Currently, the only way to bind radio buttons to discriminated union values for a model property is shown in the ShapeArea app: create events from the buttons' CheckedChanged events, if they are checked:

// Map the CheckedChanged events, when changing to checked, to the corresponding ShapeChanged event
form.RectangleButton.CheckedChanged
    |> Observable.filter (fun _ -> form.RectangleButton.Checked)
    |> Observable.mapTo (ShapeChanged Rectangle)
form.EllipseButton.CheckedChanged
    |> Observable.filter (fun _ -> form.EllipseButton.Checked)
    |> Observable.mapTo (ShapeChanged Ellipse)

Then create an event handler that sets the model property to the event argument. This only binds the view to the model. If model-to-view binding is also desired, a model-to-func binding needs to be written to check the appropriate radio button based on the value.

This is awkward and a lot boilerplate to achieve a simple thing conceptually. It would be great if we had a sensible API to achieve this for us. Not sure what it should look like though, since it involves multiple controls and all existing binding API involves only one control.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.