Definindo um Modelo HTML para Anexar Usando JQuery

125

Eu tenho uma matriz que eu estou fazendo um loop. Sempre que uma condição for verdadeira, desejo anexar uma cópia do HTMLcódigo abaixo a um elemento de contêiner com alguns valores.

Onde posso colocar esse HTML para reutilizar de maneira inteligente?

<a href="#" class="list-group-item">
    <div class="image">
         <img src="" />
    </div>
    <p class="list-group-item-text"></p>
</a>

JQuery

$('.search').keyup(function() {
    $('.list-items').html(null);

    $.each(items, function(index) {
        // APPENDING CODE HERE
    });
});
Patrick Reck
fonte
2
Se você está procurando uma maneira inteligente, coloque todas as suas informações em um único DIV e crie um estilo. Não crie várias tabelas com apenas duas células, não as envolva em âncoras.
Sergey Snegirev
3
O OP está obviamente interessado em práticas de codificação adequadas (parabéns!), Então eu queria ajudar. Se eu quisesse contribuir com o problema real, postaria uma resposta. Tenho certeza de que ninguém concorda que o uso de uma tabela de duas células full-blown para posicionar uma imagem e um texto é apenas justificada com exceção de alguns requisitos muito exóticos (IE4?)
Sergey Snegirev
1
@BrianG. A publicação de um comentário não significa realmente que você está saindo pela tangente. Embora eu concorde que os comentários sejam um local apropriado para eles (desde que ainda sejam relevantes), eles também são adequados para dicas de orientação de pessoas que ainda não têm tempo para expandi-las para uma resposta. É útil para o OP deixar claro o que você está fazendo.
millimoose
Ninguém respondeu à sua pergunta, Patrick?
Sebastian Neira

Respostas:

164

Você pode optar por usar um mecanismo de modelo no seu projeto, como:

Se você não deseja incluir outra biblioteca, John Resig oferece uma solução jQuery , semelhante à abaixo.


Navegadores e leitores de tela ignoram os tipos de script não reconhecidos:

<script id="hidden-template" type="text/x-custom-template">
    <tr>
        <td>Foo</td>
        <td>Bar</td>
    <tr>
</script>

Usando o jQuery, adicionar linhas com base no modelo seria semelhante a:

var template = $('#hidden-template').html();

$('button.addRow').click(function() {
    $('#targetTable').append(template);
});
Mathijs Flietstra
fonte
@MaksimLuzik está certo. O bigode (observe a ortografia da marca registrada) é mais leve e se encaixa no código do OP, mas o guidão tem mais recursos para lidar com matrizes e pode fornecer uma solução mais suficiente no final. Aqui está um bom artigo de comparação: blog.cubettech.com/…
Michael Scheper
13
Exemplo de como editar o modelo aqui: jsfiddle.net/meehanman/7vw8bc84
Dean Meehan
175

Pergunta antiga, mas como a pergunta é "usando jQuery", pensei em fornecer uma opção que permita fazer isso sem introduzir nenhuma dependência de fornecedor.

Embora existam muitos mecanismos de modelagem por aí, muitos de seus recursos caíram em desagrado recentemente, com iteração ( <% for), condicionais ( <% if) e transformações ( <%= myString | uppercase %>) vistas como micro-idioma na melhor das hipóteses e antipadrões na pior. As práticas modernas de modelagem incentivam simplesmente o mapeamento de um objeto para sua representação DOM (ou outra), por exemplo, o que vemos com propriedades mapeadas para componentes no ReactJS (especialmente componentes sem estado).

Modelos dentro do HTML

Uma propriedade em que você pode confiar para manter o HTML do seu modelo próximo ao restante do HTML é usar um não executável <script> type, por exemplo <script type="text/template">. Para o seu caso:

<script type="text/template" data-template="listitem">
    <a href="${url}" class="list-group-item">
        <table>
            <tr>
                <td><img src="${img}"></td>
                <td><p class="list-group-item-text">${title}</p></td>
            </tr>
        </table>
    </a>
</script>

No carregamento do documento, leia o seu modelo e o tokenize usando um simples String#split

var itemTpl = $('script[data-template="listitem"]').text().split(/\$\{(.+?)\}/g);

Observe que, com nosso token, você o obtém no [text, property, text, property]formato alternado . Isso nos permite mapeá-lo bem usando uma Array#mapfunção de mapeamento:

function render(props) {
  return function(tok, i) { return (i % 2) ? props[tok] : tok; };
}

Onde propspoderia parecer { url: 'http://foo.com', img: '/images/bar.png', title: 'Lorem Ipsum' }.

Reunindo tudo, assumindo que você analisou e carregou o seu itemTplcomo acima, e você tem uma itemsmatriz no escopo:

$('.search').keyup(function () {
  $('.list-items').append(items.map(function (item) {
    return itemTpl.map(render(item)).join('');
  }));
});

Essa abordagem também é apenas apenas jQuery - você deve ser capaz de adotar a mesma abordagem usando javascript vanilla com document.querySelectore .innerHTML.

jsfiddle

Modelos dentro de JS

Uma pergunta a ser feita é: você realmente deseja / precisa definir modelos como arquivos HTML? Você sempre pode componente + reutilizar um modelo da mesma maneira que reutilizaria a maioria das coisas que deseja repetir: com uma função.

No es7-land, usando funções de desestruturação, seqüências de caracteres de modelo e setas, você pode escrever funções de componentes absolutamente bonitas que podem ser facilmente carregadas usando o $.fn.htmlmétodo acima.

const Item = ({ url, img, title }) => `
  <a href="${url}" class="list-group-item">
    <div class="image">
      <img src="${img}" />
    </div>
    <p class="list-group-item-text">${title}</p>
  </a>
`;

Então você poderia renderizá-lo facilmente, mesmo mapeado a partir de uma matriz, assim:

$('.list-items').html([
  { url: '/foo', img: 'foo.png', title: 'Foo item' },
  { url: '/bar', img: 'bar.png', title: 'Bar item' },
].map(Item).join(''));

Ah, e observação final: não se esqueça de higienizar suas propriedades passadas para um modelo, se forem lidas em um banco de dados ou se alguém puder passar HTML (e depois executar scripts etc.) da sua página.

Josh de Qaribou
fonte
1
Nice template engine.
Arthur Castro
40
Seus modelos dentro JS seção é a bomba
BigRon
2
Você poderia adicionar uma demo / violino para a abordagem "Templates Inside HTML"?
LCJ
1
Uau! Modelo dentro de JS ... Nunca pensei nisso! Surpreendente!
Roman Lopez
2
Uau, isso é realmente muito bom. Estou adicionando um atributo de URL de dados a cada modelo. E buscando dados ajax para cada um. Isso é tão bom e me salvou muito tempo! Obrigado.
Davey
42

Use o modelo HTML!

Como a resposta aceita representaria um método de sobrecarga de script, gostaria de sugerir outro que seja, na minha opinião, muito mais limpo e mais seguro devido aos riscos XSS que acompanham os scripts de sobrecarga.

Fiz uma demonstração para mostrar como usá-lo em uma ação e como injetar um modelo em outro, editar e adicionar ao documento DOM.

exemplo html

<template id="mytemplate">
  <style>
     .image{
        width: 100%;
        height: auto;
     }
  </style>
  <a href="#" class="list-group-item">
    <div class="image">
      <img src="" />
    </div>
    <p class="list-group-item-text"></p>
  </a>
</template>

exemplo js

// select
var t = document.querySelector('#mytemplate');

// set
t.content.querySelector('img').src = 'demo.png';
t.content.querySelector('p').textContent= 'demo text';

// add to document DOM
var clone = document.importNode(t.content, true); // where true means deep copy
document.body.appendChild(clone);

HTML <template>

  • + Seu conteúdo é efetivamente inerte até ser ativado. Essencialmente, sua marcação está oculta no DOM e não é renderizada.

  • + Qualquer conteúdo dentro de um modelo não terá efeitos colaterais. Os scripts não são executados, as imagens não são carregadas, o áudio não é reproduzido ... até que o modelo seja usado.

  • O conteúdo é considerado como não estando no documento. Usar document.getElementById()ou querySelector()na página principal não retornará nós filhos de um modelo.

  • + Os modelos podem ser colocados em qualquer lugar no interior de <head>, <body>ou <frameset>e pode conter qualquer tipo de conteúdo que é permitido naqueles elementos. Observe que "qualquer lugar" significa que <template>pode ser usado com segurança em locais que o analisador de HTML não permite.

Cair pra trás

O suporte ao navegador não deve ser um problema, mas se você deseja cobrir todas as possibilidades, pode fazer uma verificação fácil:

Para detectar recursos <template>, crie o elemento DOM e verifique se a propriedade .content existe:

function supportsTemplate() {
  return 'content' in document.createElement('template');
}

if (supportsTemplate()) {
  // Good to go!
} else {
  // Use old templating techniques or libraries.
}

Algumas informações sobre o método de script Sobrecarregando

  • + Nada é renderizado - o navegador não renderiza esse bloco porque a <script>tag possui display:nonepor padrão.
  • + Inert - o navegador não analisa o conteúdo do script como JS porque seu tipo está definido para algo diferente de "text/javascript".
  • Questões de segurança - incentiva o uso de .innerHTML. A análise de string em tempo de execução dos dados fornecidos pelo usuário pode facilmente levar a vulnerabilidades do XSS.

Artigo completo: https://www.html5rocks.com/en/tutorials/webcomponents/template/#toc-old

Referência útil: https://developer.mozilla.org/en-US/docs/Web/API/Document/importNode http://caniuse.com/#feat=queryselector

CRIANDO COMPONENTES DA WEB Tutorial de criação de componentes da Web personalizados usando modelos HTML da Trawersy Media: https://youtu.be/PCWaFLy3VUo

DevWL
fonte
4
O templateelemento não é suportado pelo Internet Explorer (a partir de 2018, IE11). Testado com o exemplo 580 de w3c.github.io/html/single-page.html .
Roland
Eu gosto do <template>, embora eu recomende o uso de classes liberalmente, por isso fica claro quais objetos estão sendo definidos. Ainda são necessárias algumas habilidades js para mapear seus dados corretamente no modelo, especialmente usando uma abordagem no estilo de componentes para mapear grandes conjuntos de dados. Pessoalmente, eu ainda gosto de apenas criar o HTML em funções, ou idealmente, construir o DOM diretamente com o próprio JS e renunciar completamente ao HTML (por exemplo, como o React faz isso).
Josh de Qaribou
A abordagem do modelo funcionou bem para mim. Uma recomendação que tenho é clonar o modelo primeiro (e não no final) e trabalhar com o objeto clonado. Caso contrário, as alterações que você está fazendo estão acontecendo no próprio modelo. Se você tivesse uma lógica condicional em que definisse algumas propriedades / conteúdo apenas algumas vezes, essas propriedades estariam presentes no seu próximo uso do modelo. Clonando antes de cada uso, você garante sempre uma base consistente para trabalhar.
jt.
13

Adicione em algum lugar do corpo

<div class="hide">
<a href="#" class="list-group-item">
    <table>
        <tr>
            <td><img src=""></td>
            <td><p class="list-group-item-text"></p></td>
        </tr>
    </table>
</a>
</div>

então crie css

.hide { display: none; }

e adicione aos seus js

$('#output').append( $('.hide').html() );
Mateusz Nowak
fonte
1
O OP tem um JSON e deseja renderizar um html usando essa matriz como fonte de dados. Como esta resposta está resolvendo o problema? Sua resposta pega um nó html e o clona / duplica e nada relacionado à ligação de dados.
Adrian Iftode
Então eu sou quem está com voto negativo.
Adrian Iftode
Realmente acho que você não está ajudando a obter uma resposta melhor, pois poderia começar lembrando-nos que o ponto era mais amplo do que aquilo que foi abordado por nossas respostas.
Sebastian Neira
1
Isso funciona, mas é mais eficiente para fazer .children().clone(), em vez de .html(). A velocidade é de cerca de 3: 1 para um aleatório <tr/>que tive em uma página usando esse código i=10000; time=performance.now(); while (--i) {$a.clone().append(0 ? $b.html() : $b.children().clone())} performance.now()-time. A proporção é realmente um pouco mais exagerada porque estou usando $ a.clone (), mas tentar esvaziá-la a cada iteração é mais um sucesso de desempenho do que a clonagem, por isso não tenho certeza de como torná-lo mais preciso, porque funções de tempo têm seu próprio custo.
Chinoto Vokro
1
Essa abordagem é ruim. principalmente porque você obtém o download do conteúdo do modelo e é analisado, mesmo que não o tenha usado. Esse é um problema muito grande para considerar isso como uma resposta válida. Também como alguém Menthon acima, se você deve, pelo menos, clone uso em vez de .html ()
DevWL
3

Outra alternativa: Pure

Eu o uso e isso me ajudou muito. Um exemplo mostrado em seu site:

HTML

<div class="who">
</div>

JSON

{
  "who": "Hello Wrrrld"
}

Resultado

<div class="who">
  Hello Wrrrld
</div>
alditis
fonte
2

Para resolver esse problema, reconheço duas soluções:

  • O primeiro vem com o AJAX, com o qual você terá que carregar o modelo de outro arquivo e adicionar sempre que quiser .clone().

    $.get('url/to/template', function(data) {
        temp = data
        $('.search').keyup(function() {
            $('.list-items').html(null);
    
            $.each(items, function(index) {
                 $(this).append(temp.clone())
            });
    
        });
    });

    Leve em consideração que o evento deve ser adicionado assim que o ajax for concluído para garantir que os dados estejam disponíveis!

  • O segundo seria adicioná-lo diretamente em qualquer lugar do html original, selecioná-lo e ocultá-lo no jQuery:

    temp = $('.list_group_item').hide()

    Depois, você pode adicionar uma nova instância do modelo com

    $('.search').keyup(function() {
        $('.list-items').html(null);
    
        $.each(items, function(index) {
            $(this).append(temp.clone())
        });
    });
  • Igual ao anterior, mas se você não quiser que o modelo permaneça lá, mas apenas no javascript, acho que você pode usar (não o testou!) Em .detach()vez de ocultar.

    temp = $('.list_group_item').detach()

    .detach() remove elementos do DOM enquanto mantém os dados e eventos ativos (.remove () não!).

Sebastian Neira
fonte
- Não há problema! -
iConnor
O op tem um JSON e deseja renderizar um html usando esse array como fonte de dados. Como esta resposta está resolvendo o problema? Sua resposta pega um nó html e o clona / duplica e nada relacionado à ligação de dados.
Adrian Iftode
1
Cito op: Quero anexar uma cópia do código HTML abaixo a um elemento de contêiner com alguns valores. Eu acredito que isso resolve o problema dele.
Sebastian Neira
E como é tratada a parte "alguns valores"?
Adrian Iftode
Bem, isso não significa que não resolve o problema. Compreendo que você tenha me dito que estava faltando um dos pontos :) De qualquer forma, como vou saber como inserir esses valores se nem mesmo sei de que valores estamos falando? Dê uma olhada nesta parte. Onde posso reutilizar esse HTML de maneira inteligente? . O que realmente está sendo perguntado? Sobre o JSON ou a inclusão HTML?
Sebastian Neira
1

Resposta muito boa do DevWL sobre o uso do elemento de modelo HTML5 nativo . Para contribuir com essa boa pergunta do OP, gostaria de acrescentar como usar esse templateelemento usando o jQuery, por exemplo:

$($('template').html()).insertAfter( $('#somewhere_else') );

O conteúdo do templatearquivo não é html, mas apenas tratado como dados; portanto, você precisa agrupar o conteúdo em um objeto jQuery para acessar os métodos do jQuery.

Jacques
fonte