Dap em ação. Escrevendo TodoMVC. Parte 1

O primeiro artigo sobre a dap , obviamente, não se tornou meu sucesso de escrita: a grande maioria dos comentários a ele se resumia a "niasilil" e "niasilil, mas eu condeno". E o prêmio do único comentário construtivo de nível superior vai para o OldVitus , por conselhos para demonstrar o dap usando TodoMVC como exemplo, para que haja algo para comparar. O que farei neste artigo.

TodoMVC , se alguém não sabe, esse é um mundo de chamadas de interface do usuário padrão, que permite comparar soluções para o mesmo problema - a "lista de tarefas" condicional - usando estruturas diferentes. A tarefa, com toda a sua simplicidade (sua solução no dap quebra “em uma tela”), é muito ilustrativa. Portanto, usando seu exemplo, tentarei mostrar como as tarefas típicas de um front-end da web são implementadas usando dap.

Não procurei e estudei a descrição formal do problema, mas decidi simplesmente reverter um dos exemplos. O back-end deste artigo não é interessante para nós, portanto, não o escreveremos, mas usaremos um dos já prontos no site www.todobackend.com e, a partir daí, levaremos um cliente de exemplo e um arquivo CSS padrão.

Para usar o dap, você não precisa baixar e instalar nada. Nenhuma npm install e é tudo. Não é necessário criar nenhum projeto com uma estrutura de diretórios específica, manifestos e outros atributos de sucesso da TI. Editor de texto e navegador suficientes. Para depurar solicitações XHR, você também pode precisar de um servidor Web - um servidor bastante simples, como esta extensão para o Chrome. Todo o frontend consistirá em um único arquivo .html (é claro, referindo-se ao script dap-engine e ao arquivo CSS TodoMVC padrão)

Então, do zero.

1. Crie um arquivo .html


 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Todo -- dap sample</title> <link rel="stylesheet" href="https://www.todobackend.com/client/css/vendor/todomvc-common.css"/> <script src="https://dap.js.org/0.4.js"></script> </head> <body> <script> //   dap </script> </body> </html> 

A preparação html usual em que incluímos o arquivo CSS, gentilmente fornecido pelo site www.todobackend.com e o dap-engine, não menos gentilmente fornecido pelo site dap.js.org

2. Copie a estrutura DOM do exemplo original


Para usar o arquivo CSS padrão sem alterações, aderiremos à mesma estrutura DOM do exemplo original . Abra-o no navegador Chrome, pressione Ctr + Shift + I, selecione a guia Elementos e veja se o aplicativo está na section id="todo-app"> elemento section id="todo-app">



Ao abrir esta subárvore uma a uma, reescrevemos sua estrutura em nosso arquivo .html. Agora, estamos simplesmente desenhando de maneira rápida, e não escrevendo código, apenas escrevemos as assinaturas dos elementos em 'aspas simples' e entre parênteses de seus filhos. Se não houver filhos, desenhamos colchetes vazios. Monitoramos os índices e o saldo dos colchetes.

 //   dap '#todoapp'( '#header'( 'H1'() 'INPUT#new-todo placeholder="What needs to be done?" autofocus'() ) '#main'( '#toggle-all type=checkbox'() 'UL#todo-list'( 'LI'( 'INPUT.toggle type=checkbox'() 'LABEL'() 'BUTTON.destroy'() ) ) ) '#footer'( '#todo-count'() 'UL#filters'( 'LI'() ) '#clear-completed'() ) ) 

Nota: repetindo elementos (por exemplo, aqui estão elementos LI ), escrevemos na estrutura uma vez, mesmo se houver vários deles no original; obviamente, essas são matrizes do mesmo padrão.

O formato da assinatura, eu acho, é compreensível para quem escreveu HTML e CSS com as mãos, então não vou me aprofundar em detalhes por enquanto. Só posso dizer que as etiquetas são escritas em letras maiúsculas e a ausência de uma etiqueta é equivalente à presença de uma etiqueta DIV. A abundância de elementos # (com ID) aqui é devida às especificidades do arquivo CSS incluído, que usa principalmente seletores de identificação.

3. Lembre-se de que o programa dap é Javascript


Para nos salvar de colchetes desnecessários no código, o mecanismo dap injeta vários métodos diretamente no String.prototype (eu sei que implementar seus métodos em objetos padrão é ayahay, mas ... em suma, passamos), que converte a string de assinatura em dap modelo. Um desses métodos é .d(rule, ...children) . O primeiro argumento necessário é uma regra de geração (regra -d ), e o restante dos argumentos é um número arbitrário de filhos.

