Blazor - Usando JavaScript - I
Neste artigo veremos como usar JavaScript em aplicações Blazor WebAssembly. |
Com o Blazor
WebAssembly, podemos criar aplicativos robustos sem escrever nenhum código
JavaScript.
No entanto, existem alguns cenários que requerem o uso de JavaScript. Por exemplo, podemos ter uma biblioteca JavaScript favorita que queremos continuar a usar. Além disso, sem JavaScript, não podemos manipular o DOM ou chamar qualquer uma das APIs JavaScript.
A seguir temos uma
lista parcial de coisas às quais não temos acesso diretamente via Blazor
WebAssembly:
- A manipulação do DOM;
- A API de captura de mídia e streams;
- A API WebGL (gráficos 2D e 3D para a web);
- A API de armazenamento da Web (localStorage e sessionStorage);
- A API de geolocalização;
- Caixas pop-up JavaScript (caixa de alerta, caixa de confirmação, caixa de
prompt);
- O status online do navegador;
- Histórico do navegador
- A biblioteca Chart.js;
- Outras bibliotecas JavaScript de terceiros;
A lista anterior não é abrangente, pois existem centenas de bibliotecas JavaScript atualmente disponíveis. No entanto, o ponto principal a ser lembrado é que não podemos manipular o DOM sem usar JavaScript.
Portanto, provavelmente sempre precisaremos usar algum JavaScript em nossos aplicativos da web, e, felizmente, podemos usar a interoperabilidade javascript ou JS nas aplicações Blazor WebAssembly para ter acesso a estes recursos.
Explorando a interoperabilidade JS
Para invocar uma função JavaScript a partir da plataforma .NET, usamos a
abstração IJSRuntime.
Essa abstração representa uma instância de um tempo de execução JavaScript que o framework pode chamar. Para usar o IJSRuntime, devemos primeiro injetá-lo em nosso componente usando injeção de dependência.
A diretiva
@inject é usada para injetar uma dependência
em um componente. O código a seguir injeta o IJSRuntime
no componente atual:
@inject IJSRuntime js
A abstração IJSRuntime tem dois métodos que podemos usar para invocar
funções JavaScript:
Ambos os métodos são assíncronos. A diferença entre esses dois métodos é que um deles retorna um valor e o outro não.
Podemos fazer
downcast de uma instância de IJSRuntime para uma
instância de IJSInProcessRuntime para executar o
método de forma síncrona. Finalmente, podemos invocar um método da plataforma
.NET a partir de código JavaScript decorando o método com
JsInvokable. Veremos exemplos de cada um desses métodos mais
adiante.
No entanto, antes de podermos invocar um método JavaScript, precisamos carregar
o JavaScript em nosso aplicativo.
Carregando código JavaScript
Existem algumas maneiras de carregar o código JavaScript em um aplicativo Blazor
WebAssembly. Uma maneira é inserir o código JavaScript diretamente em um
elemento de script no elemento body do arquivo
wwwroot/index.html.
No entanto, em vez
de inserir o código JavaScript diretamente no arquivo html, recomendamos o uso
de um arquivo JavaScript externo para suas funções JavaScript.
Podemos adicionar um arquivo externo referenciando-o no arquivo
wwwroot./index.html. O código a seguir faz
referência a um arquivo chamado btwInterop.js que
está na pasta wwwroot/scripts.
<script src="scripts/bweInterop.js"></script>
Uma maneira melhor de organizar scripts é colocar um arquivo JavaScript externo
com um componente específico. Para adicionar um arquivo JavaScript colocado com
um componente específico, crie um arquivo JavaScript na mesma pasta do
componente com o mesmo nome do componente, mas com uma extensão de arquivo JS.
Por exemplo, o
componente MeuComponente definido no arquivo
MeuComponente.razor usaria
MeuComponente.razor.js como seu arquivo JavaScript colocado.
Para que o componente faça referência ao código no arquivo JavaScript, o arquivo
deve ser importado para o componente durante o método
OnAfterRenderAsync do componente.
No exemplo a seguir, o identificador de importação é usado para importar um arquivo JavaScript.
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await js.InvokeAsync<IJSObjectReference>
("import", "./Pages/MeuComponente.razor.js");
}
}
|
Neste código o
arquivo JavaScript que está sendo importado está na pasta
Pages e é chamado MeuComponente.razor.js.
Nota: Os arquivos JavaScript colocados
serão movidos automaticamente para a pasta wwwroot
quando o aplicativo for publicado.
O código anterior usa o método InvokeAsync da
biblioteca IJSRuntime para invocar a função de
importação de JavaScript do .NET.
Chamando código JavaScript a partir do .NET
Existem dois métodos diferentes na biblioteca IJSRutime que podemos usar para
invocar JavaScript de .NET de forma assíncrona.
Para invocar uma função JavaScript do .NET de forma síncrona, a biblioteca IJSRutime deve ser reduzida(downcast) para IJSInProcessRuntime.
Usando InvokeAsync
O método InvokeAsync é um método assíncrono usado para invocar uma função
JavaScript que retorna um valor.
Este é o método InvokeAsync da biblioteca IJSRuntime:
ValueTask<TValue> InvokeAsync<TValue>(string identifier, params object[] args); |
Neste código, o primeiro argumento é o identificador da função JavaScript e o segundo argumento é um array de argumentos serializáveis em JSON. O segundo argumento é opcional.
O método
InvokeAsync retorna um
ValueTask do tipo TValue onde TValue
é uma instância desserializada via JSON do valor de retorno do JavaScript.
Em JavaScript, o objeto Window representa a janela
do navegador. Para determinar a largura e a altura da janela atual, usamos as
propriedades innerWidth e
innerHeight do objeto Window.
O código JavaScript a seguir inclui um método chamado
getWindowSize que retorna a largura e a altura do objeto
Window em um arquivo
bweinterop.js :
var bweInterop = {};
bweInterop.getWindowSize = function () {
var size = {
width: window.innerWidth,
height: window.innerHeight
}
return size;
}
|
Esta é a definição da classe WindowSize que é usada para armazenar o tamanho da janela no .NET:
public class WindowSize
{
public int? Width { get; set; }
public int? Height { get; set; }
}
|
A seguir temos o componente Index.razor chamando o método GetWindowSize do arquivo bweInterop.js:
@page "/"
@inject IJSRuntime js
<PageTitle>Home</PageTitle>
@if (windowSize.Width != null)
{
<h2>
Tamanho da Janela: @windowSize.Width x @windowSize.Height
</h2>
}
<button @onclick="GetWindowSize">Tamanho da Janela</button>
@code {
private WindowSize windowSize = new WindowSize();
private async ValueTask GetWindowSize()
{
windowSize = await js.InvokeAsync<WindowSize>(
"bweInterop.getWindowSize");
}
}
|
Neste código temos:
A injeção da biblioteca IJSRuntime no componente. Quando o botão T'tamanho da janela' for clicado, o método GetWindowSize usa o método InvokeAsync de IJSRuntime para invocar a função JavaScript getWindowSize.
A função
JavaScript GetWindowSize retorna a largura e a
altura da janela para a propriedade windowSize. Por fim, o componente regenera
sua árvore de renderização e aplica quaisquer alterações ao DOM do navegador.
Esta é uma captura de tela da página após clicar no botão 'Tamanho da janela':
O método InvokeSync de IJSRuntime é usado para chamar funções JavaScript que retornam um valor. Se não precisarmos retornar um valor, podemos usar o método InvokeAsync.
Usando o InvokeVoidAsync
O método InvokeVoidAsync é um método assíncrono
usado para invocar uma função JavaScript que não retorna um valor.
Este é o método InvokeVoidAsync da bibliotea
IJSRuntime:
InvokeVoidAsync(string identifier, params object[] args);
|
Assim como o
método InvokeAsync, o primeiro argumento é o identificador da função JavaScript
que está sendo chamada e o segundo argumento é um array de argumentos
serializáveis em JSON. O segundo argumento é opcional.
Em JavaScript, o objeto Document representa o nó
raiz do documento HTML. A propriedade title do
objeto Document é usada para especificar o texto
que aparece na barra de título do navegador.
Suponha que
queremos atualizar o título do navegador enquanto navegamos entre os componentes
em nosso aplicativo Blazor WebAssembly. Para fazer isso, precisamos usar
JavaScript para atualizar a propriedade title.
O código JavaScript a seguir exporta uma função chamada
setDocumentTitle, que define a propriedade title
do objeto Document com o valor fornecido pelo
argumento title:
Shared/Document.razor.js
export function setDocumentTitle(title) {
document.title = title;
}
|
Neste código
usamos uma instrução export para exportar a
função setDocumentTitle.
O seguinte componente Document.razor usa a função
JavaScript setDocumentTitle para atualizar a barra
de título do navegador:
@inject IJSRuntime js
@code {
[Parameter] public string Title { get; set; } = "Home";
protected override async ValueTask OnAfterRenderAsync
(bool firstRender)
{
if (firstRender)
{
IJSObjectReference module =
await js.InvokeAsync<IJSObjectReference>
("import", "./Shared/Document.razor.js");
await module.InvokeVoidAsync
("setDocumentTitle", Title);
}
}
}
|
O código de
marcação a seguir usa o componente Document para
atualizar a barra de título do navegador para Home – Meu Aplicativo:
<Document Title="Home - Meu aplicativo" />
Por padrão, as chamadas de interoperabilidade JS são assíncronas. Para fazer chamadas de interoperabilidade JS síncronas, precisamos usar IJSInProcessRuntime.
Usando IJSInProcessRuntime
Até agora examinamos apenas a invocação de funções JavaScript de forma assíncrona. Mas também podemos invocar funções JavaScript de forma síncrona.
Fazemos isso fazendo um downcast de IJSRuntime para IJSInProcessRuntime.
A biblioteca
IJSInProcessRuntime permite que nosso código .NET
invoque chamadas de interoperabilidade JS de forma síncrona. Isso pode ser
vantajoso porque essas chamadas têm menos sobrecarga do que suas contrapartes
assíncronas.
Estes são os métodos síncronos de IJsInProcessRuntime:
O código a seguir usa IJSInProcessRuntime para invocar uma função JavaScript de forma síncrona:
@inject IJSRuntime js
@code {
private string GetGuid()
{
string guid =
((IJSInProcessRuntime)js).Invoke<string>("getGuid");
return guid;
}
}
|
Neste código a
instância IJsRuntime foi reduzida para uma instância IJSInProcessRuntime e o
método Invoke da instância IJSInProcessRuntime foi
usado para chamar o método JavaScript getGuid.
A abstração IJSRuntime fornece métodos para invocar funções JavaScript
diretamente de métodos .NET. Eles podem ser invocados de forma assíncrona ou
síncrona. Chamar um método .NET diretamente de uma função JavaScript requer um
atributo especial.
Invocando código .NET a partir de JavaScript
Podemos invocar um método .NET público a partir do JavaScript decorando o método
com o atributo JSInvokable.
O método .NET a seguir é decorado com o atributo
JSInvokable para permitir que seja invocado do JavaScript:
private WindowSize windowSize = new WindowSize();
[JSInvokable]
public void GetWindowSize(WindowSize newWindowSize)
{
windowSize = newWindowSize;
StateHasChanged();
}
|
Neste código a
propriedade windowSize é atualizada sempre que o
método GetWindowSize é invocado do JavaScript.
Depois que a propriedade windowSize é atualizada, o método
StateHasChanged do componente é chamado para notificar o componente de
que seu estado foi alterado e, portanto, o componente deve ser renderizado
novamente.
Para chamar um método .NET de JavaScript, devemos criar uma classe
DotNetObjectReferenece para o JavaScript usar
para localizar o método .NET. A classe
DotNetObjectReferenece envolve um argumento de interoperabilidade JS,
indicando que o valor não deve ser serializado como JSON, mas deve ser passado
como uma referência.
Nota: Para evitar vazamentos de memória e
permitir a coleta de lixo em um componente que cria uma classe
DotNetObjectReference, você deve descartar cuidadosamente cada instância de
DotNetObjectReference.
O código a seguir cria uma instância DotNetObjectReference
que envolve o componente Resize. A referência é
então passada para o método JavaScript:
private DotNetObjectReference<Resize> objRef;
protected async override ValueTask OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
objRef = DotNetObjectReference.Create(this);
await js.InvokeVoidAsync(
"bweInterop.registerResizeHandler",
objRef);
}
}
|
Podemos invocar um método em um componente .NET de JavaScript usando uma referência ao componente criado com DotNetObjectReference.
No código JavaScript do arquivo bweInterop.js a seguir, a função registerResizeHandler cria a função resizeHandler que é chamada na inicialização e sempre que a janela for redimensionada.
bweInterop.registerResizeHandler = function (dotNetObjectRef) {
function resizeHandler() {
dotNetObjectRef.invokeMethodAsync('GetWindowSize',
{
width: window.innerWidth,
height: window.innerHeight
});
};
resizeHandler();
window.addEventListener("resize", resizeHandler);
}
|
Aqui a função
invokeMethodAsync é usada para invocar o método
GetWindowSize da plataforma .NET que foi decorado
com o atributo JSInvokable.
Nota : Podemos usar a função invokeMethod ou a função
invokeMethodAsync para invocar métodos de instância .NET a partir do JavaScript.
Este é o código .NET completo para o componente Resize:
@page "/resize"
@inject IJSRuntime js
@implements IDisposable
<PageTitle>Resize</PageTitle>
@if (windowSize.Width != null)
{
<h2>
Window Size: @windowSize.Width x @windowSize.Height
</h2>
}
@code {
private DotNetObjectReference<Resize> objRef;
private WindowSize windowSize = new WindowSize();
protected async override Task OnAfterRenderAsync(
bool firstRender)
{
if (firstRender)
{
objRef = DotNetObjectReference.Create(this);
await js.InvokeVoidAsync(
"bweInterop.registerResizeHandler",
objRef);
}
}
[JSInvokable]
public void GetWindowSize(WindowSize newWindowSize)
{
windowSize = newWindowSize;
StateHasChanged();
}
public void Dispose()
{
objRef?.Dispose();
}
}
|
Neste código o componente Resize exibe a largura e a altura atuais do navegador. Conforme você redimensiona o navegador, os valores exibidos são atualizados automaticamente.
Além disso, o
objeto DotNetObjectReference é descartado quando o
componente é descartado. Para testar o componente Resize
, pressione Ctrl+F5 para iniciar o aplicativo sem depuração. Depois que o
aplicativo iniciar, navegue até a página /resize
e redimensione a janela.
A abstração IJSRuntime nos fornece uma maneira de
invocar funções JavaScript do .NET e invocar métodos .NET do JavaScript.
Para mostrar um exemplo prático de uso do JavaScript vamos usar a API Web
Storage do JavaScript na próxima parte do artigo....
"Também não oprimirás o estrangeiro; pois vós conheceis o coração do
estrangeiro, pois fostes estrangeiros na terra do Egito."
Êxodo 23:9
Referências:
NET - Unit of Work - Padrão Unidade de ...