How to run stand-alone Javascript in Blazor

Viewed 1604

How can I run some Javascript functions in a Blazor app, without using the JS interop or jQuery? Just plain old Javascript functions that interact with the DOM, independently of Blazor.

I added my script right before the closing </body> tag:

<script src="app.js"></script>

And in app.js I have the following:

var elements = document.querySelectorAll(".some-element");

elements.forEach(function (element) {
    element.addEventListener("click", (e) => {
        alert("Hello");
    });
});

Of course the selector finds no element. I'm guessing they aren't yet present in the DOM at that point? How can I run that script without using the JS interop or jQuery?

4 Answers

If your okay with using jquery you can use an on handler, which will apply to dynamically added elements

$("body").on("click", ".some-element", function(){
  alert("Hello");
});

You can do natively too, but seems sketchy IMHO:

document.querySelector('body').addEventListener('click', function(event) {
  if (event.target.className.toLowerCase() === 'some-element') {
      alert("Hello");
  }
});

Note you have weird javascripty things to worry about now like z-index if the click event actually gets up to the body, but it should work for simple stuff.

Main question is WHY would you want to do this in Blazor - whole point is you can use C# events instead you crazy person!!

Create a page like this:

<div height="@($"{Height}px")" width="@($"{Width}px")">
    Test
</div>

<button @onclick="DoubleSize">Double Size</button>

@code
{
    public int Width { get; set; } = 50;

    public int Height { get; set; } = 50;

    private void DoubleSize()
    {
        Width = Width * 2;
        Height = Height * 2;
    }
}

On render the height and width are 50px:

enter image description here

Then you click the Button, and they change to 100px:

enter image description here

Similarly with CSS if that's what you were talking about:

<div style="width: @($"{Width}px"); height: @($"{Height}px")">
    Test
</div>

enter image description here

This is interacting with the properties of the DOM elements without using Javascript... you can do this with any property. You don't need to use Javascript - you just bind them to your properties in C#...

You shouldn't need to 'retrieve the final properties of the rendered DOM elements' as you should be controlling them from C# and not worrying about the DOM

I'm pretty new in Blazor, so sorry if it's not the correct way to do it in Balzor. But I think that you need always use JS interop to use JavaScript function. You want to execute some script, but the question is: when do you want to execute the script? I imagine you want to execute an action after navigate to a page, after make a click in a button... and all this events happens in Blazor

If you ask about relationate an blazor element with the doom you need use @ref

A little example. You create a .js like

var auxiliarJs = auxiliarJs || {};
auxiliarJs.getBoundingClientRect = function (elementRef) {
    let result = elementRef? elementRef.getBoundingClientRect():
                {left: 0,top: 0,right: 0,bottom: 0,x: 0,y: 0,width: 0,height: 0 };
    return result;
}
auxiliarJs.executeFunction = function (elementRef, funciones) {
    let res = null;
    try {
        if (Array.isArray(funciones)) {
            functiones.forEach(funcion => {
                elementRef[funcion]()
            });
        }
        else
            res = elementRef[funciones]();
    }
    catch (e) { }

    if (res)
        return res;
}
auxiliarJs.setDocumentTitle = function (title) {
        document.title = title;
    };

And a service.cs with his interface

public interface IDocumentService
{
    Task<ClientRect> getBoundingClientRect(ElementReference id);
    Task setDocumentTitle(string title);
    Task<JsonElement> executeFunction(ElementReference id, string funcion);
    Task executeFunction(ElementReference id, string[] funciones);
}

public class DocumentService:IDocumentService
{
    private IJSRuntime jsRuntime;

    public DocumentService(IJSRuntime jsRuntime)
    {
        this.jsRuntime = jsRuntime;
    }
    public Dictionary<string, object> JSonElementToDictionary(JsonElement result)
    {
        Dictionary<string, object> obj = new Dictionary<string, object>();
        JsonProperty[] enumerador = result.EnumerateObject().GetEnumerator().ToArray();

        foreach (JsonProperty prop in enumerador)
        {
            obj.Add(prop.Name, prop.Value);
        }
        return obj;
    }

    public async Task<ClientRect> getBoundingClientRect(ElementReference id)
    {
        return await jsRuntime.InvokeAsync<ClientRect>("auxiliarJs.getBoundingClientRect", id);
    }
    
    public async Task setDocumentTitle(string title)
    {
        await jsRuntime.InvokeVoidAsync("auxiliarJs.setDocumentTitle", title);

    }
    public async Task<JsonElement> executeFunction(ElementReference id,string funcion)
    {
        var result= await jsRuntime.InvokeAsync<JsonElement>
                         ("auxiliarJs.executeFunction", id, funcion);
        return result;

    }
    public async Task executeFunction(ElementReference id, string[] funciones)
    {
        await jsRuntime.InvokeVoidAsync("auxiliarJs.executeFunction", id, funciones);
    }
}

public class ClientRect
{
    public float left{ get; set; }
    public float top { get; set; }
    public float right { get; set; }
    public float bottom { get; set; }
    public float x { get; set; }
    public float y { get; set; }
    public float width { get; set; }
    public float height { get; set; }
}

Well, you inject the service as usual in program.cs

    public static async Task Main(string[] args){
       ....
       builder.Services.AddSingleton<IDocumentService, DocumentService>();
    }

And in your component razor

@inject IDocumentService document

<div @ref="mydiv"></div>
<input @ref="myinput">
<button @onclick="click">click</button>

@code{  
    private ElementReference mydiv;
    private ElementReference myinput;

    click(){
      ClientRect rect = await document.getBoundingClientRect(mydiv);
      document.setDocumentTitle("New Title");
      document.executeFunction(myinput 'focus')
    }
}

Blazor "overwrites" all attached JS Events during render process

window.attachHandlers = () => {
var elements = document.querySelectorAll(".some-element");
elements.forEach(function (element) {
element.addEventListener("click", (e) => {
    alert("Hello");
});

});

and in razor page

 protected override void OnAfterRender(bool firstRender)
{
    if(firstRender)
    {

    JSRuntime.InvokeVoidAsync("attachHandlers");
    }
}
Related