Bhav
Bhav

Reputation: 2207

How do I add jQuery to a Blazor server app?

I'm attempting to add some jQuery to a Blazor server app. Specifically being able to autocomplete an InputText field on a form (where id = 'CompanyName') which is on a razor page (CreateNote.razor). When I run the app, autocomplete doesn't work. I've added the alert line to test if that executes and that works fine.

_Layout.cshtml:

...
</head>
    <link href="TelephoneNotes.styles.css" rel="stylesheet" />

    <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
    <script src="https://code.jquery.com/ui/1.13.1/jquery-ui.min.js" integrity="sha256-eTyxS0rkjpLEo16uXTS0uVCS4815lc40K2iVpWDvdSY=" crossorigin="anonymous"></script>

    <link href="https://tofsjonas.github.io/sortable/sortable.css" rel="stylesheet" />
    <script src="https://tofsjonas.github.io/sortable/sortable.js"></script>

    <script type="text/javascript">$(function () {
            $("#companyName").autocomplete({
                source: ["Apple", "Google", "Facebook"],
                minLength: 3
            })

    $(document).ready(function () {
        alert("test");
    });

        })</script>
    <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
</head>

CreateNote.razor:

<EditForm Model="@newNote" OnSubmit="@InsertNote">
...
    <div class="form-group row" style="padding-top: 5px;">
        <label for="company" class="col-sm-2 col-form-label">
            Company:
        </label>
        <div class="col-sm-10">
            <InputText id="companyName" class="form-control" placeholder=""
                       @bind-Value="newNote.CompanyName" />
        </div>
    </div>
...
</EditForm>

UPDATE:

I've created an init.js with the wwwroot folder:

function CompleteFormControls() {
    $("#companyName").autocomplete({
        source: ["Apple", "Google", "Facebook"],
        minLength: 3
    }

I've added the following line to the bottom of _Layout.cshtml before the closing body tag:

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

I've create a JSInterop class:

namespace TelephoneNotes.Services
{
    using Microsoft.JSInterop;

    public class JSInterop
    {
        private readonly IJSRuntime js;

        public JSInterop(IJSRuntime js)
        {
            this.js = js;
        }

        public async ValueTask InvokeCompleteFormControls()
        {
            await js.InvokeVoidAsync("CompleteFormControls");
        }

        public void Dispose()
        {
        }
    }
}

CreateNote.razor:

@page "/createnote"

<PageTitle>Create Note</PageTitle>

@using TelephoneNotes.Data
@inject NotesService NotesService
@implements IDisposable
@inject IJSRuntime JS

<h1>Create Note</h1>

<EditForm Model="@newNote" OnSubmit="@InsertNote">
    <div class="form-group row" style="padding-top: 5px;">
        <label for="company" class="col-sm-2 col-form-label">
            Company:
        </label>
        <div class="col-sm-10">
            <InputText id="companyName" class="form-control" placeholder=""
                       @bind-Value="newNote.CompanyName" />
        </div>
    </div>

    <div class="form-group row" style="padding-top: 15px;">
        <button type="submit" class="btn btn-primary">Submit</button>
    </div>
</EditForm>

@code {
    private List<string?> companyNames = new List<string?>();
    private TelephoneNotes.Services.JSInterop? jsClass;

    protected override async Task OnInitializedAsync()
    {
        jsClass = new(JS);
        companyNames = await NotesService.GetCompanyNames();
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await jsClass.InvokeCompleteFormControls();
        }
    }

    public void Dispose() => jsClass?.Dispose();
}

However when I run the app and go to the create note page, I receive the following error:

Error: Microsoft.JSInterop.JSException: Could not find 'CompleteFormControls' ('CompleteFormControls' was undefined).

Any suggestions why?

_Host.cshtml:

@page "/"
@namespace TelephoneNotes.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
    Layout = "_Layout";
}

<component type="typeof(App)" render-mode="ServerPrerendered" />
<script src="/init.js"></script>

Upvotes: 1

Views: 4101

Answers (4)

Wajeeh Hasan
Wajeeh Hasan

Reputation: 197

Blazor does not understand the query selector '$', you need to replace it with keyword document.

Upvotes: 0

spacemonki
spacemonki

Reputation: 303

In Pages/_Layout.cshtml add the following snippet to the end of the body along with your JS script:

<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="js/init.js"></script>

In this case your JS script will be in wwwroot/js folder

In the CreateNote.razor:

IJSRuntime _js

//other code here

@code {
protected override async Task OnAfterRenderAsync(bool firstRender)
  {
      await _js.InvokeVoidAsync("CompleteFormControls");
  }
}

Upvotes: 0

Nb777
Nb777

Reputation: 2032

You can not use Js like this in Blazor.

What is $( document ).ready() {}?
It means that the code inside this scope {} will render just and only once the page DOM is ready for JS code to execute.

When is the DOM ready in Blazor?
In most cases you have to execute the JS code in the bult-in OnAfterRender function because in this point you will be sure that DOM is complete and you can use JS to manipulate the front-end.

What is the best way to execute JS functions?

  1. Create a separate file(MyJs.js) for Js in the wwwroot folder and add your JS functions there.

    function CompleteFormControls ()
    {
      setTimeout(() => {
        if($("#companyName") != null){
          $("#companyName").autocomplete({
          source: ["Apple", "Google", "Facebook"],
          minLength: 3});
        }
     },500);
    }
    
  2. Add your Js file 'MyJs.js' in the end of the body of your _Host.html and it should be down of jquery:

    <body>
     <script src="~/lib/jquery/jquery.min.js"></script>
     <script src="~/MyJs.js"></script>
    </body>
    
  3. Create a class file to represent your functions in MyJs.js file:

    puplic statis class JsHelper
    {
     [DebuggerHidden]// tell VS Don't debug this function
     puplic statis aasync ValueTask CompleteFormControlsAsync(this IJSRunTime)
     {
      await js.InvokeVoidAsync("CompleteFormControls");
     }
    } 
    
  4. Inject IJSRuntime in your razor page:

    [Inject] private IJSRuntime Js {get; set;}
    protected override Task OnAfterRenderAsync(bool firstRender)
    {
      if(firstRender)
      {
        await Js.CompleteFormControlsAsync();
      }
    }
    

Note: You can also replace OnAfterRenderAsync and use some event like Click event, because in this case you are sure that the DOM has also completed rendering.

Upvotes: 1

Ziregbe Otee
Ziregbe Otee

Reputation: 554

Put your javascript at the end of the body tag. The current location you've put it $("#companyName") will be null. The component has to be loaded before your javascript executes.

<html>
<body>

<Your Components will be here>

 <script type="text/javascript">$(function () {
            $("#companyName").autocomplete({
                source: ["Apple", "Google", "Facebook"],
                minLength: 3
            })

    $(document).ready(function () {
        alert("test");
    });

        })</script>
<body>
</html>

Upvotes: 0

Related Questions