New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'bind-...' for two-way binding #409

Closed
rynowak opened this Issue Mar 28, 2018 · 16 comments

Comments

Projects
@rynowak
Member

rynowak commented Mar 28, 2018

Summary

We're adding a feature that replaces @bind(...) with something more first class for tooling.

Introducing bind-... for two-way binding:

<input bind="@CurrentValue" />
@functions {
    public string CurrentValue { get; set; }
}

bind-... attribute implements two-way binding to components and dom elements via a tag helper with special compile-time behavior. This means that intellisense, colorization, and completion know about bind-... and can provide contextual documentation and contextual completion.

image

note: the correct date format is actually yyyy-MM-dd. Do as I say, not as I do 😆

What is two-way binding?

If you're not familiar with @bind (the thing being upgraded), this does two-way binding to both components and dom elements. This is like a macro, it doesn't do anything you couldn't do normally, but it should help you out 👍

Example:

<input bind="@CurrentValue" />
@functions {
    public string CurrentValue { get; set; }
}

Generates something like:

<input value="@CurrentValue" onchange="@((__value) => CurrentValue = __value)/>
@functions {
    public string CurrentValue { get; set; }
}

This means than when the component first runs, the value of the input element will come from the CurrentValue property. When the user types in the textbox, the CurrentValue property will be set to the changed value. This is why we call it two-way binding.

In reality the code generation is a little more complex because bind deals with a few cases of type conversions. But in principle, bind-... will associate the current value of an expression with a value attribute, and will handle changes via a change handler attribute. We expect to invest more in the runtime support in this area in the future, including better conversion, error handler, etc.

One additional point, the expression provided to bind-... should be an LValue, meaning that it's something that can be assigned. Since this is code-generation, it works in places that the C# ref keyword doesn't work.

React can do something similar: https://reactjs.org/docs/two-way-binding-helpers.html

Use cases

DOM elements

The most obvious usage of bind-... is for input elements of various types. You're going to have lots of these 😆

That looks like this:

<input bind="@CurrentValue" />
@functions {
    public string CurrentValue { get; set; }
}

Or this:

<input type="checkbox" bind="@IsSelected" />
@functions {
    public bool IsSelected { get; set; }
}

For cases like this, we have a set of mappings between the structure of an input tag and the attributes that we need to set of the generated dom elements. These mappings are driven by attributes defined in code and are extensible.

Right now this set is minimal, but we plan to provide a good set of mappings out of the box for completion to lead you down the right path.

Format strings

You can also provide a format string - currently only for DateTime values.

<input type="date" bind="@StartDate" format="yyyy-MM-dd" />
@functions {
    public string StartDate { get; set; }
}

The format string will be used to convert to and from .NET values to the DOM attributes. We plan to enhance this area in the future, currently it's restricted to DateTime. If you use this with an expression that isn't a DateTime, expect some fireworks 🎆

Components

bind-... can also enhance components by recognizing component attributes that follow a specific pattern.

That looks like:

@* in Counter.cshtml *@
<div>...html omitted for brevity...</div>
@functions {
    public int Value { get; set; } = 1;
    public Action<int> ValueChanged { get; set; }
}

@* in another file *@
<Counter bind-Value="@CurrentValue" />
@functions {
    public int CurrentValue { get; set; }
}

The code generation for components is a little plainer. Since you're calling into .NET code from .NET code, we expect the types to match.

Note that the component author doesn't have to do anything special to enable this, we detect the bindables based on the names and types of the component properties.

Bind, user-defined

If you have a use for it, you can define your own mappings for bind-... and do things like this:

// in BindAttributes.cs
[BindElement("ul", "foo", "myvalue", "myevent")]
public class BindAttributes
{
}

@* in MyComponent.cshtml *@
<ul bind-foo="@SomeExpression" />

Bind, general case

bind-... also supports a fallback case that totally flexible. You can specify bind with any value attribute name and change handler attribute name.

<ul bind-myvalue-myevent="@SomeExpression" />

This will bind the current value of @SomeExpression to myvalue and a change handler lambda to myevent.

Next steps

The first change here introduces the general concept of bind as a language feature rather than a function call. I think that this works well for the cases that we have tested with @bind, but we need to do more work in the runtime to make it really complete.

This includes:

  • support conditional attributes
  • extend the set of bind-... mappings we provide by default
  • improve support for conversions
  • improve error handling for conversion failures

One of the next things we do will be to give the same treatment to @onclick and extend the set of default mappings we provide for event handlers.

There's also a master list of improvements to the programming model for components described here: #1

That list doesn't have much detail is only a general outline.

@schotime

This comment has been minimized.

schotime commented Mar 28, 2018

We should also ensure that using a single state store like the FlightFinder demo does works just as well.

If you auto bind to a property then another component depends on that property to render, it does not get re-rendered because you haven't fired the StateHasChanged() event. And thats pretty hard to do without getting into ugly stuff like the below.

