0

I have a javascript event I need to listen for (kicked off by an outside vendor library) and then act on that inside of my component by hiding some content when that event is fired. I've gotten it working when the component only appears on a given page once, but the first instance of the component doesn't work when the component is included on the page more than once.

The component code:

...

// this is a method that is called internally by the component when needed but also externally 
// when the javascript event is fired by the 3rd party library that controls an input box on 
// the screen that has an "x" icon in it that clears out its contents.  This code also clears 
// out the resulting list of data that came back when the user typed something into it
[JSInvokable("CancelAutocomplete")]
public async Task CancelAutocomplete()
{
    this.IsLoading = false;
    this.SearchResults = null;
    await InvokeAsync(StateHasChanged);
}

// this i found online to combat the need for a static method when invoking blazor code from js
// see the next code block down to see what this looks like on the JS side
private DotNetObjectReference<PersonAutocompleteInput>? dotNetHelper;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        dotNetHelper = DotNetObjectReference.Create(this);
        await JS.InvokeVoidAsync("Helpers.setDotNetHelper", dotNetHelper);
    }
}

/// <summary>
/// Disposes of the dotNetHelper so its not gobbling memory
/// </summary>
protected override void Dispose(bool disposed)
{
    base.Dispose(disposed);
    this.dotNetHelper?.Dispose();
}

Then there is the javascript code (currently called from my MainLayout.razor.js so it would be available on any page that might use the above component):

...

class Helpers {
    static dotNetHelper;

    static setDotNetHelper(value) {
        Helpers.dotNetHelper = value;
    }

    static async cancelAutocomplete() {
        await Helpers.dotNetHelper.invokeMethodAsync('CancelAutocomplete');
    }

}

window.Helpers = Helpers;

In the same javascript file is the code that actually listens for the custom javascript event:

// our 3rd party library throws the 'custom-clear-click' event when a user presses the 'x' icon in a user input box to clear the text
// when that happens we should clear out the autocomplete box as well
document.addEventListener('custom-clear-click', (evt) => {

    Helpers.cancelAutocomplete();

});

In the razor component itself there is code that shows or hides based on whether there are results in SearchResults:

@if (this.SearchResults != null)
{
    ... html stuff here
}

To summarize, this all works when the component only appears once, however if it appears twice on the page nothing happens for the first component, it only works on the second component. No errors thrown in the console.

4
  • Isn't it because you have a static dotnethelper and each time the component is instantiated it sets the last component as the static dotNetHelper? Commented Oct 31, 2023 at 0:50
  • @RBee that's my thought as well, not sure how to fix it though, as I understand it a static method is required in order to be called from javascript interop Commented Oct 31, 2023 at 13:31
  • Maybe a parent component which will be tied to the static script, and handle using blazor to propogate those events to your current component instances. Commented Oct 31, 2023 at 13:35
  • @Rbee after some research elsewhere I fixed the issue, answer below if you're interested Commented Oct 31, 2023 at 20:20

1 Answer 1

0

After some discussion here and further research here

My final solution works and looks like this:

MainLayout.razor.js

// third party library throws the 'custom-clear-click' event when a user presses the 'x' icon in a user input box to clear the text
// when that happens we should clear out the autocomplete box as well
document.addEventListener('twc-clear-click', (evt) => {

    interopCall(evt.target);

});

// this allows us to independently track all of our XXXAutocompleteInput components so we can close the autocomplete results windows on the X click event from third party library
// now the appropriate autocomplete results are closed no matter where it is clicked from or how many are on the screen at once

// this is where we store the unique dotnethelper with the input element from each component
window.setup = (element, dotNetHelper) => {
    element.dotNetHelper = dotNetHelper;
}

// this is what we call from our javascript event thats emitted by third party library and pass in the element it was called from and then call the correct dotNetHelper
// each autocomplete razor component has its own CancelAutocomplete event, which is called below via the appropriate element and dotNetHelper
window.interopCall = async (element) => {
    await element.dotNetHelper.invokeMethodAsync('CancelAutocomplete');
}

Component .razor code

...

// gets a reference to the input box for use in the javascript interop calls when the X button on the TDS input throws the twc-clear-click event
private ElementReference inputRef;

...

[JSInvokable("CancelAutocomplete")]
public async Task CancelAutocomplete()
{
    try
    {
        // do stuff here that changes state
        await InvokeAsync(StateHasChanged);
    } catch(Exception e)
    {

    }
}

// this stores a reference to this component (objRef) AND a the specific instance of an input element contained within it (inputRef) 
// and passes it to MainLayout.razor.js which stores it globally by objRef and inputRef for reference later
private DotNetObjectReference<PersonAutocompleteInput>? objRef;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        objRef = DotNetObjectReference.Create(this);
        await JS.InvokeVoidAsync("setup", inputRef, objRef);
    }
}

/// <summary>
/// Disposes of the timer so its not gobbling memory
/// </summary>
protected override void Dispose(bool disposed)
{
    base.Dispose(disposed);
    this.objRef?.Dispose();
}

Component .razor html

...

<input @ref="inputRef" ...>...</input>

...
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.