Por favor, trate esta questão como estritamente educacional. Ainda estou interessado em ouvir novas respostas e idéias para implementar isso
tl; dr
Como eu implementaria a ligação de dados bidirecional com JavaScript?
Ligação de dados ao DOM
Por ligação de dados ao DOM, quero dizer, por exemplo, ter um objeto JavaScript a
com uma propriedade b
. Então, ter um <input>
elemento DOM (por exemplo), quando o elemento DOM for alterado, a
alterado e vice-versa (ou seja, a ligação de dados bidirecional).
Aqui está um diagrama do AngularJS sobre como isso se parece:
Então, basicamente, eu tenho JavaScript semelhante a:
var a = {b:3};
Em seguida, um elemento de entrada (ou outro formulário) como:
<input type='text' value=''>
Gostaria que o valor da entrada fosse a.b
o valor de (por exemplo) e, quando o texto da entrada mudar, eu também gostaria a.b
de mudar. Quandoa.b
JavaScript muda, a entrada muda.
A questão
Quais são algumas técnicas básicas para fazer isso em JavaScript simples?
Especificamente, gostaria de uma boa resposta para me referir a:
- Como a ligação funcionaria para objetos?
- Como ouvir mudanças no formulário pode funcionar?
- De uma maneira simples, é possível ter apenas o HTML modificado no nível do modelo? Gostaria de não acompanhar a ligação no próprio documento HTML, mas apenas em JavaScript (com eventos DOM e JavaScript mantendo referência aos elementos DOM usados).
O que eu tentei?
Sou um grande fã de Bigode, então tentei usá-lo para modelar. No entanto, tive problemas ao tentar executar a ligação de dados em si, já que o Bigode processa o HTML como uma string. Assim, depois de obter o resultado, não tenho referência a onde estão os objetos no meu viewmodel. A única solução alternativa que pude pensar nisso foi modificar a sequência de caracteres HTML (ou a árvore DOM criada) com atributos. Não me importo de usar um mecanismo de modelo diferente.
Basicamente, tive a forte sensação de que estava complicando o problema em questão e há uma solução simples.
Nota: Não forneça respostas que usem bibliotecas externas, especialmente aquelas com milhares de linhas de código. Eu usei (e gosto!) AngularJS e KnockoutJS. Eu realmente não quero respostas no formulário 'use framework x'. Idealmente, eu gostaria de um futuro leitor que não saiba usar muitas estruturas para entender como implementar a ligação de dados bidirecional. Não espero uma resposta completa , mas uma que transmita a ideia.
fonte
Respostas:
Uma abstração que atualiza os dois objetos
Suponho que existem outras técnicas, mas, em última análise, eu teria um objeto que contém referência a um elemento DOM relacionado e fornece uma interface que coordena as atualizações de seus próprios dados e seu elemento relacionado.
O
.addEventListener()
fornece uma interface muito agradável para isso. Você pode atribuir a ele um objeto que implementa aeventListener
interface e ele invocará seus manipuladores com esse objeto comothis
valor.Isso fornece acesso automático ao elemento e aos dados relacionados.
Definindo seu objeto
A herança prototípica é uma boa maneira de implementar isso, embora não seja necessária, é claro. Primeiro, você criaria um construtor que recebe seu elemento e alguns dados iniciais.
Então aqui o construtor armazena o elemento e os dados nas propriedades do novo objeto. Também liga um
change
evento ao dadoelement
. O interessante é que ele passa o novo objeto em vez de uma função como o segundo argumento. Mas isso por si só não vai funcionar.Implementando a
eventListener
interfacePara fazer isso funcionar, seu objeto precisa implementar o
eventListener
interface. Tudo o que é necessário para fazer isso é fornecer umhandleEvent()
método ao objeto .É aí que a herança entra.
Há muitas maneiras diferentes de estruturar isso, mas, para o seu exemplo de coordenação de atualizações, decidi fazer com que o
change()
método aceite apenas um valor e tenha ohandleEvent
passe esse valor em vez do objeto de evento. Dessa forma, tambémchange()
pode ser chamado sem um evento.Então agora, quando o
change
evento acontecer, ele atualizará o elemento e a.data
propriedade. E o mesmo acontecerá quando você ligar.change()
seu programa JavaScript.Usando o código
Agora você acabou de criar o novo objeto e deixá-lo realizar atualizações. As atualizações no código JS aparecerão na entrada e os eventos de alteração na entrada serão visíveis para o código JS.
DEMO: http://jsfiddle.net/RkTMD/
fonte
Mustache.render(template,object)
, assumindo que quero manter um objeto sincronizado com o modelo (não específico do Bigode), como eu continuaria com isso?input
devem atualizar um desses pontos, haveria um mapeamento da entrada para esse ponto. Vou ver se consigo criar um exemplo rápido.Template
construtor primário que faz a análise, mantém os diferentesMyCtor
objetos e fornece uma interface para atualizar cada um por seu identificador. Deixe-me saber se você tem perguntas. :) EDIT: ... use esse link em vez disso ... Esqueci que havia um aumento exponencial no valor de entrada a cada 10 segundos para demonstrar as atualizações do JS. Isso limita isso.Então, eu decidi jogar minha própria solução no pote. Aqui está um violino de trabalho . Observe que isso é executado apenas em navegadores muito modernos.
O que ele usa
Essa implementação é muito moderna - requer um navegador (muito) moderno e os usuários duas novas tecnologias:
MutationObserver
s para detectar mudanças no dom (ouvintes de eventos também são usados)Object.observe
para detectar mudanças no objeto e notificar o dom. Perigo, uma vez que esta resposta foi escrita Oo foi discutido e decidido pelo ECMAScript TC, considere um polyfill .Como funciona
domAttribute:objAttribute
mapeamento - por exemplobind='textContent:name'
A solução
Aqui está a
dataBind
função, observe que são apenas 20 linhas de código e podem ser mais curtas:Aqui está um pouco de uso:
HTML:
JavaScript:
Aqui está um violino de trabalho . Observe que esta solução é bastante genérica. O shovel observador Object.observe e mutation está disponível.
fonte
obj.name
um setter não pode ser observado externamente, mas deve transmitir que ele mudou de dentro do setter - html5rocks.com/en/tutorials/es7/observe/#toc-notifications - meio que dispara uma chave nos trabalhos para Oo () se você quiser um comportamento mais complexo e interdependente usando setters. Além disso, quandoobj.name
não é configurável, redefinir seu setter (com vários truques para adicionar notificação) também não é permitido - portanto, os genéricos com Oo () são totalmente descartados nesse caso específico.Eu gostaria de adicionar ao meu preposter. Sugiro uma abordagem ligeiramente diferente que permitirá que você simplesmente atribua um novo valor ao seu objeto sem usar um método. Note-se, no entanto, que isso não é suportado por navegadores especialmente mais antigos e o IE9 ainda requer o uso de uma interface diferente.
O mais notável é que minha abordagem não utiliza eventos.
Getters e Setters
Minha proposta utiliza o recurso relativamente jovem de getters e setters , particularmente apenas de criadores. De um modo geral, os mutadores nos permitem "personalizar" o comportamento de como determinadas propriedades recebem um valor e são recuperadas.
Uma implementação que vou usar aqui é o método Object.defineProperty . Funciona no FireFox, GoogleChrome e - eu acho - IE9. Ainda não testei outros navegadores, mas como isso é apenas uma teoria ...
De qualquer forma, ele aceita três parâmetros. O primeiro parâmetro é o objeto para o qual você deseja definir uma nova propriedade, o segundo uma sequência semelhante ao nome da nova propriedade e o último um "objeto descritor" fornecendo informações sobre o comportamento da nova propriedade.
Dois descritores particularmente interessantes são
get
eset
. Um exemplo seria algo como o seguinte. Observe que o uso desses dois proíbe o uso dos outros 4 descritores.Agora, fazer uso disso se torna um pouco diferente:
Quero enfatizar que isso só funciona para navegadores modernos.
Violino de trabalho: http://jsfiddle.net/Derija93/RkTMD/1/
fonte
Proxy
objetos Harmony :) Setters parecem uma boa idéia, mas isso não exigiria que modificássemos os objetos reais? Além disso, em uma nota lateral -Object.create
poderia ser usado aqui (novamente, assumindo um navegador moderno que permitisse o segundo parâmetro). Além disso, o setter / getter pode ser usado para 'projetar' um valor diferente para o objeto e o elemento DOM :). Eu estou querendo saber se você tem quaisquer conhecimentos sobre templates também, que parece ser um verdadeiro desafio aqui, especialmente a estrutura bem :)Proxy
, como você disse.;) Entendi o desafio de manter duas propriedades distintas sincronizadas. Meu método elimina um dos dois.Proxy
eliminaria a necessidade de usar getters / setters, você poderia ligar elementos sem saber quais propriedades eles têm. O que eu quis dizer é que os getters podem mudar mais do que bindTo.value e podem conter lógica (e talvez até um modelo). A questão é como manter esse tipo de ligação bidirecional com um modelo em mente? Digamos que estou mapeando meu objeto para um formulário, gostaria de manter o elemento e o formulário sincronizados e estou me perguntando como eu continuaria com esse tipo de coisa. Você pode verificar como isso funciona na knockout learn.knockoutjs.com/#/?tutorial=intro por exemploAcho que minha resposta será mais técnica, mas não diferente, pois as outras apresentam a mesma coisa usando técnicas diferentes.
Então, para começar, a solução para esse problema é o uso de um padrão de design conhecido como "observador", permite dissociar os dados da apresentação, fazendo com que a alteração de uma coisa seja transmitida aos ouvintes, mas neste caso é feito de mão dupla.
Pela maneira DOM para JS
Para vincular os dados do DOM ao objeto js, você pode adicionar marcações na forma de
data
atributos (ou classes, se precisar de compatibilidade), assim:Dessa forma, ele pode ser acessado via js usando
querySelectorAll
(ou o velho amigogetElementsByClassName
para compatibilidade).Agora você pode vincular o evento ouvindo as alterações de maneiras: um ouvinte por objeto ou um grande ouvinte no contêiner / documento. A ligação ao documento / contêiner disparará o evento para cada alteração feita nele ou em um filho, ele terá um espaço menor de memória, mas gerará chamadas de eventos.
O código será algo como isto:
Pela maneira JS do DOM
Você precisará de duas coisas: um meta-objeto que conterá as referências do elemento DOM da bruxa será vinculado a cada objeto / atributo js e uma maneira de ouvir as alterações nos objetos. É basicamente da mesma maneira: você precisa ter uma maneira de ouvir as alterações no objeto e vinculá-lo ao nó DOM, pois seu objeto "não pode ter" metadados, você precisará de outro objeto que contenha metadados de uma maneira que o nome da propriedade mapeia para as propriedades do objeto de metadados. O código será algo como isto:
Espero ter ajudado.
fonte
Object.observe
pois o suporte está presente apenas no chrome por enquanto. caniuse.com/#feat=object-observeOntem, comecei a escrever minha própria maneira de vincular dados.
É muito engraçado brincar com isso.
Eu acho bonito e muito útil. Pelo menos nos meus testes usando Firefox e Chrome, o Edge também deve funcionar. Não tenho certeza sobre os outros, mas se eles suportam Proxy, acho que funcionará.
https://jsfiddle.net/2ozoovne/1/
Aqui está o código:
Então, para definir, apenas:
Por enquanto, acabei de adicionar a ligação de valor HTMLInputElement.
Deixe-me saber se você sabe como melhorá-lo.
fonte
Há uma implementação muito simples de ligação de dados bidirecional neste link "Ligação de dados bidirecional fácil em JavaScript"
O link anterior, juntamente com as idéias de knockoutjs, backbone.js e agility.js, levaram a essa estrutura MVVM leve e rápida, ModelView.js
baseado em jQueryque joga bem com jQuery e do qual eu sou o humilde (ou talvez não tão humilde) autor.Reproduzindo o código de exemplo abaixo (no link da postagem do blog ):
Código de amostra para DataBinder
fonte
Alterar o valor de um elemento pode acionar um evento DOM . Ouvintes que respondem a eventos podem ser usados para implementar a ligação de dados em JavaScript.
Por exemplo:
Aqui está o código e uma demonstração que mostra como os elementos DOM podem ser ligados entre si ou com um objeto JavaScript.
fonte
Vincular qualquer entrada html
defina duas funções:
use as funções:
fonte
Aqui está uma idéia usando a
Object.defineProperty
qual modifica diretamente a maneira como uma propriedade é acessada.Código:
Uso:
violino: Aqui
fonte
Passei por um exemplo básico de javascript usando manipuladores de eventos onkeypress e onchange para criar uma visualização vinculativa aos nossos js e js para visualizar
Aqui exemplo de plunker http://plnkr.co/edit/7hSOIFRTvqLAvdZT4Bcc?p=preview
fonte
fonte
Uma maneira simples de vincular uma variável a uma entrada (ligação bidirecional) é acessar diretamente diretamente o elemento de entrada no getter e setter:
Em HTML:
E para usar:
Uma maneira mais sofisticada de fazer o que precede sem getter / setter:
Usar:
fonte
É uma ligação de dados bidirecional muito simples em javascript vanilla ....
fonte
Tarde para a festa, especialmente desde que eu escrevi 2 libs relacionadas há meses / anos atrás, eu as mencionarei mais tarde, mas ainda parece relevante para mim. Para torná-lo um spoiler muito curto, as tecnologias de minha escolha são:
Proxy
para observação do modeloMutationObserver
para as alterações de rastreamento do DOM (por motivos vinculativos, não alterações de valor)addEventListener
manipuladores regularesIMHO, além do PO, é importante que a implementação da vinculação de dados:
user.address.block
shift
,splice
e afins)Levando tudo isso em consideração, na minha opinião, é impossível lançar apenas algumas dezenas de linhas JS. Eu tentei fazer isso como um padrão e não como lib - não funcionou para mim.
Em seguida, ter
Object.observe
é removido e, ainda assim, dado que a observação do modelo é parte crucial - toda essa parte DEVE ser separada por uma outra lib. Agora, ao ponto de diretores de como eu resolvi esse problema - exatamente como o OP perguntou:Modelo (parte JS)
Minha opinião sobre a observação do modelo é Proxy , é a única maneira sensata de fazê-lo funcionar, IMHO. O recurso completo
observer
merece sua própria biblioteca, então eu desenvolvi aobject-observer
biblioteca para esse único objetivo.O (s) modelo (s) deve (m) ser registrado (s) através de uma API dedicada, esse é o ponto em que os POJOs se transformam em
Observable
s, não pode ver nenhum atalho aqui. Os elementos DOM que são considerados vistas limitadas (veja abaixo) são atualizados com os valores do (s) modelo (s) primeiro e depois a cada alteração de dados.Visualizações (parte HTML)
IMHO, a maneira mais limpa de expressar a ligação, é através de atributos. Muitos fizeram isso antes e muitos farão depois; portanto, não há notícias aqui, esta é apenas a maneira certa de fazer isso. No meu caso, segui a seguinte sintaxe:,
<span data-tie="modelKey:path.to.data => targerProperty"></span>
mas isso é menos importante. O que é importante para mim, nenhuma sintaxe de script complexa no HTML - isso está errado, novamente, IMHO.Todos os elementos designados como vistas limitadas devem ser coletados primeiro. Parece-me inevitável, do ponto de vista do desempenho, gerenciar alguns mapeamentos internos entre os modelos e as visualizações, parece o caso certo em que memória + algum gerenciamento deve ser sacrificado para salvar pesquisas e atualizações em tempo de execução.
As visualizações são atualizadas a partir do modelo, se disponíveis, e após alterações posteriores, como dissemos. Mais ainda, todo o DOM deve ser observado por meio de
MutationObserver
, a fim de reagir (ligar / desligar) nos elementos adicionados / removidos / alterados dinamicamente. Além disso, tudo isso deve ser replicado no ShadowDOM (um aberto, é claro) para não deixar buracos negros não ligados.De fato, a lista de detalhes pode ir mais longe, mas esses são, na minha opinião, os principais princípios que implementariam a ligação de dados com um bom equilíbrio entre a integridade dos recursos de um e a simplicidade sadia do outro lado.
E assim, além do
object-observer
mencionado acima, eu escrevi também umadata-tier
biblioteca, que implementa a ligação de dados ao longo dos conceitos mencionados acima.fonte
As coisas mudaram muito nos últimos 7 anos, temos componentes da Web nativos na maioria dos navegadores agora. Na IMO, o núcleo do problema é o compartilhamento de estado entre elementos, uma vez que é trivial atualizar a interface do usuário quando o estado muda e vice-versa.
Para compartilhar dados entre elementos, você pode criar uma classe StateObserver e estender seus componentes da Web a partir disso. Uma implementação mínima se parece com isso:
mexer aqui
Eu gosto dessa abordagem porque:
data-
propriedadesfonte