<input class="toggle" type="checkbox" @bind(completed) />
@functions
{
    bool completed
    {
        get { return state.Todos[Id].Completed; }
        set { state.Todos[Id].Completed = value; StateHasChanged(); }
    }
}
@darilek

This comment has been minimized.

darilek commented Mar 28, 2018

IMHO these custom attributes (bind, format, converter, ...) should be prefixed to distinguish them from the standard HTML attributes

@muqeet-khan

This comment has been minimized.

muqeet-khan commented Mar 28, 2018

@rynowak I am trying to understand if there is a way for bind to provide ''bindable" data to the compnents ChildContent?

Think of this scenario, for example:

@* Consumer is sending raw data to the component for "processing" *@
<MyStockExample bind-rawstockdata="@rawStockData" bind-processedstockdata="@processedData">
    <textarea rows="5" >
           @processedData
    </textarea>
</MyStockExample>


@functions{
    public ProcessedStockData processedData { get; set; }
    public RawStockData rawStockData { get; set; }
}

Wherein, MyStockExample is taking in RawStockData and then "processing" it and providing it as a bindable object to its child content as (ProcessedStockData) so that the consumer can then use that data without having to depend on the component author.

The question is would the above be possible with this change? If no, is this approach not the right one in context of Blazor components?

@rynowak

This comment has been minimized.

Member

rynowak commented Mar 28, 2018

@muqeet-khan - you don't need bind for that case because it's not two-way, and not changing.

We plan to address this scenario also in the future.

@danroth27

This comment has been minimized.

Member

danroth27 commented Mar 28, 2018

@danroth27 danroth27 added this to the 0.2.0 milestone Mar 28, 2018

@danroth27 danroth27 added this to Triage in Blazor via automation Mar 28, 2018

@awulkan

This comment has been minimized.

awulkan commented Mar 28, 2018

If this requires manual work to update the DOM after a programmatic change to the property then it's not two-way binding imo. It will confuse a lot of devs coming over from other frameworks. It will also make the code harder to maintain in many cases.

All definitions I can find of two-way binding specify an immediate automatic update of both the rendered output and the stored value upon change, in both directions.

Or are there plans to also implement an auto-propagating two-way binding alternative?

@bdparrish

This comment has been minimized.

Contributor

bdparrish commented Mar 29, 2018

<input value="@CurrentValue" onchange="@((__value) => CurrentValue = __value)/>

The code should be moved to JavaScript or the @functions section. Leaving in the HTML will make it difficult to debug in the Devloper Tools. It also clutters the HTML.

@jonathanperis

This comment has been minimized.

jonathanperis commented Mar 29, 2018

Why not asp-bind like aspnet core existing tag helpers for consistency reasons?

@tdinucci

This comment has been minimized.

tdinucci commented Mar 29, 2018

I personally prefer this new "bind-" syntax to the @Bind previously. However is there even a need for the "bind-*" syntax?

I personally think Polymer has a very nice model for bindings.

To me the following transformation doesn't feel intuitive - what does "bind" bind to?. Is it "value", or "id", or "type", etc

<input bind="@CurrentValue" />

to

<input value="@CurrentValue" onchange="@((__value) => CurrentValue = __value)/>

If something like the Polymer syntax were used though then the binding is unambiguous:

<input value="{{CurrentValue}}"/>

I also wonder if things like string formatting (from top post):

<input type="date" bind="@StartDate" format="yyyy-MM-dd" />

Could be achieved with the string interpolation syntax. With the syntax below I don't have to learn anything Blazor specific, I can just use my existing HTML and C# knowledge:

<input type="date" value="@StartDate:yyyy-MM-dd"/>

To take things a little further, the binding syntax could be augmented to signify one/two way bindings. Polymer syntax is:

One-way:

<input value="[[CurrentValue]]"/>

Two-way:

<input value="{{CurrentValue}}"/>
I'm in no way suggesting that you do exactly the same thing as Polymer, I just think there syntax has a lot of nice aspects.

@danroth27 danroth27 moved this from Triage to In progress in Blazor Mar 29, 2018

@rynowak

This comment has been minimized.

Member

rynowak commented Mar 29, 2018

The code should be moved to JavaScript or the @functions section. Leaving in the HTML will make it difficult to debug in the Devloper Tools. It also clutters the HTML.

@bdparrish - this is an example of the what the generated code looks like, it's not something we should suggest anyone write.

@bdparrish

This comment has been minimized.

Contributor

bdparrish commented Mar 29, 2018

@rynowak

I understand that. I know that the code is simple but code needs to be separated from the presentation.

Also if the generated code looks like that, then it makes it difficult to debug those parts of the HTML.

@kaagati

This comment has been minimized.

kaagati commented Mar 29, 2018

Aurelia binding syntax is very intutive, why cant blazor use similar.

http://aurelia.io/docs/binding/basics

@iAmBipinPaul

This comment has been minimized.

iAmBipinPaul commented Mar 30, 2018

Auerlia data binding syntax is more descriptive and describes it's purpose.

image

http://aurelia.io/docs/binding/basics#html-and-svg-attributes

@rynowak

This comment has been minimized.

