Blazor - Criando um quadro Kanban - I


   Neste artigo vamos apresentar alguns conceitos importantes relacionados com o tratamento de eventos no Blazor e a seguir vamos criar uma aplicação simples que simula um quadro Kanban.

Embora não seja o foco deste artigo vou apresentar o que vem a ser o quadro Kanban sem entrar em detalhes sobre o método Kanban.

A palavra kanban é de origem japonesa e significa 'cartão' ou 'sinalização' e é usada para fazer referência a um sistema de sinalizaçãoes que usa cartões para indicar o progresso ou status da tarefa.

Um quadro Kanban é um quadro usado por diversas metodologias no qual são usados alguns cartões que se deslocam pelo quadro para indicar o status de uma tarefa.

Assim um quadro Kanban é usado por uma equipe que utiliza o Kanban para a gestão visual de seu trabalho e para melhorar sua entrega de produtos e serviços em termos de previsibilidade, qualidade e desempenho no tempo de colocação no mercado.

Vamos criar um projeto Blazor WebAssembly onde vamos simular um quadro Kanban simples definindo 3 níveis de prioridades onde poderemos colocar os cartões (kanbans).

Este projeto vai usar os recursos do Bootstrap e nele vamos criar um componente chamado DropZone (Zona de Lançamento) que representa a área de cada nível de prioriedade. Vamos usar 3 componentes DropZone no projeto para criar o quadro Kanban.

Vamos criar um componente NewTask para representar as tarefas que serão incluídas no quadro Kanban.

Vamos criar a classe  TaskItem que representa a tarefa e sua prioriedade a uma enumeração TaskPriority que define 3 níveis de prioridades :  High, Mid e Low.

Por fim, vamos adicionar o componente NewTask para incluir a capacidade de adicionar novas tarefas ao quadro Kanban.

Um pouco de teoria : Tratamento de eventos no Blazor

Os componentes Razor manipulam eventos usando um atributo de elemento HTML denominado @on{EVENT} onde EVENT é o nome do evento.

O código a seguir chama o método OnClickHandler quando o botão 'Click em mim' for acionado:

<button type="button" @onclick="OnClickHandler">
   Clique em mim
</button>
@code {
    private void OnClickHandler()
    {
         // ...código
    }
}

Os manipuladores de eventos acionam automaticamente uma renderização de interface do usuário. Portanto, não precisamos chamar o método StateHasChanged ao processar os elementos.

Nota:  StateHasChanged é um método em ComponentBase que notifica mudanças de estado em um componente. Quando chamado, ele renderiza novamente o componente.

Os manipuladores de eventos podem fazer referência a quaisquer argumentos associados ao evento. Eles também podem ser usados para chamar métodos síncronos e assíncronos.

O código a seguir chama o método assíncrono OnChangeHandlerAsync quando a caixa de seleção for alterada:

<input type="checkbox" @onchange="OnChangedHandlerAsync" />Tudo bem?
@code {
      bool isOk;
      private async Task OnChangedHandlerAsync(ChangeEventArgs e)
      {
          isOk = (bool)e.Value!;
         // aguardar ...
      }
 }

No código anterior, a classe ChangeEventArgs é usada para fornecer informações sobre o evento de alteração. No exemplo a classe ChangeEventArgs tinha apenas uma propriedade, a propriedade Value que para este objeto pode ser true ou false.

A classe ChangeEventArgs herda da classe EventArgs. Todas as classes EventArgs com suporte do framework ASP.NET Core também têm suporte da estrutura Blazor WebAssembly.

A seguir temos uma lista dos EventArgs suportados:

A classe EventArgs é herdada por cada uma das classes anteriores. Podemos criar nossa própria classe de dados de evento personalizada criando uma classe derivada da classe EventArgs.

Até agora, examinamos maneiras de chamar um método sem nenhum argumento ou com argumentos fornecidos automaticamente pelo evento. No entanto, às vezes precisamos fornecer nossos próprios argumentos.

Usando expressões lambdas

Quando precisamos fornecer argumentos para um método, podemos usar uma expressão lambda. As expressões lambda são usadas para criar funções anônimas e usam o operador => para separar os parâmetros do corpo da expressão.

Há duas formas que o corpo de uma expressão lambda pode usar :

No exemplo a seguir, temos dois buttons, o primeiro botão usa uma expressão e o segundo botão usa um bloco de instrução:

<h1>@mensagem</h1>

<button type="button"
        @onclick="@(() => SetMessage("Blazor é legal"))">
            Quem é legal ?
</button>

