Por: @rudineigr
Publicado em: 2019-05-29
Reordenação de itens por Drag&Drop

Muitas aplicações precisam ordenar itens, por exemplo as regras de firewall, que precisam ter uma ordem definida para garantir que os filtros aplicados funcionem corretamente. Para tal, deve ser implementado algum mecanismo de ordenação destas regras, que fique registrado em banco de dados para geração das regras posteriormente em arquivo.
O formato de ordenação do v2, por se tratar de arquivos e não um banco de dados, era feito em cima das linhas do arquivo de conf, onde era possível trocar uma linha de posição por botões de ▲ e ▼, que colocavam a linha para cima ou para baixo. Para tal, o arquivo era lido por completo e reescrito inteiro toda vez que uma linha era alterada. Isto causava uma série de problemas, principalmente se havia edições em paralelo, a solução para isto foi implementar um lock de edição no arquivo de conf.
No v3, as regras são mais cadastradas em confs, agora são cadastradas em banco de dados, e para realizar a ordenação dos campos foi adicionado um campo order
nas regras, que permitiu o usuário digitar um número o qual seria utilizado para ordenar as regras. Isto não é muito prático para o usuário, o ideal seria o usuário poder “arrastar” as regras para a posição que ele quer que ela esteja.

Para a aplicação permitir isso, teriam algumas alternativas:
- O campo order armazenar a posição do item, simulando um array ou a posição da regra no arquivo;
- O frontend atualizar o campo ordem para conseguir colocar o item na posição desejada;
- O backend realizar a alteração de posição com base na posição dos itens adjacentes.
As duas primeiras são mais simples do ponto de vista de implementação, mas tem falhas graves. Na primeira alternativa, sempre que um item tiver sua posição alterada, todos os itens precisariam ser atualizados para suas novas posições. Na segunda alternativa, para realizar a alteração da posição do item, o frontend precisaria garantir que os dados que estão em tela estão atualizados, para evitar que seja alterado um item que foi modificado por outra pessoa. Estas duas alternativas podem ocasionar os mesmos problemas que havia na ordenação das linhas do arquivo do v2, de apagar alterações realizadas por outros usuários, além de não considerarem que pode haver sido cadastrado outras regras nesse meio tempo.
Na terceira alternativa, o backend seria responsável por reordenar o item, utilizando como informação os itens adjacentes que devem ser utilizados para determinar a posição do item. A implementação deste formato pode ser feita utilizando o campo order
que já existe nestes cadastros ordenados, mas removeria o controle dele da API/usuário e passaria o controle dele para o backend. O algoritmo de ordenação deste formato funcionaria da seguinte forma:
- Um item quando cadastrado é inserido com um
order
1000 acima do maiororder
que existir no banco, se não existir nenhum, é inserido com o valor 1000; - Para alterar a ordenação de um item, é utilizado os campos
order
dos itens adjacentes a posição desejada e colocado o valor médio entre os valores deorder
dos itens adjacentes;
Segue abaixo algumas ilustrações de como funcionaria a ordenação. Partindo de um banco cadastrado seguindo a lógica de cadastro adicionando 1000 no order, seria criado o seguinte banco:

Para alterar a ordem do item DD
para ele ser o segundo da lista, será necessário informar para o backend que DD
deve ser posicionado depois de AA
e antes de BB
, da seguinte forma:
curl \
-X PUT \
-H 'Content-Type: application/json' \
-d '{"after": 1, "before": 2}' \
http://localhost/api/items/4/order
Para alteração da ordem, será utilizado os campos order
de AA
e de BB
. Para o calculo do novo valor de order
de DD
é utilizado a diferença dos order
dos campos adjacentes, dividido por dois e somado ao order
de menor valor, resultado na seguinte disposição:

Também é possível realizar reordenações para posições limites da lista, como início e fim, repassando somente o último item como referência da última posição. Por exemplo, para colocar o BB
como último item da lista, seria necessário passar somente o id
de CC
:
curl \
-X PUT \
-H 'Content-Type: application/json' \
-d '{"after": 3, "before": null}' \
http://localhost/api/items/2/order
Para tal, é utilizado o order
do item para gerar um valor maior que o último item. Por padrão pode ser utilizado 1000 para ter um espaço maior entre os valores de order
. O resultado neste caso seria o seguinte:

O mesmo vale para para alteração de um item para ele ser o primeiro da lista, deve ser repassado o id
do primeiro item como referência da primeira posição. Por exemplo, para alterar o CC
para ser o primeiro item, deve ser passado a referência do AA
:
curl \
-X PUT \
-H 'Content-Type: application/json' \
-d '{"after": null, "before": 1}' \
http://localhost/api/items/3/order
E o resultado será o seguinte:

O único problema deste formato é quando não há espaço entre os campos order
de dois itens para inserir um item. Por exemplo, na seguinte situação abaixo:

Nesta situação, caso for reordenar o item DD
para ele ficar entre o BB
e o CC
, não teria espaço entre os dois valores de order
dos itens. A solução neste caso é realizar uma renumeração do campo order
da lista inteira, colocando um intervalo de 1000 entre os itens. Após realizado isto, o DD
pode ser reordenado. O resultado disto seria o seguinte:

Este seria o único caso onde seria realmente necessário alterar todos os itens da lista, mas não vai acontecer com frequência e ele leva em conta os itens que estão no banco para reordenar, não os itens que estão em tela, o que garante que os dados vão estar corretos no momento da reordenação.
Um protóprio desta lógica está disponível no repositório abaixo:
https://git.itflex.com.br/prototipos/ordenacao-drag-and-drop-api