I am trying to import this Javascript function into my Blazor application. The function of the script is simple, add the class c-show into an existing list <li> element that already has two classes. The original javascript in its completion is:
Javascript
document.addEventListener('DOMContentLoaded', (event) => {
document.querySelectorAll('.c-sidebar-nav-item.c-sidebar-nav-dropdown').forEach(dropMenu => {
dropMenu.addEventListener('click', () => dropMenu.classList.toggle('c-show'));
});
})
This script affects this element:
<li class="c-sidebar-nav-item c-sidebar-nav-dropdown">
Adding the c-show making it:
<li class="c-sidebar-nav-item c-sidebar-nav-dropdown">
I am trying to achieve the same thing with Blazor/C# via Interop.
So I have added the following to my element:
<li class="c-sidebar-nav-item c-sidebar-nav-dropdown" #onclick="dropMenu">
And my code section being:
#code {
public async void dropMenu()
{
classList.toggle('c-show')
}
}
But I am not entirely sure on how get the result I am working for since all I know is that I need to adjust the javascript somehow but not sure on how.
You can use JSInterop call from the OnAfterRenderAsync method to initialize your JavaScript object, this can be done only once, and then you can call your JavaScript methods each time the component is rendered.
Note: You need to inject the JSRuntime object in order to execute JSInterop calls.
#page "/"
#inject IJSRuntime jsRuntime
<li id="myid" #ref=MyElementReference class="c-sidebar-nav-item c-sidebar-
nav-dropdown" #onclick="dropMenu">
#code{
// This add an element reference to the li element, which you can pass to
// your JavaScript functions
ElementReference MyElementReference;
// You have to call your JavaScript code after your components have been
// rendered. The OnAfterRenderAsync method is called after the component
// has been rendered, and thus you can put code here to initialize your
// component. This should be when firstRender is true, and multiple calls
// to your JavaScript objects, when firstRender is false.
protected override async Task OnAfterRenderAsync(bool firstRender)
{
// Note: Here you initialize your elements, only once. When the user
// clicks on the li element, you'll call your required method from
// the click event handler dropMenu
if (firstRender)
{
await JSRuntime.InvokeAsync<object>("MyJSMethods.myMethod",
MyElementReference);
}
}
}
public async void dropMenu()
{
await JSRuntime.InvokeAsync<object>("MyJSMethods.myMethod",
MyElementReference);
}
Place your script at the bottom of the _Host.cshtml file, just below
Adjust your element accordingly. Note the code here only display the alert with the id given to the li tag. Instead, you'll have to add the code to change your objects as necessary.
<script src="_framework/blazor.server.js"></script>
<script>
window.MyJSMethods =
{
myMethod: function (element) {
window.alert(element.id);
}
};
</script>
Note that the parameter named element that the function takes is an element object because we defined the argument as ElementReference in Blazor. Of course, you could've passed the element id or class name, etc.
Related
I am quite new to Blazor, but I need to select the whole input field when the element is focussed.
In HTML, I can use this:
<input onfocus="this.select();">
The problem however: I already have a Blazor method binded to the #onfocus event:
<input #onfocus="#OnFocus"
I can't use both of them, as I get the following error in Visual Studio:
This means that I need to find a way to select the input field somehow inside the OnFocus() method. I know I can fix this issue using JavaScript (JSInterop), but I hope there is somehow a way to do this without using JavaScript.
Current OnFocus() method:
private void OnFocus (FocusEventArgs e) {
if (String.IsNullOrEmpty(this.inputValue)) {
this.inputValue = Utils.SetDefaultValue();
}
Utils.SelectInput(this.index); <-- current working JSInterop method I want to get rid off
}
I guess this should work:
<input #bind="Value" #onfocus ="#((FocusEventArgs args) => OnFocus(args))" onfocusin="this.select();">
#code
{
private string Value { get; set; } = "MyValue";
private void OnFocus(FocusEventArgs e)
{
}
}
Is there a way to use both #onfocus and HTML focus in Blazor without JavaScript?
Note: the #onfocus is a compiler directive which creates code like this:
__builder.AddAttribute(6, "onfocus",
EventCallback.Factory.Create<FocusEventArgs>(this, (FocusEventArgs args) => OnFocus(args) ));
As you can see, this code, when rendered, attaches an event handler to the JS "onfocus" event, which is why you can't use it twice (causing error). focus or rather focus(), is a method that is called to set the focus, just as select is a method that is called to select text.
Note that you can use #onfocus to create an event handler from where you can call a JS function to select the code. This is legitimate.
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.
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.
I am working with some controls that are written using microsoft ajax tool kit. I want to trigger an event in these controls using jQuery. I was hoping that it should be as simple as triggering any event from jQuery but it does not seem to be working. Here is sample code..
Ajax Control
/// <reference name="MicrosoftAjax.js"/>
Type.registerNamespace("MyNameSpace");
MyNameSpace.AjaxUserControl = function(element) {
MyNameSpace.AjaxUserControl.initializeBase(this, [element]);
}
MyNameSpace.AjaxUserControl.prototype = {
initialize: function() {
MyNameSpace.AjaxUserControl.callBaseMethod(this, 'initialize');
}
},
dispose: function() {
MyNameSpace.AjaxUserControl.callBaseMethod(this, 'dispose');
},
_onChange: function(evt) {
alert('On Change event.');
},
}
if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();
Jquery Code:
var myJqueryControl = (function ($, AjaxControl) {
$(document).ready(function(){
var ajaxEventProxy = Function.createDelegate(AjaxControl, AjaxControl._onChange);
$("#JqueryCommandButton").click(ajaxEventProxy);
} (jQuery,MyNameSpace.AjaxUserControl));
Its not working, when the button is clicked i cannot see alert message inside onchange. Would appreciate any guidance on how to make it work.
Thanks
CSC
It would work much easier if you followed the JQuery widget pattern. Because an AJAX control wraps around an element, much like the widget does, you can more easily integrate the two, by writing some setup with the init method:
_init: function() {
//store a control reference within the widget
this._control = $find(this.element.attr("id"));
}
Then, in your widget, you can refer to the control via the this._control.
If you can't switch models, the way to resolve this is not refer to the type directly, but the instance... instead of passing in the reference MyNameSpace.AjaxUserControl to the constructor, pass in an instance, then you can do:
instance._onchange();
To invoke the event.
So I now have the following jquery to hide or show a textbox based on specific values selected in a DropDownList. This works except that I need the first display of the popup to always be hidden. Since no index change was made in the drop down list, the following does not work for that. If I code it as visible="false", then it always stays hidden. How can I resolve this?
<script language="javascript" type="text/javascript">
var _CASE_RESERVE_ACTION = "317";
var _LEGAL_RESERVE_ACTION = "318";
function pageLoad() {
$(".statusActionDDLCssClass").change(function() {
var value = $(this).val();
if (value == _CASE_RESERVE_ACTION || value == _LEGAL_RESERVE_ACTION) {
$(".statusActionAmountCssClass").attr("disabled", false);
$(".statusActionAmountCssClass").show();
}
else {
$(".statusActionAmountCssClass").attr("disabled", true);
$(".statusActionAmountCssClass").hide();
}
});
}
</script>
Thank you,
Jim in Suwanee, GA
If you set
visible=false
.Net will not render it. You can do
style="display:none;"
and .Net will render the tag properly but CSS will hide it from the user.
Add the following to pageLoad function
function pageLoad(sender, args) {
$("input.statusActionAmountCssClass").hide();
.... rest of code .....
}
By the way, I would recommend using the selector $("input.statusActionAmountCssClass") to get a jQuery object containing a reference to your input, otherwise jQuery will search all elements to match the CSS class .statusActionAmountCssClass
EDIT:
Another change that could also be made is to use jQuery's data() to store the two global variables
$.data(window, "_CASE_RESERVE_ACTION","317");
$.data(window, "_LEGAL_RESERVE_ACTION","318");
then when you need them simply cache the value in a local variable inside the function
function someFunctionThatNeedsGlobalVariableValues() {
var caseReserveAction = $.data(window, "_CASE_RESERVE_ACTION");
var legalReserveAction = $.data(window, "_LEGAL_RESERVE_ACTION");
}
this way, the global namespace is not polluted. See this answer for more on data() command