<button type="button"
        @onclick="@(() => { @mensagem = "Blazor Detona!"; })">
      Quem Detona ?
</button>

@code{
    private string? mensagem;
    private void SetMessage(string novaMensagem)
    {
        mensagem = novaMensagem;
    }
}

No código acima, quando o botão 'Quem é Legal ?' for clicado, a expressão lambda expression chama o método SetMessage para atualizar o valor do campo de mensagem.

Quando o botão 'Quem Detona ?' for clicado, a expressão lambda vai usar uma instrução para atualizar o valor do campo de mensagem.

Impedindo o comportamento padrão de um evento

Ocasionalmente, precisamos impedir a ação padrão associada a um evento. Podemos fazer isso usando o atributo de diretiva @on{EVENT}:preventDefault, onde EVENT é o nome do evento.

Por exemplo, ao arrastar um elemento, o comportamento padrão impede que ele seja solto em outro elemento. No entanto, para o projeto do quadro Kanban neste artigo, precisaremos colocar itens em várias zonas de lançamento. Portanto, precisaremos evitar esse comportamento padrão.

O código a seguir impede que o comportamento padrão ondragover ocorra. Ao impedir o comportamento padrão, poderemos soltar elementos no elemento div que está sendo usado como dropzone:

<div class="dropzone"
    dropzone="true"
    ondragover="event.preventDefault();">
</div>

Focando um elemento

Há momentos em que precisamos dar foco via código a um elemento HTML. Nesses casos, usamos o método FocusAsync do tipo ElementReference. O ElementReference é identificado adicionando um atributo @ref ao elemento HTML ao qual queremos dar foco. Para atribuir o foco ao elemento HTML, um campo do tipo ElementReference deve ser definido.

O código a seguir de um componente - Focus.razor  - adiciona o valor do elemento input à lista de tarefas e define o foco de volta para o elemento input sempre que o botão é clicado.

@page "/focus"     

<input type="text" @ref="taskInput" @bind-value="@taskName" />

<button type="button" @onclick="OnClickHandlerAsync">
     Incluir Tarefa
 </button>

@foreach (var item in tasks)
{
    <div>@item</div>
}

@code {

    private string? taskName;
    private ElementReference taskInput;
    private List<string> tasks = new();

    private async Task OnClickHandlerAsync()
    {
        tasks.Add(taskName!);
        taskName = "";
        await taskInput.FocusAsync();
    }
}

Neste código, taskInput é definido como um ElementReference e está associado ao elemento de entrada por meio do atributo @ref.

No evento OnClickHandlerAsync, o método FocusAsync é chamado e, o resultado é que cada vez que o botão for clicado, o foco é retornado ao elemento de entrada.

Nota: Como o método FocusAsync depende do DOM, ele só funciona em elementos depois que eles são renderizados.

O framework Blazor WebAssembly facilita o acesso a eventos usando o atributo @on{EVENT}. Todos os EventArgs que estamos acostumados a usar na ASP.NET são suportados.

Usamos expressões lambda para fornecer argumentos para os métodos chamados pelo evento. Usamos o atributo de diretiva preventDefault para evitar o comportamento padrão de um evento. Por fim, o método FocusAsync do tipo ElementReference é usado para atribuir o foco programaticamente a um elemento HTML.

Ao trabalhar com componentes, geralmente precisamos fornecer vários atributos. Usando o recurso splatting de atributo, podemos evitar atribuir os atributos diretamente na marcação HTML.

Apresentando o splatting de atributo (espalhamento de atributo)

Quando um componente filho tem muitos parâmetros, pode ser tedioso atribuir cada um dos valores em HTML. Para evitar ter que fazer isso, podemos usar o recurso attribute splatting.

Usando este recursos, os atributos são capturados em um dicionário e, em seguida, passados para o componente como uma unidade.

Um atributo é adicionado por entrada de dicionário e o dicionário deve implementar IEnumerable<KeyValuePair<string,object>> ou IReadOnlyDictionary<string, object> com chaves de string.

Para referenciar o dicionário usamos a diretiva @attributes.

Este é o código de um componente chamado BweButton.razor que possui vários parâmetros diferentes:

<button type="@Type"
   class="@Class"  disabled="@Disabled"  title="@Title"  @onclick="@ClickEvent">
    @ChildContent
</button>

@code {
   [Parameter] public string? Class { get; set; }
   [Parameter] public bool Disabled { get; set; }
   [Parameter] public string? Title { get; set; }
   [Parameter] public string? Type { get; set; }
   [Parameter] public EventCallback ClickEvent { get; set; }
   [Parameter] public RenderFragment? ChildContent { get; set; }
}
 

