Issue giving up focus on control - c#

I am running into an odd issue using Blazor Webassembly. I have a component that has 2 text boxes. For some reason, if I type anything into the first text box, then try to either tab to the second text box or click in the second text box, I have to do it twice. It's like the first tab or click doesn't register. After playing around with it, I was able to minimally duplicate the issue. The issue seems to happen when I have both a model binding on the control and I use a #ref attribute.
Here is my .cshtml file:
#inject IJSRuntime JsRuntime
#page "/"
<div>
<input class="form-control" id="first-text-box" type="text" #ref="TextBoxControl" #bind="_model.Box1" />
</div>
<div>
<input class="form-control" id="second-text-box" type="text" #bind="_model.Box2" />
</div>
#code {
private ElementReference TextBoxControl { get; set; }
private readonly BoxClass _model = new BoxClass();
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await JsRuntime.InvokeVoidAsync("jsFunctions.focusElement", TextBoxControl);
await base.OnAfterRenderAsync(firstRender);
}
}
Here is the focusElement javascript function:
window.jsFunctions = {
focusElement: function(element) {
element.focus();
}
};
Finally, here is a Fiddle of the issue to see it happen live: https://blazorfiddle.com/s/kph6msiv
Like I said, just type something into the first text box, then try to tab or click to the next text box and you'll see that you'll need to do it twice.

You are focusing every time it renders, which happens when Blazor data binds, which happens when you exit the control.
Instead, only call your JavsScript if firstRender is true

Related

Blazor - How to focus on the first invalid field in EditForm

I have a form in Blazor which utilized form validation, as described in the documentation.
When the user tries to submit the form, and it fails validation, I'd like to focus the user on the first invalid <input> field.
How can this be done in Blazor WebAssemby?
You could use OnInvalidSubmit and access the EditContext.
<EditForm #ref=editForm OnInvalidSubmit=#FocusFirstError ...
...
<InputText #bind-Value=#inputRegisteredUser.GivenName
#ref=inputGivenName
class="form-control"
placeholder="Given Name"
id="GivenName" />
...
private EditForm editForm;
// repeat for all inputs
private InputText inputGivenName;
private async Task FocusFirstError()
{
var editContext = editForm.EditContext;
// repeat for all inputs
if(editContext.GetValidationMessages(() => inputRegisteredUser.GivenName).Any())
{
await inputGivenName.Element.Value.FocusAsync();
return;
}
}

Display data (fetched from DB) into InputTextarea Blazor

I am trying to display some data fetched from database into a Textarea of my Razor page.
I have tried out something like this. Is this the correct way of displaying the data for view only?
Razor page code snippet:
#if(summaryProjection != null)
{
<div> Seller Data</div>
<textarea>
#summaryProjection.tradeSummary.articleCurrency
</textarea>
}
cs side code:
public Models.SummaryProjection summaryProjection = new Models.SummaryProjection();
protected override async Task OnInitializedAsync()
{
string Id = TID;
summaryProjection = await Http.GetFromJsonAsync<Models.SummaryProjection>($"api/T/summary?Id=" + Id);
}
If it's a block of text you want to display then textarea is as good a solution as any other (with some css formatting to make it pretty). You will need to add disabled to prevent editing. In your example you appear to want to display a currency value. for that it's not the best - just use a suitably styled input, again with disabled set.
<textarea disabled>
#summaryProjection.tradeSummary.articleCurrency
</textarea>

Rendering sequence in blazor component

I have two RenderFragments in blazor component. One is MainContent another one is AuxilaryContent i.e. I place the AuxilaryContent at first followed by MainContent. As of now, Auxilary content rendered succeeded by MainContent because as I placed AuxilaryContent at first.
But my requirement is that I need to render MainContent first, based upon rendering of MainContent, I may render AuxilaryContent or not. But in DOM, AuxilaryContent always lies before MainContent.
Is this possible?
If I am using bool in MainContent, then by using the bool to trigger SecondaryContent means, it requires another StateHasChanged(). It involves unwanted re-rendering of components.
#page "/check"
#AuxilaryContent
#MainContent
#code {
RenderFragment MainContent => (builder) =>
{
//It must be rendered first
};
RenderFragment AuxilaryContent => (builder) =>
{
//It should rendered after MainContent rendering. But in DOM, it always lies before MainContent
};
}
Suppose you have the 2 components, main and aux.
The second aux can be shown or not by the result of the main one ,thats what i understand.
First of all Blazor is a spa (single-page application) it means that everything is in fact in the same page i mean,suppose you start by the index of the page , it contains all the components of the blazor proyect.
The components inside can contain or not others.
For example, suppose a collection of books:
The index page will be more less like that:
#page "/"
<book1_component></book1_component>
<book2_component></book2_component>
<book3_component></book3_component>
Inside of each component you can put another component if u want.
<h1>book1</h1>
<label>title</label> <input type="text" />
<label>author</label> <input type="text" />
<other_component> </other_component>
#code{
//methods
}
The interesting thing about blazor is that you should work with it as you have State-Patron:
You can check here what its a state patron:
https://refactoring.guru/es/design-patterns/state
What i mean is if the state changes, then the behavior of blazor will change too.
Example: Here you can see that if the state of the index component change , there will be one or other components or even none.
#page "/"
<input type="int" max="4" min="1" #bind="state" />
#if( state == 1 )<book1_component></book1_component>
#if( state == 2 )<book2_component></book2_component>
#if( state == 3 )<book3_component></book3_component>
#if(i>4) <label> no books!</label>
#code{
int state;
}
So in your example you should do more less the same , for example, if a condition is true then you show, your component.
So if you want to refresh some component with statehaschange, you can do it in its own .razor page .
#page "/"
<InputCheckbox #bind="state" />
#if( state) <aux_component></aux_component>
else Not loading component.
#code{
bool state;
//operations to change the state
}
Other way ,can be : If you want to keep the logic on only one component, you can use the lifecycle methods:
https://learn.microsoft.com/es-es/aspnet/core/blazor/components/lifecycle?view=aspnetcore-5.0
So if you want to execute some code first put onInitiallize, then go to the next lifecycle method and execute second block by a condition, something like that.
#page "/check"
#AuxilaryContent
#MainContent
#code {
protected override void OnInitiallize(){
//executing the first
RenderFragment MainContent => (builder) =>
{
//It must be rendered first
};
}
protected override void OnParametersSet(){
//main component rendered
RenderFragment AuxilaryContent => (builder) =>
{
//It should rendered after MainContent rendering. But in DOM, it always lies before MainContent
};
}
}
Hope it helps.
I would consider restructuring this component into two nested components: main container controlling the conditional rendering of the aux one.

C# StateHasChanged() in a razor component does not force <video> tag to load/change the mp4 playing, only using javascript seems to work

I am experimenting with server-side blazor. I am trying to have multiple buttons set/change the .mp4 file playing in a tag in a razor component. The only way I have found to make this work is calling a javascript function via IJSRuntime:InvokeVoidAsync() from the OnParametersSet() in my razor component. The javascript function is located in _Host.cshtml. This seems like a rather ugly solution to what should be a simple problem.
I have tried using StateHasChanged() in the OnClick button functions. The h1-header tag and source src="NewFile" are shown to update when I look at the html render in chrome, and the h1 tag correctly changes on the page when a button is clicked, but the new video is not loaded. My guess is this is tied to the video playing on its own thread, or the video tag itself not changing. I just don't understand how to get this done it from razor/c#.
Because of build errors, the Javascript code was added to File: _Html.cshtml
<script>
function loadVideo (strNewVideo)
{
document.getElementById('videoSourceId').src = strNewVideo;
document.getElementById("videoTagId").load();
}
</script>
Simple component to play videos...
File: VideoPlayer.razor
#inject IJSRuntime theJavaScriptEngine;
<div class="align-content-center">
<h1>#this.m_strRenderMe</h1>
<video id="videoTagId" autoplay width="1080" height="720">
<source id="videoSourceId" src=#this.m_strRenderMe type="video/mp4" />
</video>
</div>
#code {
ElementReference theVideoTag;
[Parameter]
public string strVideoFilePath { get; set; }
private string m_strRenderMe;
protected override void OnParametersSet()
{
this.m_strRenderMe = this.strVideoFilePath;
theJavaScriptEngine.InvokeVoidAsync("loadVideo", this.m_strRenderMe);
this.StateHasChanged();
}
}
Razor Page with component from above and 4 buttons isw in
File: Counter.razor
#page "/counter"
#using UseBlazorToReadPowerPoint.Classes
#inject CPersistantAppState thePersistantAppState
<VideoPlayer strVideoFilePath=#thePersistantAppState.m_strVideoPath></VideoPlayer>
<button class="btn btn-primary" #onclick="PlayVideo_1">Video 1</button>
<button class="btn btn-primary" #onclick="PlayVideo_2">Video 2</button>
<button class="btn btn-primary" #onclick="PlayVideo_3">Video 3</button>
<button class="btn btn-primary" #onclick="PlayVideo_4">Video 4</button>
#code {
void PlayVideo_1()
{
thePersistantAppState.m_strVideoPath = "videos/Video-1.mp4";
}
void PlayVideo_2()
{
thePersistantAppState.m_strVideoPath = "videos/Video-2.mp4";
}
void PlayVideo_3()
{
thePersistantAppState.m_strVideoPath = "videos/Video-3.mp4";
}
void PlayVideo_4()
{
thePersistantAppState.m_strVideoPath = "videos/Video-4.mp4";
}
}
To persist the filename selected.
File: CPersistantAppState.cs
namespace UseBlazorToReadPowerPoint.Classes
{
public class CPersistantAppState
{
public string m_strVideoPath;
}
}
The listed code works. I just cannot figure out how to make this work without the javascript call. Seems like there has to be a cleaner way of doing this.
Issue is not with StateHasChangeg(), it is about html's Video tag:
This method is generally only useful when you've made dynamic changes to the set of sources available for the media element, either by changing the element's src attribute or by adding or removing elements nested within the media element itself. load() will reset the element and rescan the available sources, thereby causing the changes to take effect.
It means that is mandatory invoke load after change src attribute. You can't invoke load from blazor at this time, it means you should to invoke it via IJSRuntime:
Blazor code
<div class="align-content-center">
<h1>#m_strRenderMe[currentVideo]</h1>
<button #onclick="ChangeVideo">Change video</button>
<video id="videoTagId" autoplay width="1080" height="720">
<source id="videoSourceId" src="#m_strRenderMe[currentVideo]" type="video/mp4" />
</video>
</div>
#code
{
int currentVideo = 0;
string[] m_strRenderMe = new string[] {
"https://.../videoplayback-1.mp4",
"https://.../videoplayback-2.mp4"
};
protected void ChangeVideo()
{
currentVideo = (currentVideo + 1) % 2;
theJavaScriptEngine.InvokeVoidAsync("loadVideo");
}
}
JS code
<script>
function loadVideo ()
{
document.getElementById("videoTagId").load();
}
</script>
Check it out at BlazorFiddle.

MVC - Causing server-side page validation to fire on button click, using an AJAX call

On an app I'm working on, I have a requirement that, upon clicking a certain button, validation fires on some related textboxes; if they do not pass, nothing occurs, otherwise an action that is fired from an AJAX call occurs. This AJAX call returns some message about the success of the operation.
My partial view this is occurring in looks a bit like this:
<div>
<div id='cell-phone'>
#Model.ValidationMessageFor(x=>x.Model.CellNumber)
#Model.TextBoxFor(x=>x.Model.CellNumber)
</div>
<div id='pager'>
<!-- Ditto, only x.Model.Pager; yes, some people use pagers still. -->
</div>
<div id='page-address'>
<!-- ... x.Model.PageAddress ... -->
</div>
<div id='pager-test'>
<a href='#' id='test-pager' class='button'>Test</a>
<span id='test-result'></span>
</div>
</div>
<script>
var $cellNum = $('#cell-phone input'),
$pagerNum = $('#pager input'),
$pageAddress = $('#page-address input'),
$testPager = $('#pager-test'),
$testResult = $('#test-result');
$(document).ready(function () {
$testPager.click(function () {
pagerTest();
});
});
function pagerTest() {
var args = { address: $pageAddress.val() };
$.getJSON('#Url.Action("SendTestPage")', args, function(result) {
$testResult.html(result.Message);
});
}
</script>
...down at the server level...
public JsonResult SendTestPage(string address)
{
// SNIP: Other unnecessary details.
var result = new EmailSendResult
{
success = SendEmailMethod(address)
};
result.message = result.success
? "Message sent!"
: "Couldn't send message...";
return result;
}
....
public class EmailSendResult
{
public bool success;
public string message;
}
Question: while I am able to get the message/success values back, I also need to cause the View Model's validations to fire. I don't see how to do this using an AJAX call. My suspicion is that either A) I'm using the wrong tool for the job, or B) I'm using the right tool for one job, but I need something else. What am I missing to be able to cause validations to fire?
When you click on 'test-pager' link, the action will be called but the validation of your form doesn't trigger because your link is not a submit. If you want to validation work you must have a submit button on the form. When the user clicks it the validation will fire. So change the test-pager to something like this:
<input type="submit" id="test-pager" class="button" />
Or ( if I understand question correctly) you can bind address textbox change event and within it call testPage function.

Categories

Resources