Member

rynowak commented Mar 31, 2018

Closing this issue as this feature was introduced with 4407de1

Thanks for the feedback and discussion on this topic. We chose this style because it feels very Razor and we're already working with something established (server-side Razor).

@rynowak rynowak closed this Mar 31, 2018

Blazor automation moved this from In progress to Done Mar 31, 2018

@rynowak rynowak added 3 - Done and removed 2 - Working labels Mar 31, 2018

rynowak added a commit that referenced this issue Apr 10, 2018

Remove old workaround @OnClick and @Bind
This change removes support for the old syntax used for event handlers
and two-way binding.

See the relevant issues for details on the new features and
improvements:

bind #409
event handlers #503

Along with this change we've removed a few additional things Blazor
could do that aren't part of Razor's usual syntax.

----

The features that was used to make something like:
```
<button @OnClick(...) />
```

is an expression that's embedded in a an element's attribute. This
feature might be useful in the future if we want to support 'splatting'
arbitrary attributes into a tag, but the runtime support for this isn't
accessible outside the Blazor core.

----

The features that implement:
```
<button onclick=@{ } />
```

have been removed in favor of a better design for lambdas, method group
conversions and other things for event handler attributes.

use `<button onclick=@(x => ...} />` instead.

We think is a better approach in general, because we want the app
developer to write and see the parameter list.

----

Both syntactic features that have been removed have dedicated error
messages in the compiler. If you're porting old code it should help you
figure out what to do.

rynowak added a commit that referenced this issue Apr 10, 2018

Remove old workaround @OnClick and @Bind
This change removes support for the old syntax used for event handlers
and two-way binding.

See the relevant issues for details on the new features and
improvements:

bind #409
event handlers #503

Along with this change we've removed a few additional things Blazor
could do that aren't part of Razor's usual syntax.

----

The features that was used to make something like:
```
<button @OnClick(...) />
```

is an expression that's embedded in a an element's attribute. This
feature might be useful in the future if we want to support 'splatting'
arbitrary attributes into a tag, but the runtime support for this isn't
accessible outside the Blazor core.

----

The features that implement:
```
<button onclick=@{ } />
```

have been removed in favor of a better design for lambdas, method group
conversions and other things for event handler attributes.

use `<button onclick=@(x => ...} />` instead.

We think is a better approach in general, because we want the app
developer to write and see the parameter list.

----

Both syntactic features that have been removed have dedicated error
messages in the compiler. If you're porting old code it should help you
figure out what to do.

rynowak added a commit that referenced this issue Apr 10, 2018

Remove old workaround @OnClick and @Bind
This change removes support for the old syntax used for event handlers
and two-way binding.

See the relevant issues for details on the new features and
improvements:

bind #409
event handlers #503

Along with this change we've removed a few additional things Blazor
could do that aren't part of Razor's usual syntax.

----

The features that was used to make something like:
```
<button @OnClick(...) />
```

is an expression that's embedded in a an element's attribute. This
feature might be useful in the future if we want to support 'splatting'
arbitrary attributes into a tag, but the runtime support for this isn't
accessible outside the Blazor core.

----

The features that implement:
```
<button onclick=@{ } />
```

have been removed in favor of a better design for lambdas, method group
conversions and other things for event handler attributes.

use `<button onclick=@(x => ...} />` instead.

We think is a better approach in general, because we want the app
developer to write and see the parameter list.

----

Both syntactic features that have been removed have dedicated error
messages in the compiler. If you're porting old code it should help you
figure out what to do.

rynowak added a commit that referenced this issue Apr 10, 2018

Remove old workaround @OnClick and @Bind
This change removes support for the old syntax used for event handlers
and two-way binding.

See the relevant issues for details on the new features and
improvements:

bind #409
event handlers #503

Along with this change we've removed a few additional things Blazor
could do that aren't part of Razor's usual syntax.

----

The features that was used to make something like:
```
<button @OnClick(...) />
```

is an expression that's embedded in a an element's attribute. This
feature might be useful in the future if we want to support 'splatting'
arbitrary attributes into a tag, but the runtime support for this isn't
accessible outside the Blazor core.

----

The features that implement:
```
<button onclick=@{ } />
```

have been removed in favor of a better design for lambdas, method group
conversions and other things for event handler attributes.

use `<button onclick=@(x => ...} />` instead.

We think is a better approach in general, because we want the app
developer to write and see the parameter list.

----

Both syntactic features that have been removed have dedicated error
messages in the compiler. If you're porting old code it should help you
figure out what to do.
@estomagordo

This comment has been minimized.

estomagordo commented Jun 10, 2018

Excuse a silly question, but what is the role of the Action in the Counter example? What exactly is required to bind to values in a component?

@Andrzej-W

This comment has been minimized.

Andrzej-W commented Jun 10, 2018

Without Action you have only one way data binding. Also take into account that information in this post is a little outdated. Please read this in the doc:
https://blazor.net/docs/components/index.html#component-parameters
and example you are looking for is below Components attributes subheading.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment