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 https://github.com/aspnet/Blazor/issues/409 event handlers https://github.com/aspnet/Blazor/issues/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.
This commit is contained in:
parent
05c166eafa
commit
15ba614e6f
|
|
@ -1,14 +1,15 @@
|
|||
@page "/counter"
|
||||
@using Microsoft.AspNetCore.Blazor
|
||||
<h1>Counter</h1>
|
||||
|
||||
<p>Current count: @currentCount</p>
|
||||
|
||||
<button @onclick(IncrementCount)>Click me</button>
|
||||
<button onclick="@IncrementCount">Click me</button>
|
||||
|
||||
@functions {
|
||||
int currentCount = 0;
|
||||
|
||||
void IncrementCount()
|
||||
void IncrementCount(UIMouseEventArgs e)
|
||||
{
|
||||
currentCount++;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using AngleSharp;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
|
||||
|
|
@ -13,6 +12,43 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
{
|
||||
internal static class BlazorDiagnosticFactory
|
||||
{
|
||||
public static readonly RazorDiagnosticDescriptor CodeBlockInAttribute =
|
||||
new RazorDiagnosticDescriptor(
|
||||
"BL9979",
|
||||
() =>
|
||||
"Code blocks delimited by '@{...}' like '@{{ {0} }}' for attributes are no longer supported " +
|
||||
"These features have been changed to use attribute syntax. " +
|
||||
"Use 'attr=\"@(x => {... }\"'.",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic Create_CodeBlockInAttribute(SourceSpan? source, string expression)
|
||||
{
|
||||
var diagnostic = RazorDiagnostic.Create(
|
||||
CodeBlockInAttribute,
|
||||
source ?? SourceSpan.Undefined,
|
||||
expression);
|
||||
return diagnostic;
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor ExpressionInAttributeList =
|
||||
new RazorDiagnosticDescriptor(
|
||||
"BL9980",
|
||||
() =>
|
||||
"Expressions like '{0}' inside of a tag must be part of an attribute. " +
|
||||
"Previous releases of Blazor supported constructs like '@onclick(...)' or '@bind(...)'." +
|
||||
"These features have been changed to use attribute syntax. " +
|
||||
"Use 'onclick=\"@...\"' or 'bind=\"...\" respectively.",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic Create_ExpressionInAttributeList(SourceSpan? source, string expression)
|
||||
{
|
||||
var diagnostic = RazorDiagnostic.Create(
|
||||
ExpressionInAttributeList,
|
||||
source ?? SourceSpan.Undefined,
|
||||
expression);
|
||||
return diagnostic;
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor UnexpectedClosingTag = new RazorDiagnosticDescriptor(
|
||||
"BL9981",
|
||||
() => "Unexpected closing tag '{0}' with no matching start tag.",
|
||||
|
|
|
|||
|
|
@ -5,14 +5,12 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using AngleSharp;
|
||||
using AngleSharp.Html;
|
||||
using AngleSharp.Parser.Html;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||
{
|
||||
|
|
@ -27,15 +25,11 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
= new HashSet<string>(
|
||||
new[] { "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr" },
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
private readonly static Regex bindExpressionRegex = new Regex(@"^bind\((.+)\)$");
|
||||
private readonly static CSharpParseOptions bindArgsParseOptions
|
||||
= CSharpParseOptions.Default.WithKind(CodeAnalysis.SourceCodeKind.Script);
|
||||
|
||||
private readonly ScopeStack _scopeStack = new ScopeStack();
|
||||
private string _unconsumedHtml;
|
||||
private List<IntermediateToken> _currentAttributeValues;
|
||||
private IDictionary<string, PendingAttribute> _currentElementAttributes = new Dictionary<string, PendingAttribute>();
|
||||
private IList<PendingAttributeToken> _currentElementAttributeTokens = new List<PendingAttributeToken>();
|
||||
private int _sourceSequence = 0;
|
||||
|
||||
private struct PendingAttribute
|
||||
|
|
@ -43,11 +37,6 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
public List<IntermediateToken> Values { get; set; }
|
||||
}
|
||||
|
||||
private struct PendingAttributeToken
|
||||
{
|
||||
public IntermediateToken AttributeValue;
|
||||
}
|
||||
|
||||
public override void WriteCSharpCode(CodeRenderingContext context, CSharpCodeIntermediateNode node)
|
||||
{
|
||||
if (context == null)
|
||||
|
|
@ -118,27 +107,26 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
throw new InvalidOperationException($"Invoked {nameof(WriteCSharpCodeAttributeValue)} while {nameof(_currentAttributeValues)} was null.");
|
||||
}
|
||||
|
||||
// For attributes like "onsomeevent=@{ /* some C# code */ }", we treat it as if you
|
||||
// wrote "onsomeevent=@(_ => { /* some C# code */ })" because then it works as an
|
||||
// event handler and is a reasonable syntax for that.
|
||||
var innerCSharp = (IntermediateToken)node.Children.Single();
|
||||
innerCSharp.Content = $"_ => {{ {innerCSharp.Content} }}";
|
||||
_currentAttributeValues.Add(innerCSharp);
|
||||
// We used to support syntaxes like <elem onsomeevent=@{ /* some C# code */ } /> but this is no longer the
|
||||
// case.
|
||||
//
|
||||
// We provide an error for this case just to be friendly.
|
||||
var content = string.Join("", node.Children.OfType<IntermediateToken>().Select(t => t.Content));
|
||||
context.Diagnostics.Add(BlazorDiagnosticFactory.Create_CodeBlockInAttribute(node.Source, content));
|
||||
return;
|
||||
}
|
||||
|
||||
public override void WriteCSharpExpression(CodeRenderingContext context, CSharpExpressionIntermediateNode node)
|
||||
{
|
||||
// To support syntax like <elem @completeAttributePair /> (which in turn supports syntax
|
||||
// like <elem @OnSomeEvent(Handler) />), check whether we are currently in the middle of
|
||||
// writing an element. If so, treat this C# expression as something that should evaluate
|
||||
// as a RenderTreeFrame of type Attribute.
|
||||
// We used to support syntaxes like <elem @completeAttributePair /> but this is no longer the case.
|
||||
// The APIs that a user would need to do this correctly aren't accessible outside of Blazor's core
|
||||
// anyway.
|
||||
//
|
||||
// We provide an error for this case just to be friendly.
|
||||
if (_unconsumedHtml != null)
|
||||
{
|
||||
var token = (IntermediateToken)node.Children.Single();
|
||||
_currentElementAttributeTokens.Add(new PendingAttributeToken
|
||||
{
|
||||
AttributeValue = token
|
||||
});
|
||||
var content = string.Join("", node.Children.OfType<IntermediateToken>().Select(t => t.Content));
|
||||
context.Diagnostics.Add(BlazorDiagnosticFactory.Create_ExpressionInAttributeList(node.Source, content));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -279,15 +267,6 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
_currentElementAttributes.Clear();
|
||||
}
|
||||
|
||||
if (_currentElementAttributeTokens.Count > 0)
|
||||
{
|
||||
foreach (var token in _currentElementAttributeTokens)
|
||||
{
|
||||
WriteElementAttributeToken(context, nextTag, token);
|
||||
}
|
||||
_currentElementAttributeTokens.Clear();
|
||||
}
|
||||
|
||||
_scopeStack.OpenScope( tagName: nextTag.Data, isComponent: false);
|
||||
}
|
||||
|
||||
|
|
@ -325,56 +304,6 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
}
|
||||
}
|
||||
|
||||
private void WriteElementAttributeToken(CodeRenderingContext context, HtmlTagToken tag, PendingAttributeToken token)
|
||||
{
|
||||
var bindMatch = bindExpressionRegex.Match(token.AttributeValue.Content);
|
||||
if (bindMatch.Success)
|
||||
{
|
||||
// TODO: Consider alternatives to the @bind syntax. The following is very strange.
|
||||
|
||||
// The @bind(X, Y, Z, ...) syntax is special. We convert it to a pair of attributes:
|
||||
// [1] value=@BindMethods.GetValue(X, Y, Z, ...)
|
||||
var valueParams = bindMatch.Groups[1].Value;
|
||||
WriteAttribute(context.CodeWriter, "value", new[]
|
||||
{
|
||||
new IntermediateToken
|
||||
{
|
||||
Kind = TokenKind.CSharp,
|
||||
Content = $"{BlazorApi.BindMethods.GetValue}({valueParams})"
|
||||
}
|
||||
});
|
||||
|
||||
// [2] @onchange(BindSetValue(parsed => { X = parsed; }, X, Y, Z, ...))
|
||||
var parsedArgs = CSharpSyntaxTree.ParseText(valueParams, bindArgsParseOptions);
|
||||
var parsedArgsSplit = parsedArgs.GetRoot().ChildNodes().Select(x => x.ToString()).ToList();
|
||||
if (parsedArgsSplit.Count > 0)
|
||||
{
|
||||
parsedArgsSplit.Insert(0, $"_parsedValue_ => {{ {parsedArgsSplit[0]} = _parsedValue_; }}");
|
||||
}
|
||||
var parsedArgsJoined = string.Join(", ", parsedArgsSplit);
|
||||
var onChangeAttributeToken = new PendingAttributeToken
|
||||
{
|
||||
AttributeValue = new IntermediateToken
|
||||
{
|
||||
Kind = TokenKind.CSharp,
|
||||
Content = $"onchange({BlazorApi.BindMethods.SetValue}({parsedArgsJoined}))"
|
||||
}
|
||||
};
|
||||
WriteElementAttributeToken(context, tag, onChangeAttributeToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
// For any other attribute token (e.g., @onclick(...)), treat it as an expression
|
||||
// that will evaluate as an attribute frame
|
||||
context.CodeWriter
|
||||
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{nameof(BlazorApi.RenderTreeBuilder.AddAttribute)}")
|
||||
.Write((_sourceSequence++).ToString())
|
||||
.WriteParameterSeparator()
|
||||
.Write(token.AttributeValue.Content)
|
||||
.WriteEndMethodInvocation();
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteUsingDirective(CodeRenderingContext context, UsingDirectiveIntermediateNode node)
|
||||
{
|
||||
context.CodeWriter.WriteUsing(node.Content, endLine: true);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ namespace Microsoft.AspNetCore.Blazor.Components
|
|||
[BindInputElement("text", null, "value", "onchange")]
|
||||
|
||||
[BindElement("select", null, "value", "onchange")]
|
||||
[BindElement("textarea", null, "value", "onchange")]
|
||||
public static class BindAttributes
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -178,50 +178,5 @@ namespace Microsoft.AspNetCore.Blazor.Components
|
|||
// at the end of every event callback.
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
// At present, if you have a .cshtml file in a project with <Project Sdk="Microsoft.NET.Sdk.Web">,
|
||||
// Visual Studio will run design-time builds for it, codegenning a class that attempts to override
|
||||
// this method. Therefore the virtual method must be defined, even though it won't be used at runtime,
|
||||
// because otherwise VS will display a design-time error in its 'Error List' pane.
|
||||
// TODO: Track down what triggers the design-time build for .cshtml files and how to stop it, then
|
||||
// this method can be removed.
|
||||
/// <summary>
|
||||
/// Not used. Do not invoke this method.
|
||||
/// </summary>
|
||||
/// <returns>Always throws an exception.</returns>
|
||||
public virtual Task ExecuteAsync()
|
||||
=> throw new NotImplementedException($"Blazor components do not implement {nameof(ExecuteAsync)}.");
|
||||
|
||||
/// <summary>
|
||||
/// Applies two-way data binding between the element and the property.
|
||||
/// </summary>
|
||||
/// <param name="value">The model property to be bound to the element.</param>
|
||||
protected RenderTreeFrame bind(object value)
|
||||
=> throw new NotImplementedException($"{nameof(bind)} is a compile-time symbol only and should not be invoked.");
|
||||
|
||||
/// <summary>
|
||||
/// Applies two-way data binding between the element and the property.
|
||||
/// </summary>
|
||||
/// <param name="value">The model property to be bound to the element.</param>
|
||||
protected RenderTreeFrame bind(DateTime value, string format)
|
||||
=> throw new NotImplementedException($"{nameof(bind)} is a compile-time symbol only and should not be invoked.");
|
||||
|
||||
/// <summary>
|
||||
/// Handles click events by invoking <paramref name="handler"/>.
|
||||
/// </summary>
|
||||
/// <param name="handler">The handler to be invoked when the event occurs.</param>
|
||||
/// <returns>A <see cref="RenderTreeFrame"/> that represents the event handler.</returns>
|
||||
protected RenderTreeFrame onclick(Action handler)
|
||||
// Note that the 'sequence' value is updated later when inserted into the tree
|
||||
=> RenderTreeFrame.Attribute(0, "onclick", handler != null ? (_ => handler()) : (UIEventHandler)null);
|
||||
|
||||
/// <summary>
|
||||
/// Handles change events by invoking <paramref name="handler"/>.
|
||||
/// </summary>
|
||||
/// <param name="handler">The handler to be invoked when the event occurs. The handler will receive the new value as a parameter.</param>
|
||||
/// <returns>A <see cref="RenderTreeFrame"/> that represents the event handler.</returns>
|
||||
protected RenderTreeFrame onchange(Action<object> handler)
|
||||
// Note that the 'sequence' value is updated later when inserted into the tree
|
||||
=> RenderTreeFrame.Attribute(0, "onchange", args => handler(((UIChangeEventArgs)args).Value));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,5 +47,45 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
Assert.Equal(20, item.Span.CharacterIndex);
|
||||
});
|
||||
}
|
||||
|
||||
// This is the old syntax used by @bind and @onclick, it's explicitly unsupported
|
||||
// and has its own diagnostic.
|
||||
[Fact]
|
||||
public void OldEventHandlerSyntax_ReportsError()
|
||||
{
|
||||
// Arrange/Act
|
||||
var generated = CompileToCSharp(@"
|
||||
<elem @foo(MyHandler) />
|
||||
@functions {
|
||||
void MyHandler()
|
||||
{
|
||||
}
|
||||
|
||||
string foo(Action action)
|
||||
{
|
||||
return action.ToString();
|
||||
}
|
||||
}");
|
||||
|
||||
// Assert
|
||||
var diagnostic = Assert.Single(generated.Diagnostics);
|
||||
Assert.Equal("BL9980", diagnostic.Id);
|
||||
}
|
||||
|
||||
// This used to be a sugar syntax for lambdas, but we don't support that anymore
|
||||
[Fact]
|
||||
public void OldCodeBlockAttributeSyntax_ReportsError()
|
||||
{
|
||||
// Arrange/Act
|
||||
var generated = CompileToCSharp(@"
|
||||
<elem attr=@{ DidInvokeCode = true; } />
|
||||
@functions {
|
||||
public bool DidInvokeCode { get; set; } = false;
|
||||
}");
|
||||
|
||||
// Assert
|
||||
var diagnostic = Assert.Single(generated.Diagnostics);
|
||||
Assert.Equal("BL9979", diagnostic.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -315,34 +315,6 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
frame => AssertFrame.Whitespace(frame, 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsAttributesWithCSharpCodeBlockValues()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(
|
||||
@"<elem attr=@{ DidInvokeCode = true; } />
|
||||
@functions {
|
||||
public bool DidInvokeCode { get; set; } = false;
|
||||
}");
|
||||
var didInvokeCodeProperty = component.GetType().GetProperty("DidInvokeCode");
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.False((bool)didInvokeCodeProperty.GetValue(component));
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Element(frame, "elem", 2, 0),
|
||||
frame =>
|
||||
{
|
||||
Assert.Equal(RenderTreeFrameType.Attribute, frame.FrameType);
|
||||
Assert.NotNull(frame.AttributeValue);
|
||||
Assert.Equal(1, frame.Sequence);
|
||||
|
||||
((UIEventHandler)frame.AttributeValue)(null);
|
||||
Assert.True((bool)didInvokeCodeProperty.GetValue(component));
|
||||
},
|
||||
frame => AssertFrame.Whitespace(frame, 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsUsingStatements()
|
||||
{
|
||||
|
|
@ -358,37 +330,6 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
frame => AssertFrame.Text(frame, typeof(List<string>).FullName, 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsAttributeFramesEvaluatedInline()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(
|
||||
@"<elem @onclick(MyHandler) />
|
||||
@functions {
|
||||
public bool DidInvokeCode { get; set; } = false;
|
||||
void MyHandler()
|
||||
{
|
||||
DidInvokeCode = true;
|
||||
}
|
||||
}");
|
||||
var didInvokeCodeProperty = component.GetType().GetProperty("DidInvokeCode");
|
||||
|
||||
// Assert
|
||||
Assert.False((bool)didInvokeCodeProperty.GetValue(component));
|
||||
Assert.Collection(GetRenderTree(component),
|
||||
frame => AssertFrame.Element(frame, "elem", 2, 0),
|
||||
frame =>
|
||||
{
|
||||
Assert.Equal(RenderTreeFrameType.Attribute, frame.FrameType);
|
||||
Assert.NotNull(frame.AttributeValue);
|
||||
Assert.Equal(1, frame.Sequence);
|
||||
|
||||
((UIEventHandler)frame.AttributeValue)(null);
|
||||
Assert.True((bool)didInvokeCodeProperty.GetValue(component));
|
||||
},
|
||||
frame => AssertFrame.Whitespace(frame, 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsTwoWayBindingForTextboxes()
|
||||
{
|
||||
|
|
@ -419,6 +360,36 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
frame => AssertFrame.Text(frame, "\n", 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsTwoWayBindingForTextareas()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(
|
||||
@"<textarea bind=""MyValue"" ></textarea>
|
||||
@functions {
|
||||
public string MyValue { get; set; } = ""Initial value"";
|
||||
}");
|
||||
var myValueProperty = component.GetType().GetProperty("MyValue");
|
||||
|
||||
// Assert
|
||||
var frames = GetRenderTree(component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Element(frame, "textarea", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "value", "Initial value", 1),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "onchange", 2);
|
||||
|
||||
// Trigger the change event to show it updates the property
|
||||
((UIEventHandler)frame.AttributeValue)(new UIChangeEventArgs
|
||||
{
|
||||
Value = "Modified value"
|
||||
});
|
||||
Assert.Equal("Modified value", myValueProperty.GetValue(component));
|
||||
},
|
||||
frame => AssertFrame.Text(frame, "\n", 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsTwoWayBindingForDateValues()
|
||||
{
|
||||
|
|
@ -534,7 +505,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
// Arrange
|
||||
var component = CompileToComponent(@"
|
||||
@using Microsoft.AspNetCore.Blazor
|
||||
<button onclick=""@OnClick"" @onclick(OnClick)/>
|
||||
<button onclick=""@OnClick"" />
|
||||
@functions {
|
||||
public void OnClick(UIMouseEventArgs e) { Clicked = true; }
|
||||
public bool Clicked { get; set; }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
@using System.Collections.Generic
|
||||
@using Microsoft.AspNetCore.Blazor
|
||||
Child components follow.
|
||||
<button class="addChild" @onclick(AddChild)>Add</button>
|
||||
<button class="removeChild" @onclick(RemoveChild)>Remove</button>
|
||||
<button class="addChild" onclick="@AddChild">Add</button>
|
||||
<button class="removeChild" onclick="@RemoveChild">Remove</button>
|
||||
|
||||
@foreach (var message in currentChildrenMessages)
|
||||
{
|
||||
|
|
@ -12,13 +13,13 @@ Child components follow.
|
|||
int numAdded = 0;
|
||||
List<string> currentChildrenMessages = new List<string>();
|
||||
|
||||
void AddChild()
|
||||
void AddChild(UIMouseEventArgs e)
|
||||
{
|
||||
numAdded++;
|
||||
currentChildrenMessages.Add($"Child {numAdded}");
|
||||
}
|
||||
|
||||
void RemoveChild()
|
||||
void RemoveChild(UIMouseEventArgs e)
|
||||
{
|
||||
if (currentChildrenMessages.Count > 0)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<h2>Textbox</h2>
|
||||
@using Microsoft.AspNetCore.Blazor
|
||||
<h2>Textbox</h2>
|
||||
<p>
|
||||
Initially blank:
|
||||
<input id="textbox-initially-blank" bind="textboxInitiallyBlankValue" />
|
||||
|
|
@ -13,12 +14,12 @@
|
|||
<h2>Text Area</h2>
|
||||
<p>
|
||||
Initially blank:
|
||||
<textarea id="textarea-initially-blank" @bind(textAreaIntiallyBlankValue)></textarea>
|
||||
<textarea id="textarea-initially-blank" bind="textAreaIntiallyBlankValue"></textarea>
|
||||
<span id="textarea-initially-blank-value">@textAreaIntiallyBlankValue</span>
|
||||
</p>
|
||||
<p>
|
||||
Initially populated:
|
||||
<textarea id="textarea-initially-populated" @bind(textAreaIntiallyPopulatedValue)></textarea>
|
||||
<textarea id="textarea-initially-populated" bind="textAreaIntiallyPopulatedValue"></textarea>
|
||||
<span id="textarea-initially-populated-value">@textAreaIntiallyPopulatedValue</span>
|
||||
</p>
|
||||
|
||||
|
|
@ -46,7 +47,7 @@
|
|||
}
|
||||
</select>
|
||||
<span id="select-box-value">@selectValue</span>
|
||||
<button id="select-box-add-option" @onclick(AddAndSelectNewSelectOption)>Add and select new item</button>
|
||||
<button id="select-box-add-option" onclick="@AddAndSelectNewSelectOption">Add and select new item</button>
|
||||
</p>
|
||||
|
||||
@functions {
|
||||
|
|
@ -63,7 +64,7 @@
|
|||
enum SelectableValue { First, Second, Third, Fourth }
|
||||
SelectableValue selectValue = SelectableValue.Second;
|
||||
|
||||
void AddAndSelectNewSelectOption()
|
||||
void AddAndSelectNewSelectOption(UIMouseEventArgs e)
|
||||
{
|
||||
includeFourthOption = true;
|
||||
selectValue = SelectableValue.Fourth;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
<h1>Counter</h1>
|
||||
@using Microsoft.AspNetCore.Blazor
|
||||
<h1>Counter</h1>
|
||||
<p>Current count: @currentCount</p>
|
||||
<p><button @onclick(handleClicks ? IncrementCount : (Action)null)>Click me</button></p>
|
||||
<p><button onclick="@((handleClicks ? (Action<UIMouseEventArgs>)IncrementCount : null))">Click me</button></p>
|
||||
|
||||
<label>
|
||||
<input type="checkbox" @bind(handleClicks) />
|
||||
<input type="checkbox" bind="@handleClicks" />
|
||||
Toggle click handler registration
|
||||
</label>
|
||||
|
||||
|
|
@ -11,7 +12,7 @@
|
|||
int currentCount = 0;
|
||||
bool handleClicks = true;
|
||||
|
||||
void IncrementCount()
|
||||
void IncrementCount(UIMouseEventArgs e)
|
||||
{
|
||||
currentCount++;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
<h1>Counter</h1>
|
||||
@using Microsoft.AspNetCore.Blazor
|
||||
<h1>Counter</h1>
|
||||
|
||||
<!-- Note: passing 'Message' parameter with lowercase name to show it's case insensitive -->
|
||||
<p>Current count: <MessageComponent message=@currentCount.ToString() /></p>
|
||||
|
||||
<button @onclick(IncrementCount)>Click me</button>
|
||||
<button onclick="@IncrementCount">Click me</button>
|
||||
|
||||
@functions {
|
||||
int currentCount = 0;
|
||||
|
||||
void IncrementCount()
|
||||
void IncrementCount(UIMouseEventArgs e)
|
||||
{
|
||||
currentCount++;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
@addTagHelper *, TestContentPackage
|
||||
@using Microsoft.AspNetCore.Blazor
|
||||
@using TestContentPackage
|
||||
|
||||
<h1>Functionality and content from an external package</h1>
|
||||
|
|
@ -11,7 +12,7 @@
|
|||
|
||||
<p>Click the following button to invoke a JavaScript function.</p>
|
||||
|
||||
<button @onclick(ShowJavaScriptPrompt)>Show JavaScript prompt</button>
|
||||
<button onclick="@ShowJavaScriptPrompt">Show JavaScript prompt</button>
|
||||
|
||||
@if (!string.IsNullOrEmpty(result))
|
||||
{
|
||||
|
|
@ -31,7 +32,7 @@
|
|||
{
|
||||
string result;
|
||||
|
||||
void ShowJavaScriptPrompt()
|
||||
void ShowJavaScriptPrompt(UIMouseEventArgs e)
|
||||
{
|
||||
result = MyPrompt.Show("Hello!");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
@inject System.Net.Http.HttpClient Http
|
||||
@using Microsoft.AspNetCore.Blazor
|
||||
@inject System.Net.Http.HttpClient Http
|
||||
|
||||
<h1>Cookie counter</h1>
|
||||
<p>The server increments the count by one on each request.</p>
|
||||
<p>TestServer base URL: <input @bind(testServerBaseUrl) /></p>
|
||||
<button id="delete" @onclick(DeleteCookie)>Delete cookie</button>
|
||||
<button id="increment" @onclick(GetAndIncrementCounter)>Get and increment current value</button>
|
||||
<p>TestServer base URL: <input bind="@testServerBaseUrl" /></p>
|
||||
<button id="delete" onclick="@DeleteCookie">Delete cookie</button>
|
||||
<button id="increment" onclick="@GetAndIncrementCounter">Get and increment current value</button>
|
||||
|
||||
@if (!requestInProgress)
|
||||
{
|
||||
|
|
@ -17,13 +18,13 @@
|
|||
string testServerBaseUrl;
|
||||
string responseText;
|
||||
|
||||
async void DeleteCookie()
|
||||
async void DeleteCookie(UIMouseEventArgs e)
|
||||
{
|
||||
await DoRequest("api/cookie/reset");
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
async void GetAndIncrementCounter()
|
||||
async void GetAndIncrementCounter(UIMouseEventArgs e)
|
||||
{
|
||||
await DoRequest("api/cookie/increment");
|
||||
StateHasChanged();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
@using System.Net
|
||||
@using System.Net.Http
|
||||
@using Microsoft.AspNetCore.Blazor
|
||||
@using Microsoft.AspNetCore.Blazor.Browser.Http
|
||||
@inject HttpClient Http
|
||||
|
||||
|
|
@ -7,12 +8,12 @@
|
|||
|
||||
<p>
|
||||
<div>URI:</div>
|
||||
<input id="request-uri" value=@uri @onchange(value => { uri = (string)value; }) size="60"/>
|
||||
<input id="request-uri" bind="@uri" size="60"/>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<div>Method:</div>
|
||||
<select id="request-method" value=@method @onchange(value => { method = (string)value; })>
|
||||
<select id="request-method" bind="@method">
|
||||
<option value="GET">GET</option>
|
||||
<option value="POST">POST</option>
|
||||
<option value="PUT">PUT</option>
|
||||
|
|
@ -22,7 +23,7 @@
|
|||
|
||||
<p>
|
||||
<div>Request body:</div>
|
||||
<textarea id="request-body" value=@requestBody @onchange(value => { requestBody = (string)value; })></textarea>
|
||||
<textarea id="request-body" bind="@requestBody"></textarea>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
|
@ -30,20 +31,20 @@
|
|||
@foreach (var header in requestHeaders)
|
||||
{
|
||||
<div class="header-entry">
|
||||
Name: <input value=@header.Name @onchange(value => { header.Name = (string)value; }) />
|
||||
Value: <input value=@header.Value @onchange(value => { header.Value = (string)value; }) />
|
||||
[<a href="#" @onclick(() => RemoveHeader(header))>remove</a>]
|
||||
Name: <input bind="@header.Name" />
|
||||
Value: <input bind="@header.Value" />
|
||||
[<a href="#" onclick="@(e => RemoveHeader(header))">remove</a>]
|
||||
</div>
|
||||
}
|
||||
<button id="add-header" @onclick(AddHeader)>Add</button>
|
||||
<button id="add-header" onclick="@AddHeader">Add</button>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<div>Request referrer:</div>
|
||||
<input id="request-referrer" type="text" value=@requestReferrer @onchange(value => { requestReferrer = (string)value; }) />
|
||||
<input id="request-referrer" type="text" bind=@requestReferrer />
|
||||
</p>
|
||||
|
||||
<button id="send-request" @onclick(DoRequest)>Request</button>
|
||||
<button id="send-request" onclick="@DoRequest">Request</button>
|
||||
|
||||
@if (responseStatusCode.HasValue)
|
||||
{
|
||||
|
|
@ -71,7 +72,7 @@
|
|||
string responseBody;
|
||||
string responseHeaders;
|
||||
|
||||
async void DoRequest()
|
||||
async void DoRequest(UIMouseEventArgs e)
|
||||
{
|
||||
responseStatusCode = null;
|
||||
|
||||
|
|
@ -124,7 +125,7 @@
|
|||
StateHasChanged();
|
||||
}
|
||||
|
||||
void AddHeader()
|
||||
void AddHeader(UIMouseEventArgs e)
|
||||
=> requestHeaders.Add(new RequestHeader());
|
||||
|
||||
void RemoveHeader(RequestHeader header)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
<div class="special-style">
|
||||
@using Microsoft.AspNetCore.Blazor
|
||||
<div class="special-style">
|
||||
This component, including the CSS and image required to produce its
|
||||
elegant styling, is in an external NuGet package.
|
||||
<button @onclick(ChangeLabel)>@buttonLabel </button>
|
||||
<button onclick="@ChangeLabel">@buttonLabel </button>
|
||||
</div>
|
||||
|
||||
@functions
|
||||
{
|
||||
@functions {
|
||||
string buttonLabel = "Click me";
|
||||
|
||||
void ChangeLabel()
|
||||
void ChangeLabel(UIMouseEventArgs e)
|
||||
{
|
||||
buttonLabel = "It works";
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue