Neste post, vamos aprender sobre o binding handler “template”, como é preenchido, através do elemento DOM, de acordo com sua instância de “viewModel”. Um template é uma maneira simples para construir uma estrutura de interface, com repetição ou aninhada em blocos. Com ele é possível reaproveitar uma estrutura, por exemplo.
Parâmetros
* name: Nome do elemento que contem o template que deve ser utilizado.
* data: Objeto que fornece os dados para o template renderizar.
* if: O template só será processado, se a expressão especificada seja válida. Isso é útil para prever um “observable” nulo, e gerar um possível erro na renderização.
* foreach: Array para renderizar no template.
* as: Quando utilizado com o binding handler “foreach”, é possível especificar um “alias”, para acessar uma propriedade de forma mais amigável. Geralmente é utilizado para acessar estruturas aninhados, para evitar algo como: “$parent”, “$parents[1]”, “$parents[2]”, etc.
Isso facilita a manutenção, e identificação de eventuais erros, ao alterar a estrutura da sua classe de “viewModel”.
Também podemos utilizar funções de callback; Todas as funções são invocadas na “renderização” do template:
* afterRender: Executa depois da renderização.
* afterAdd e beforeRemove: Estes métodos são invocados, ao adicionar/remover algum elemento dinamicamente no template, respectivamente.
Vamos fazer um exemplo simples. Primeiro vamos definir um elemento, para utilizar como template:
<script type="text/html" id="pessoa-template"> <h3 data-bind="text: $data"></h3> </script>
Acima temos uma tag “script”, do tipo “text/html” (isso é importante, sem essa especificação, não funcionará como esperado). Também temos um id, sem ele não é possível referenciar o template.
No corpo do script, temos apenas uma tag “h3”, que por sua vez, coloca o valor de “text” da tag. No caso, qualquer valor primitivo, que informarmos. Para isso, utilizamos a variável de contexto “$data”.
Agora que temos o template definido, podemos utilizar ele. Para tal:
<div data-bind="template: { name: 'pessoa-template', data: nome }"></div>
Acimas temos uma tag “div”, que utiliza o binding handler “template”. Utilizamos apenas 2 parâmetros: “name” e “data”. O name é para referenciar o template que definimos no passo anterior. Já o parâmetro “data”, fizemos referencia a propriedade “nome” da classe “ViewModel” (vamos cria-la abaixo). Com isso, já é possível utilizar um template de uma forma bem simples.
E por último, vamos definir nossa classe “ViewModel”:
function ViewModel() { this.nome = "André Btoe"; } ko.applyBindings(new ViewModel());
Acima temos uma classe, com o nome “ViewModel”. Com a propriedade (não “observable”) chamada “nome”.
Veja o primeiro exemplo: http://jsfiddle.net/andrebtoe/dp14pg1q/.
A ordem de declaração de template / chamada do template, não tem importância. Nesse exemplo podemos inverter a ordem de declaração, que ainda sim, continuaria funcionando.
Utilizando foreach
Vamos utilizar o binding handler “foreach”. A classe “ViewModel” deve ficar como abaixo:
function ViewModel() { this.nomes = ["André Btoe", "José", "Maria"]; } ko.applyBindings(new ViewModel());
Acima trocamos a propriedade “nome” por “nomes”, e em vez de ser uma string, é um array de string. Com alguns valores definidos.
Já no html, devemos trocar o parâmetro “data” pelo binding handler “foreach”.
<div data-bind="template: { name: 'pessoa-template', foreach: nomes }"></div>
Veja o exemplo: http://jsfiddle.net/andrebtoe/dp14pg1q/1
Usando alias com foreach
Podemos utilizar um “apelido” quando o “foreach” é empregado. Vamos modificar o template:
<script type="text/html" id="pessoa-template"> <h3 data-bind="text: pessoaNome"></h3> </script>
Acima, definimos que vamos utilizar o alias “pessoaNome”, porem, o template não sabe se é um alias ou não; Apenas que deverá existir neste contexto.
E na chamada do template, vamos deixar desta forma:
<div data-bind="template: { name: 'pessoa-template', foreach: nomes, as: 'pessoaNome' }"></div>
Acima, definimos o parâmetro “as”, para informar um “apelido”, com o respectivo valor “pessoaNome”. Esse é um exemplo muito simples e de pouca utilidade. Mais adiante mostro utilidade neste recurso.
Veja o exemplo http://jsfiddle.net/andrebtoe/dp14pg1q/2
Utilizando métodos de callback
Às vezes você pode querer executar algo no pós-processamento sobre os elementos DOM gerados. Creio que na maioria das vezes o ideal é criar um binding handler, porem, se você quer apenas acessar os elementos DOM, pode usar alguns métodos de callback. Como vamos ver. Vamos modificar a chamada do template.
<div data-bind="template: { name: 'pessoa-template', foreach: nomes, as: 'pessoaNome', afterRender: posRender }"></div>
Acima, adicionamos o parâmetro “afterRender”. Ao finalizar a renderização de cada item do array de pessoas, o KO irá invocar o método definido como parâmetro de “afterRender”; No caso, o método se chama “posRender”.
E na classe de “ViewModel”, vamos definir o método “posRender”.
function ViewModel() { this.nomes = ["André Btoe", "José", "Maria"]; this.posRender = function(elements, valor){ console.log(elements[1].innerText); console.log(elements[1].innerText); }; } ko.applyBindings(new ViewModel());
Acima adicionamos o método “posRender”, que por sua vez, tem 2 argumentos. O primeiro tem os elementos do DOM. E o segundo tem o item do array corrente. Na implementação do método, temos apenas 2 “consoles”, para “printar” o mesmo valor. O primeiro “printa” o valor de “innerText” (como não estamos utilizando nenhum framework de manipulação do DOM, como jQuery, Zeptojs, etc. Vamos utilizar a propriedade nativa do JavaScript), da tag “h3”; E o segundo parâmetro, “printa” o valor do item corrente, provido pela renderização do template.
Veja o exemplo http://jsfiddle.net/andrebtoe/dp14pg1q/4
Vamos criar um exemplo mais elaborado. O exemplo é de um modelo hierárquico, onde iremos ver uma forma de reaproveitar uma estrutura de template.
No modelos teremos dados sobre pessoas, e seus filhos, de forma aninhada. Sem limites de níveis.
Primeiro, vamos mudar a classe “ViewModel”, como segue.
var ModeloHierarquicoViewModel = function(nome, espacamento, filhos){ var self = this; self.nome = nome; self.espacamento = (espacamento || 0) + "px"; self.filhos = filhos; }; function ViewModel() { var self = this; self.pessoas = [ new ModeloHierarquicoViewModel("José", 0, [ new ModeloHierarquicoViewModel("Maria", 25), new ModeloHierarquicoViewModel("João", 25) ]), new ModeloHierarquicoViewModel("Maria", 0, [ new ModeloHierarquicoViewModel("Felipe", 25, [ new ModeloHierarquicoViewModel("Felipe Jr.", 50) ]) ]), new ModeloHierarquicoViewModel("Henrique", 0, [ new ModeloHierarquicoViewModel("Marcos", 25) ]), new ModeloHierarquicoViewModel("Mario") ]; } ko.applyBindings(new ViewModel());
Primeiro, definimos uma classe com o nome “ModeloHierarquicoViewModel”, ela tem 3 argumentos no construtor:
* nome: Nome da pessoa ou filho, net, bisneto, etc.
* espacamento: Número que definirá o valor de espaçamento, no nível de hierarquia a qual a instância pertence.
* filhos: Array do tipo “ModeloHierarquicoViewModel”, que deve conter (ou não) os filhos, netos, etc.
E temos a classe “ViewModel”, que tem um array simples, com o nome “pessoas”, e algumas instancias da classe “ModeloHierarquicoViewModel”.
Agora vamos modificar o html.
<script type="text/html" id="hierarquia-template"> <h3 data-bind="text: nome, style: {marginLeft: espacamento}"></h3> <!-- ko template: { name: 'hierarquia-template', if: filhos, foreach: filhos } --> <!-- /ko --> </script> <div data-bind="template: { name: 'hierarquia-template', foreach: pessoas }"></div>
Acima, criamos uma tag de “script”, com o id “hierarquia-template”; No corpo da tag “script”, temos um tag “h3”, que mostra o valor da propriedade nome (pessoa, filho, neto, etc), e colocar uma “margin-left” do css, para a tag “h3”. Logo abaixo estamos definindo um elemento virtual, que utiliza o binding handler “template”; Ele invoca o template de nome “hierarquia-template”, novamente, só que desta vez, usa como argumento para o “foreach”, a propriedades “filhos”. Assim, ele só irá parar quando o array de filhos for null ou vazio. E para garantir que não ocorra erros, no caso da propriedade “filhos” estar como null; Definimos com o parâmetro “if”, passando como argumento “filhos”.
Veja o exemplo http://jsfiddle.net/andrebtoe/dp14pg1q/10/.
Espero ter ajudado!
Até a próxima!