Com base nesse novo conhecimento, adicionamos nosso código para que, em vez de cada chave de abertura, tenhamos a sequência .d("" e antes de cada citação simples de abertura, exceto a primeira, há uma vírgula. Life hack: você pode usar a substituição automática.

 '#todoapp'.d("" ,'#header'.d("" ,'H1'.d("") ,'INPUT#new-todo placeholder="What needs to be done?" autofocus'.d("") ) ,'#main'.d("" ,'#toggle-all type=checkbox'.d("") ,'UL#todo-list'.d("" ,'LI'.d("" ,'INPUT.toggle type=checkbox'.d("") ,'LABEL'.d("") ,'BUTTON.destroy'.d("") ) ) ) ,'#footer'.d("" ,'#todo-count'.d("") ,'UL#filters'.d("" ,'LI'.d("") ) ,'#clear-completed'.d("") ) ) 

Voila! Temos uma árvore de chamadas para o método .d , que está pronto para se transformar em um modelo dap. As cadeias vazias "" são as sementes das futuras regras d e os filhos são argumentos separados por vírgulas. Formalmente, este é um programa dap válido, embora ainda não completamente com a exaustão de que precisamos. Mas já pode ser lançado! Para fazer isso, após o fechamento do colchete raiz, adicione o método .RENDER() . Esse método, como o próprio nome indica, renderiza o modelo resultante.

Portanto, nesta fase, temos um arquivo .html com este conteúdo:

 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Todo -- dap sample</title> <link rel="stylesheet" href="https://www.todobackend.com/client/css/vendor/todomvc-common.css"/> <script src="https://dap.js.org/0.4.js"></script> </head> <body> <script> '#todoapp'.d("" ,'#header'.d("" ,'H1'.d("") ,'INPUT#new-todo placeholder="What needs to be done?" autofocus'.d("") ) ,'#main'.d("" ,'#toggle-all type=checkbox'.d("") ,'UL#todo-list'.d("" ,'LI'.d("" ,'INPUT.toggle type=checkbox'.d("") ,'LABEL'.d("") ,'BUTTON.destroy'.d("") ) ) ) ,'#footer'.d("" ,'#todo-count'.d("") ,'UL#filters'.d("" ,'LI'.d("") ) ,'#clear-completed'.d("") ) ) .RENDER() //   dap   </script> </body> </html> 

Você pode abri-lo em um navegador para garantir que os elementos DOM sejam gerados, os estilos CSS sejam aplicados, resta apenas preencher este modelo com dados.

4. Obtenha os dados


Vamos para a página original , abrimos a guia Rede nas ferramentas, ligamos o filtro XHR e vemos de onde vêm os dados e de que forma.





Ok, ok. A lista de tarefas é obtida diretamente de todo-backend-express.herokuapp.com como uma matriz json de objetos. Ótimo

Para receber dados, a dap possui um conversor :query que "converte" de forma assíncrona o URL nos dados recebidos dele. A própria URL, não escreveremos diretamente na regra, e a denotaremos pela constante todos ; todo o design de mineração de dados ficará assim:

 todos:query 

e escreva a constante todos no dicionário - na seção .DICT , logo antes de .RENDER() :

 '#todoapp'.d("" ... ) .DICT({ todos : "https://todo-backend-express.herokuapp.com/" }) .RENDER() 

Após receber a matriz todos , construímos uma lista de tarefas: para cada caso, pegamos o nome do campo .title e o escrevemos no elemento LABEL , e no campo .completed o sinal de “completude” e INPUT.toggle a propriedade checked do elemento INPUT.toggle da checked INPUT.toggle . É feito assim:

  ,'UL#todo-list'.d("*@ todos:query" //  *       ,'LI'.d("" ,'INPUT.toggle type=checkbox'.d("#.checked=.completed") // #  " " ,'LABEL'.d("! .title") //  !      ,'BUTTON.destroy'.d("") ) ) 

Atualizamos esta página no navegador e ... se você a iniciar no sistema de arquivos, nada acontece. O problema é que os navegadores modernos não permitem solicitações XHR entre domínios de documentos locais.



É hora de assistir nossa página via http - usando qualquer servidor web local. Bem, ou se você não estiver pronto para escrever o dap com suas próprias mãos, veja versões seqüenciais da página usando meus links (não se esqueça de olhar a fonte - no Chrome, isso é feito usando Ctrl + U)

Então, vamos à nossa página em http: // e vemos que os dados estão chegando, a lista está sendo construída. Ótimo! Você já dominou os operadores * e ! , conversor :query , constantes e acesso aos campos do elemento atual da matriz. Veja novamente o código resultante. Ainda parece ilegível para você?

5. Adicione estado


Talvez você já tenha tentado clicar nas marcas de seleção na lista de tarefas. As próprias caixas de seleção mudam de cor, mas, diferentemente do original, o elemento pai do LI não altera seu estilo (o "trabalho concluído" deve ficar cinza e riscado, mas isso não acontece) - as coisas não mudam de estado . Mas esses elementos ainda não possuem nenhum estado e, portanto, não podem alterá-lo. Agora vamos consertar isso.

Adicione um estado de "conclusão" ao elemento LI . Para fazer isso, defina a variável de estado $completed em sua regra-d. Para o INPUT.toggle , que pode alterar esse estado, atribuiremos uma regra de reação apropriada (regra da interface do INPUT.toggle ), que definirá a variável $completed acordo com seu próprio sinalizador checked (“a daw está ativada”). Dependendo do estado de $completed elemento LI ativará ou desativará a classe CSS "concluída".

  ,'UL#todo-list'.d("*@ todos:query" ,'LI'.d("$completed=.completed"//  ,    .completed ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") //       .ui("$completed=#.checked") //    $completed ,'LABEL'.d("! .title") ,'BUTTON.destroy'.d("") ) .a("!? $completed") //     $completed,    css- completed ) 

Tais manipulações com classes CSS são uma coisa bastante comum; portanto, existe um operador especial no dap para elas !?
Observe que fazemos isso na regra geral (da palavra acumular). Por que não na regra d? A diferença entre esses dois tipos de regras é que, ao atualizar, a regra d reconstrói completamente o conteúdo do elemento, excluindo o antigo e o novo novamente, enquanto a regra geral não toca no conteúdo existente do elemento, mas “anexa” o resultado ao que já está lá. A alteração de um único atributo de um elemento LI não requer a reestruturação do restante de seu conteúdo; portanto, é mais racional fazer isso na regra.

Nós olhamos para o resultado . Já é melhor: clicar nas caixas de seleção altera o estado do item de pendência correspondente e, de acordo com esse estado, o estilo visual do elemento também muda. Mas ainda há um problema: se a lista de tarefas concluídas estiver presente inicialmente, elas não ficarão cinza, porque, por padrão, a regra geral não é executada quando o elemento é gerado. Para executá-lo mesmo durante a geração, adicionamos o operador a! À regra d do elemento LI

  ,'LI'.d("$completed=.completed; a!" //      $completed    a- 

Nós olhamos . Ok Com o status de $completed resolvido. Os casos concluídos são estilizados corretamente na inicialização inicial e durante a alternância manual subsequente.

6. Editando nomes de casos


Voltar ao original . Ao clicar duas vezes no nome do caso, o modo de edição é ativado, no qual esse nome pode ser alterado. É implementado de tal maneira que o modelo do modo de exibição "view" (com um daw, um título e um botão de exclusão) fica completamente oculto, e o elemento INPUT class="edit" é exibido. Faremos isso de maneira um pouco diferente - ocultaremos apenas o elemento LABEL , pois os outros dois elementos não interferem em nossa edição. Basta adicionar a classe view ao elemento LABEL .

Para o estado "edição", defina a variável de $editing no elemento LI . Inicialmente, ele (estado) é redefinido, dblclick por dblclick no elemento LABEL e desativado quando o elemento INPUT.edit está fora de foco. Então escrevemos:

  ,'LI'.d("$completed=.completed $editing=; a!" //       ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") .ui("$completed=#.checked") ,'LABEL.view' .d("? $editing:!; ! .title") //  $editing ,     .e("dblclick","$editing=`yes") //  dblclick  $editing ,'INPUT.edit' .d("? $editing; !! .title@value") //  $editing  .ui(".title=#.value") //  .title   change (ui     INPUT) .e("blur","$editing=") //  $editing   blur ,'BUTTON.destroy'.d("") ).a("!? $completed $editing") //   $completed  $editing  css-  'LI' 

Agora podemos editar os nomes dos casos.

7. Enviando dados para o servidor


Ok, já podemos editar as coisas no navegador, mas essas alterações também devem ser transferidas para o servidor. Observamos como o original faz:



As alterações são enviadas ao servidor usando o método PATCH com um determinado URL no formato http://todo-backend-express.herokuapp.com/28185 , que, obviamente, é exclusivo para cada caso. Este URL é indicado pelo servidor no campo .url para cada caso na lista. Ou seja, tudo o que é necessário para atualizar o caso no servidor é enviar uma solicitação PATCH para o endereço especificado no campo .url , com os dados alterados no formato JSON:

  ,'INPUT.edit' .d("? $editing; !! .title@value") .ui(".title=#.value; (@method`PATCH .url (@Content-type`application/json)@headers (.title):json.encode@body):query") .e("blur","$editing=") 

Aqui usamos o mesmo conversor :query , mas em uma versão mais detalhada. Quando :query é aplicada a uma sequência simples, essa sequência é tratada como uma URL e uma solicitação GET é executada. Se :query recebe um objeto complexo, como neste caso, trata-o como uma descrição detalhada da solicitação que contém os campos .method , .url , .headers e .body e executa a solicitação de acordo com eles. Aqui, imediatamente após a atualização do .title enviamos ao servidor uma solicitação PATCH com este .title atualizado

Mas há uma nuance. Nós obtemos o campo .url do servidor, é algo como isto: http://todo-backend-express.herokuapp.com/28185 , ou seja, o protocolo http: // é codificado permanentemente se o nosso cliente também estiver aberto via http: // então está tudo bem. Mas se o cliente estiver aberto via https: //, surgirá um problema: por motivos de segurança, o navegador bloqueia o tráfego http da fonte https.

É resolvido de forma simples: se você remover o protocolo de .url , a solicitação passará pelo protocolo da página. Então vamos fazer isso: escreva o conversor apropriado - dehttp e passaremos .url por ele. Conversores personalizados (e outras funcionalidades) são .FUNC na seção .FUNC :

  .ui(".title=#.value; (@method`PATCH .url:dehttp (@Content-type`application/json)@headers (.title):json.encode@body):query") ... .FUNC({ convert:{ //  -         dehhtp: url=>url.replace(/^https?\:/,'')//    URL } }) 

Também faz sentido colocar o objeto de cabeçalhos no dicionário para que ele possa ser usado em outras consultas:

  .ui(".title=#.value; (@method`PATCH .url:dehttp headers (.title):json.encode@body):query") ... .DICT({ todos : "//todo-backend-express.herokuapp.com/", headers: {"Content-type":"application/json"} }) 

Bem, para o feng shui completo, usaremos outra propriedade útil do conversor :query - codificação automática do corpo da solicitação em json, de acordo com o cabeçalho Content-type:application/json . Como resultado, a regra ficará assim:

  .ui(".title=#.value; (@method`PATCH .url:dehttp headers (.title)):query") 

Então olha . Ok, os nomes dos casos agora mudam não apenas no navegador, mas também no servidor. Mas! Não apenas o nome do caso pode mudar, mas também seu estado de conclusão - completed . Portanto, ele também precisa ser enviado ao servidor.
Você pode adicionar a mesma solicitação PATCH ao elemento INPUT.toggle , basta enviar (.completed) vez de (.completed) :

  ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") .ui("$completed=#.checked; (@method`PATCH .url:dehttp headers (.completed:?)):query") 

E você pode colocar essa solicitação de PATCH “entre colchetes”:

  ,'LI'.d("$completed=.completed $editing= $patch=; a!" // $patch - ""   ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") .ui("$patch=($completed=#.checked)") //   $patch  completed ,'LABEL.view' .d("? $editing:!; ! .title") .e("dblclick","$editing=`yes") ,'INPUT.edit' .d("? $editing; !! .title@value") .ui("$patch=(.title=#.value)") //   $patch  title .e("blur","$editing=") ,'BUTTON.destroy'.d("") ) .a("!? $completed $editing") //  $patch  ,   ,   .u("? $patch; (@method`PATCH .url:dehttp headers $patch@):query $patch=") 

Aqui está a coisa. As regras de reação pertencem ao grupo “up-rules”, que é executado “de baixo para cima” - do descendente ao pai, até a própria raiz (essa sequência pode ser interrompida, se necessário). É como os eventos pop-up no DOM. Portanto, alguns fragmentos da reação comum a vários descendentes podem ser atribuídos a um ancestral comum.

Especificamente, no nosso caso, o ganho dessa delegação não é particularmente perceptível, mas se houvesse mais campos editáveis, colocar essa solicitação volumosa (pelos padrões dap, é claro) em uma regra geral ajudaria muito a manter o código simples e legível. Então eu recomendo.

Observamos : Agora, as alterações de nome e de status são enviadas ao servidor.

No próximo artigo, se você estiver interessado, considere adicionar, remover e filtrar casos. Enquanto isso, você pode ver o resultado final e outros exemplos de código dap em dap.js.org/docs

Source: https://habr.com/ru/post/pt480912/


All Articles