--- sidebar_position: 6 title: Component code --- Here is the code of our main component, it is he who will call our basic item and perform a large part of the actions. ## Creation of the view ```cshtml title="Components/Crafting.razor"
Available items:
@foreach (var item in Items) { }
Recipe
Result
Actions
``` ## Code creation We use an `ObservableCollection` to allow an action on a change in the list with the `CollectionChanged` event. ```csharp title="Components/Crafting.razor.cs" public partial class Crafting { private Item _recipeResult; public Crafting() { Actions = new ObservableCollection(); Actions.CollectionChanged += OnActionsCollectionChanged; this.RecipeItems = new List { null, null, null, null, null, null, null, null, null }; } public ObservableCollection Actions { get; set; } public Item CurrentDragItem { get; set; } [Parameter] public List Items { get; set; } public List RecipeItems { get; set; } public Item RecipeResult { get => this._recipeResult; set { if (this._recipeResult == value) { return; } this._recipeResult = value; this.StateHasChanged(); } } [Parameter] public List Recipes { get; set; } /// /// Gets or sets the java script runtime. /// [Inject] internal IJSRuntime JavaScriptRuntime { get; set; } public void CheckRecipe() { RecipeResult = null; // Get the current model var currentModel = string.Join("|", this.RecipeItems.Select(s => s != null ? s.Name : string.Empty)); this.Actions.Add(new CraftingAction { Action = $"Items : {currentModel}" }); foreach (var craftingRecipe in Recipes) { // Get the recipe model var recipeModel = string.Join("|", craftingRecipe.Have.SelectMany(s => s)); this.Actions.Add(new CraftingAction { Action = $"Recipe model : {recipeModel}" }); if (currentModel == recipeModel) { RecipeResult = craftingRecipe.Give; } } } private void OnActionsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { JavaScriptRuntime.InvokeVoidAsync("Crafting.AddActions", e.NewItems); } } ``` As for our element we create a CSS file for our component. ```css title="Components/Crafting.razor.css" .css-grid { grid-template-columns: repeat(4,minmax(0,1fr)); gap: 10px; display: grid; width: 286px; } .css-recipe { grid-template-columns: repeat(3,minmax(0,1fr)); gap: 10px; display: grid; width: 212px; } .actions { border: 1px solid black; height: 250px; overflow: scroll; } ``` A JavaScript class is also created to interact with the page. ```js title="Components/Crafting.razor.js" window.Crafting = { AddActions: function (data) { data.forEach(element => { var div = document.createElement('div'); div.innerHTML = 'Action: ' + element.action + ' - Index: ' + element.index; if (element.item) { div.innerHTML += ' - Item Name: ' + element.item.name; } document.getElementById('actions').appendChild(div); }); } } ``` ## Concept: CascadingValue & CascadingParameter In our component we need to know our parent when we are in an item in order to call the `CheckRecipe()` methods as well as to add actions or to know the element we are moving. We therefore use in the view of our component `Crafting.razor` the following code: ```html title="Crafting.razor" ... ... ``` This code makes it possible to make our component available in all the sub-components found in the `CascadingValue` tag. To retrieve our `CascadingValue` we must use in our component a `CascadingParameter` as follows: ```csharp [CascadingParameter] public Crafting Parent { get; set; } ``` It would be possible to go through `Parameter` but in case of multiple levels, it would be necessary in each level to add our `Parameter` to nest them: ```cshtml title="Components/MyRootComponent.razor" @code { [Parameter] public RenderFragment ChildContent { get; set; } [Parameter] public string Text { get; set; } }
MyRootComponent - @Text
@ChildContent
``` ```cshtml title="Components/MyFirstChildComponent.razor" @code { [Parameter] public RenderFragment ChildContent { get; set; } [Parameter] public MyRootComponent RootComponent { get; set; } }
MyFirstChildComponent - @RootComponent.Text
@ChildContent
``` ```cshtml title="Components/MySecondChildComponent.razor" @code { [Parameter] public RenderFragment ChildContent { get; set; } [Parameter] public MyRootComponent RootComponent { get; set; } }
MySecondChildComponent - @RootComponent.Text
@ChildContent
``` ```cshtml title="Pages/Index.razor" @code { MyRootComponent MyRootComponent; }
MySecondChildComponent - Content
``` This also forces the developer to declare the component's usage variable each time it is used, as well as having to specify an attribute with its value. Whereas with `CascadingValue` & `CascadingParameter` the code becomes simpler: ```cshtml title="Components/MyRootComponent.razor" @code { [Parameter] public RenderFragment ChildContent { get; set; } [Parameter] public string Text { get; set; } }
MyRootComponent - @Text
@ChildContent
``` ```cshtml title="Components/MyFirstChildComponent.razor" @code { [Parameter] public RenderFragment ChildContent { get; set; } [CascadingParameter] public MyRootComponent RootComponent { get; set; } }
MyFirstChildComponent - @RootComponent.Text
@ChildContent
``` ```cshtml title="Components/MySecondChildComponent.razor" @code { [Parameter] public RenderFragment ChildContent { get; set; } [CascadingParameter] public MyRootComponent RootComponent { get; set; } }
MySecondChildComponent - @RootComponent.Text
@ChildContent
``` ```cshtml title="Pages/Index.razor"
MySecondChildComponent - Content
``` ## Concept: IJSRuntime To call JS from .net, inject the `IJSRuntime` abstraction and call one of the following methods: * IJSRuntime.InvokeAsync * JSRuntimeExtensions.InvokeAsync * JSRuntimeExtensions.InvokeVoidAsync For previous .NET methods that call JS functions: * The function identifier ( `String` ) is relative to the global scope ( `window` ). To call `window.someScope.someFunction`, the identifier is `someScope.someFunction`. There is no need to register the function before it is called. * Pass any number of on-Serializable JS arguments in `Object[]` to a JS function. * The cancellation token ( `CancellationToken` ) propagates a notification that operations should be canceled. * `TimeSpan` represents a time limit for a JS operation. * The `TValue` return type must also be serializable. `TValue` should be the .NET type that best matches the JS returned type. * `JS A Promise` is returned for `InvokeAsync` methods. `InvokeAsync` unwraps `Promise` and returns the value expected by `Promise`. For apps that have Blazor Server pre-render enabled, invoking JS is not possible during the initial pre-render. Interop JS calls should be deferred until the connection with the browser is established. The following example is based on `TextDecoder`, a JS-based decoder. The sample demonstrates how to call a JS function from a C# method that offloads a specification from developer code to an existing JS API. The JS function accepts a byte array from a C# method, decodes the array, and returns the text to the component for display. Add the following JS code inside the closing `` tag in the `wwwroot/index.html` file: ```html ``` The following `CallJsExample1` component: * Calls the JS function `convertArray` with `InvokeAsync` when selecting a button ( `Convert Array` ). * Once the JS function is called, the passed array is converted to a string. The string is returned to the component for display ( `text` ). ```cshtml title="Pages/CallJsExample1.razor" @page "/call-js-example-1" @inject IJSRuntime JS

Call JS convertArray Function

@text

Serenity
David Krumholtz on IMDB

@code { private MarkupString text; private uint[] quoteArray = new uint[] { 60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32, 116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97, 108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110, 105, 118, 101, 114, 115, 101, 10, 10, }; private async Task ConvertArray() { text = new(await JS.InvokeAsync("convertArray", quoteArray)); } } ``` ### Call JavaScript functions without reading a returned value ( `InvokeVoidAsync` ) Use `InvokeVoidAsync` in the following cases: * .NET is not required to read the result of a JS call. * JS functions return `void (0)`/`void 0` or undefined. Inside the closing `` tag of `wwwroot/index.html`, provide a JS function `displayTickerAlert1`. The function is called with `InvokeVoidAsync` and does not return a value: ```html ``` `TickerChanged` calls the `handleTickerChanged1` method in the following component `CallJsExample2`. ```cshtml @page "/call-js-example-2" @inject IJSRuntime JS

Call JS Example 2

@if (stockSymbol is not null) {

@stockSymbol price: @price.ToString("c")

} @code { private Random r = new(); private string? stockSymbol; private decimal price; private async Task SetStock() { stockSymbol = $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}"; price = r.Next(1, 101); await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price); } } ``` ### Calling JavaScript functions and reading a returned value ( `InvokeAsync` ) Use `InvokeAsync` when .net needs to read the result of a JS call. Inside the closing `` tag of `wwwroot/index.html`, provide a JS function `displayTickerAlert2`. The following example returns a string to be displayed by the caller: ```html ``` `TickerChanged` calls the `handleTickerChanged2` method and displays the returned string in the following component `CallJsExample3`. ```cshtml title="Pages/CallJsExample3.razor" @page "/call-js-example-3" @inject IJSRuntime JS

Call JS Example 3

@if (stockSymbol is not null) {

@stockSymbol price: @price.ToString("c")

} @if (result is not null) {

@result

} @code { private Random r = new(); private string? stockSymbol; private decimal price; private string? result; private async Task SetStock() { stockSymbol = $"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}"; price = r.Next(1, 101); var interopResult = await JS.InvokeAsync("displayTickerAlert2", stockSymbol, price); result = $"Result of TickerChanged call for {stockSymbol} at " + $"{price.ToString("c")}: {interopResult}"; } } ```