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> </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.org2. 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.
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"
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"
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!"
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!"
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:{
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!"
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