Edwin ZAP
Edwin ZAP

Reputation: 463

Blazor Web App (.net 8): javascript not executed on page change

I'm using a template that I bought. If I click on a link to go to another page, the javascript is not executed again. That's a big problem since there are a lot of things happening in these js.

I don't know for what reason but the only time it's working is when I click on the nav menu. Maybe it's because the nav bar has an ID and is used in the js file.

Here is a simplified version of my main.js:

(function($) {

    var $window = $(window),
        $body = $('body'),
        $header = $('#header');

//...//
    // Play initial animations on page load.
        $window.on('load', function() {
            window.setTimeout(function() {
                $body.removeClass('is-preload');
            }, 100);
        });

    // Dropdowns.
        $('#nav > ul').dropotron({
            alignment: 'center'
        });

    // Off-Canvas Navigation.

        // Navigation Panel Toggle.
            $('<a href="#navPanel" class="navPanelToggle">Menu</a>')
                .appendTo($header);

        // Navigation Panel.
            $(
                '<div id="navPanel">' +
                    '<nav>' +
                        $('#nav').navList() +
                    '</nav>' +
                    '<a href="#navPanel" class="close"></a>' +
                '</div>'
            )
                .appendTo($body)
                .panel({
                    delay: 500,
                    hideOnClick: true,
                    hideOnSwipe: true,
                    resetScroll: true,
                    resetForms: true,
                    side: 'right',
                    target: $body,
                    visibleClass: 'is-menu-visible'
                });

})(jQuery);

And I just put it in my App.razor file:

<body class="is-preload">
    <!-- Wrapper -->
    <div id="wrapper">
        <Routes />
    </div>

    <script src="_framework/blazor.web.js"></script>
    <!-- Scripts -->
    <script src="template/js/jquery.min.js"></script>
    <script src="template/js/jquery.dropotron.min.js"></script>
    <script src="template/js/browser.min.js"></script>
    <script src="template/js/breakpoints.min.js"></script>
    <script src="template/js/util.js"></script>
    <script src="template/js/main.js"></script>
</body>

Can you help me. Thank you!

Upvotes: 4

Views: 3786

Answers (1)

Kurt Hamilton
Kurt Hamilton

Reputation: 13515

DOM manipulation

The first thing to note with your code is that you are doing a lot of DOM manipulation with jQuery. You should try to do as much of your DOM manipulation from within Blazor as you can. The main reason is that Blazor manages the DOM based on the state of your components, so any changes you make outside of Blazor could be lost when the state changes in your Blazor components.

Relevant StackOverflow post: Blazor, doubts regarding modifying DOM from JavaScript

Blazor page loading

Blazor isn't like a traditional server rendered app. Regardless of which flavour of Blazor you are using (server / web assembly), you only load resources (e.g. JS, CSS) on the initial page load, much like any other SPA. This means that your JS will only run on the initial load - future navigations are made within the context of the original request.

As you are experiencing, JS will not automatically run per-navigation unless you specifically invoke it.

How to invoke JS from Blazor

Regardless of your strategy for invoking JS per-navigation, you need to understand how to call JS from your Blazor code.

Luckily Blazor gives us an easy way to do this.

Set up the javascript methods you want to run from Blazor.

(function() {
  function sayHello(name) {
    console.log(`Hello, ${name}!`);
  }

  // Create a namespace within the global scope that you can
  // add your own functions to.
  // You can only call JS functions from Blazor that are 
  // in the global scope
  window.myNamespace = window.myNamespace || {};
  // Add your function(s) to your scope here.
  // You may want to split different functions into different
  // files - hence why you get or create your namespace first.
  window.myNamespace.onLoad = {
    sayHello: sayHello
  };
)();

Call the JS function from a Blazor component. Blazor can only start interacting with JS after the first render. You can pass 0 or more arguments from Blazor into your JS function (including references to DOM elements, using the @ref Blazor binding).

@inject IJSRuntime JSRuntime
@code {
  protected override async Task OnAfterRenderAsync(bool firstRender)
  {
    if (firstRender)
    {
      // call the JS function
      // note how we don't need to explicitly call window.myNamespace
      await JSRuntime.InvokeVoidAsync("myNamespace.onLoad.sayHello", "World");
    }
  }
}

How to invoke JS on every page load

There are several approaches you could take here. One approach I might use would be to have a base component for your pages that can call the required functions on the first render.

This would mean that you have to remember to inherit from the base class when you create a new page.

Set up your base page component:

public abstract class BasePageComponent : ComponentBase
{

  [Inject]
  protected IJSRuntime JSRuntime {get; set;}

  protected override async Task OnAfterRenderAsync(bool firstRender)
  {
    await base.OnAfterRenderAsync(firstRender);

    if (firstRender)
    {
      // run some JS function(s) here 
      await JSRuntime.InvokeVoidAsync("myNamespace.onLoad.sayHello", "World");
    }
  }
}

Simply set up your pages to inherit from this base class like so:

@page "/"
@inherits BasePageComponent

There are undoubtedly other ways in which you could run JS per-navigation. My answer will at least arm you with the ability to call JS from Blazor on page load, and should hopefully inspire you to re-think your current reliance on jQuery.

Upvotes: 3

Related Questions