Abaixo temos um exemplo de código de marcação para renderizar um componente BweButton sem usar o recurso splatting de atributo:

<BweButton Class="button button-red" 
           Disabled="false"
           Title="Este é um botão vermelho"
           Type="button"
           ClickEvent="OnClickHandler">
    Enviar
</BweButton>

Abaixo temos o estilo CSS que vamos aplicar a este botão :

.button {
    color: white;
    cursor: pointer;
    padding: 2em;
}
.button-red {
    background-color: red;
}
.button-black {
    background-color: black;
}

Neste código de estilo CSS todos os elementos na classe button terão texto branco e 2em de preenchimento. Os elementos na classe button-red terão uma cor de fundo vermelha, e , esses elementos na classe button preto terão uma cor de fundo preta.

Usando o recurso attribute splatting, podemos simplificar a marcação anterior usando o seguinte código:

<BweButton @attributes="InputAttributes" ClickEvent="OnClickHandler">
      Enviar
</BweButton>
 

Esta é a definição de InputAttributes usada pelo código de marcação anterior:

public Dictionary<string, object> InputAttributes { get; set; } =
    new ()  
    {
        { "Class", "button button-red"},
        { "Disabled", false},
        { "Title", "Este é um botão vermelho" },
        { "Type", "submit" }
    };

Neste código definimos os InputAttributes que são passados para o button BweButton. O botão resultante é idêntico ao anterior onde definimos os atributos diretamente sem usar InputAttributes.

O poder real do splatting de atributos é percebido quando ele é combinado com parâmetros arbitrários.

Usando parâmetros arbitrários

No exemplo a anterior, usamos parâmetros explicitamente definidos para atribuir os atributos de um buton.

Uma maneira muito mais eficiente de atribuir valores a atributos é usar parâmetros arbitrários.

Um parâmetro arbitrário é um parâmetro que não é explicitamente definido pelo componente. O atributo Parameter tem uma propriedade CaptureUnmatchedValues que é usada para permitir que o parâmetro capture valores que não correspondem a nenhum dos outros parâmetros.

A seguir temos uma nova versão do nosso botão chamado BweButton2. Ele usa parâmetros arbitrários:

<button @attributes="InputAttributes" >
    @ChildContent
</button>

@code {

    [Parameter(CaptureUnmatchedValues = true)]
    public Dictionary<string, object>? InputAttributes{get; set;}

    [Parameter]
    public RenderFragment? ChildContent { get; set; }
}

Neste código temos  um parâmetro chamado InputAttributes que tem sua propriedade  CaptureUnmatchedValues definida como true.

Nota: Um componente pode ter apenas um parâmetro com sua propriedade CaptureUnmatchedValues definida como true.

A seguir temo o código de marcação atualizado usado para renderizar a nova versão do nosso botão:

<BweButton2 @attributes="InputAttributes2" @onclick="OnClickHandler"
            class="button button-black">
    Enviar
</BweButton2>

A definição para InputAttributes2 usada pelo código de marcação é dada a seguir:

public Dictionary<string, object> InputAttributes2 { get; set; } =
    new()
    {
        { "class", "button button-red" },
        { "title", "Este é outro botão" },
        { "name", "btnSubmit" },
        { "type", "button" },
        { "myAttribute", "123"}
    };

Embora nenhum dos atributos do dicionário tenha sido explicitamente definido na nova versão do nosso botão, o BweButton2 ainda é renderizado. No exemplo anterior, o atributo de classe é definido duas vezes.

O motivo pelo qual o botão agora está preto é devido à posição da diretiva @attributes na marcação do botão. Quando os atributos são salpicados em um elemento, eles são processados da esquerda para a direita. Portanto, se houver atributos duplicados atribuídos, o que aparecer mais tarde na ordem será o usado.

Os parâmetros arbitrários são usados para permitir que atributos previamente indefinidos sejam renderizados pelo componente. Isso é útil com componentes que oferecem suporte a uma grande variedade de personalizações, como um componente que inclui um elemento de entrada.

Com esses conceitos podemos agora iniciar a criação do projeto blazor WebAssembly KanbanBoard.

Faremos isso na próxima parte do artigo ...

"Amai, pois, a vossos inimigos, e fazei bem, e emprestai, sem nada esperardes, e será grande o vosso galardão, e sereis filhos do Altíssimo; porque ele é benigno até para com os ingratos e maus."
Lucas 6:36

Referências:


José Carlos Macoratti