Modelo externo em Sublinhado

121

Eu uso o modelo de sublinhado . É possível anexar um arquivo externo como modelo ?

No Backbone View, tenho:

 textTemplate: _.template( $('#practice-text-template').html() ),

 initialize: function(){                                            
  this.words = new WordList;            
  this.index = 0;
  this.render();
 },

No meu html é:

<script id="practice-text-template" type="text/template">
   <h3>something code</h3>
</script>

Isso funciona bem. Mas eu preciso de modelo externo . Eu tento:

<script id="practice-text-template" type="text/template" src="templates/tmp.js">

ou

textTemplate: _.template( $('#practice-text-template').load('templates/tmp.js') ),

ou

$('#practice-text-template').load('templates/tmp.js', function(data){ this.textTemplate = _.template( data ) })

mas não funcionou.

Tomáš
fonte

Respostas:

51

EDIT: Esta resposta é antiga e desatualizada. Eu o excluiria, mas é a resposta "aceita". Vou injetar minha opinião.

Eu não defenderia mais isso. Em vez disso, eu separaria todos os modelos em arquivos HTML individuais. Alguns sugerem o carregamento de forma assíncrona (Require.js ou um tipo de cache de modelo). Isso funciona bem em pequenos projetos, mas em grandes projetos com muitos modelos, você se vê fazendo uma tonelada de pequenas solicitações assíncronas no carregamento da página que eu realmente não gosto. (ugh ... ok, você pode contornar isso com o Require.js pré-compilando suas dependências iniciais com o r.js, mas para os modelos, isso ainda parece errado para mim)

Eu gosto de usar uma tarefa grunt (grunt-contrib-jst) para compilar todos os modelos HTML em um único arquivo templates.js e incluí-lo. Você obtém o melhor de todos os mundos IMO ... modelos em um arquivo, a compilação desses modelos acontece no momento da construção (não em tempo de execução) e você não tem cem solicitações assíncronas minúsculas quando a página é iniciada.

Tudo abaixo é lixo

Para mim, prefiro a simplicidade de incluir um arquivo JS no meu modelo. Portanto, posso criar um arquivo chamado view_template.js que inclua o modelo como uma variável:

app.templates.view = " \
    <h3>something code</h3> \
";

Então, é tão simples quanto incluir o arquivo de script como um arquivo normal e usá-lo na sua visão:

template: _.template(app.templates.view)

Indo um passo adiante, na verdade eu uso coffeescript, portanto, meu código se parece mais com isso e evito os caracteres de escape de final de linha:

app.templates.view = '''
    <h3>something code</h3>
'''

O uso dessa abordagem evita o surgimento de require.js onde realmente não é necessário.

Brian Genisio
fonte
46
essa abordagem perderia quaisquer funções de destaque, reformatação e refatoração de sintaxe disponíveis com o ide. não votando embora.
Kinjal Dixit
1
Sinto muito, mas tive que reduzir a votação desta resposta. É terrivelmente desajeitado, pois ainda mantém os arquivos de modelo como arquivos de script, meio que forçados a parecer modelos. Os modelos precisam ser modelos. Se você precisar trazer o Require.js ou usar a solução brilhante do koorchik abaixo, acho que definitivamente vale a pena.
Tommi Forsström
3
@ TommiForsström eu concordo. Eu me afastei dessa abordagem. Uau! 4 de dezembro de 2011 é realmente muito tempo no mundo do desenvolvimento Backbone.js :)
Brian Genisio
Na verdade, eu gostaria de excluir esta resposta, mas não posso porque é a resposta aceita. Está desatualizado e há soluções muito melhores que isso. Hoje, eu os gostaria como arquivos de modelo separados e usava uma tarefa difícil (JST, por exemplo) para compilá-los em um arquivo templates.js separado para evitar a natureza assíncrona de buscá-los todos individualmente. É a melhor abordagem dos dois mundos da IMO.
Brian Genisio
bem, se não houver muitos modelos, acho que a solução anterior é realmente a mais eficiente.
silkAdmin
107

Aqui está uma solução simples:

var rendered_html = render('mytemplate', {});

function render(tmpl_name, tmpl_data) {
    if ( !render.tmpl_cache ) { 
        render.tmpl_cache = {};
    }

    if ( ! render.tmpl_cache[tmpl_name] ) {
        var tmpl_dir = '/static/templates';
        var tmpl_url = tmpl_dir + '/' + tmpl_name + '.html';

        var tmpl_string;
        $.ajax({
            url: tmpl_url,
            method: 'GET',
            dataType: 'html', //** Must add 
            async: false,
            success: function(data) {
                tmpl_string = data;
            }
        });

        render.tmpl_cache[tmpl_name] = _.template(tmpl_string);
    }

    return render.tmpl_cache[tmpl_name](tmpl_data);
}

Usar "async: false" aqui não é uma maneira ruim, pois, em qualquer caso, você deve esperar até o modelo ser carregado.

Então, a função "render"

  1. permite armazenar cada modelo em um arquivo html separado no diretório estático
  2. é muito leve
  3. compila e armazena em cache modelos
  4. abstrai a lógica de carregamento do modelo. Por exemplo, no futuro você pode usar modelos pré-carregados e pré-compilados.
  5. é fácil de usar

[Estou editando a resposta em vez de deixar um comentário porque acredito que isso seja importante.]

se os modelos não estiverem aparecendo no aplicativo nativo , e veja você HIERARCHY_REQUEST_ERROR: DOM Exception 3, veja a resposta de Dave Robinson para O que exatamente pode causar um erro "HIERARCHY_REQUEST_ERR: DOM Exception 3"? .

Basicamente, você deve adicionar

dataType: 'html'

para a solicitação $ .ajax.

Koorchik
fonte
3
@BinaryNights - devemos sempre adicionar dataType: 'html'à nossa solicitação de ajax, apenas no caso?
Matt
Isso funciona também para visualizações aninhadas? Aparentemente, não posso fazê-lo funcionar se uma visão se referir a outra.
T. Rossi
1
Sim, também deve funcionar para modelos aninhados. Basta adicionar o auxiliar de renderização e chamá-lo da seguinte forma: <% = renderizar ('nested_template', data)%>>
koorchik
Olá, você pode explicar um pouco mais sobre "modelos de compilações e caches"? Quando tentei chamar a função render, ela não adicionou o tmpl_data para retornar o valor, apenas passou como está. Eu tive que chamar o método "Handlebars.compile" depois disso. Obrigado.
cdagli
18

Este mixin permite renderizar modelo externo usando sublinhado de maneira muito simples: _.templateFromUrl(url, [data], [settings]). A API do método é quase a mesma que a _.template () do Underscore . Armazenamento em cache incluído.

_.mixin({templateFromUrl: function (url, data, settings) {
    var templateHtml = "";
    this.cache = this.cache || {};

    if (this.cache[url]) {
        templateHtml = this.cache[url];
    } else {
        $.ajax({
            url: url,
            method: "GET",
            async: false,
            success: function(data) {
                templateHtml = data;
            }
        });

        this.cache[url] = templateHtml;
    }

    return _.template(templateHtml, data, settings);
}});

Uso:

var someHtml = _.templateFromUrl("http://example.com/template.html", {"var": "value"});
Dmitriy
fonte
2
Muito bom mixin lá muito arrumado! :) felicidades para compartilhar
Nick White
D muito legal, esse era o tipo de solução que eu estava procurando. e acho que poderia ser usado para manter um conjunto de modelos privado.
Bigmadwolf 27/03
@abhi é fornecido na resposta. Além disso, você precisa do jQuery para carregar o modelo, mas pode reescrever parte do código que carrega o modelo via AJAX ao seu gosto, usando qualquer outra biblioteca.
Dmitriy
@Dmitriy async: false está obsoleto, por isso, se eu chamar sem o parâmetro async, ele não está funcionando, acho que isso ocorre porque, por padrão, o async é verdadeiro, o que significa chamar syncronisilly, então você tem solução para esse problema
abhi
@abhi, ele funciona para jQuery 1. * Veja também esta resposta stackoverflow.com/a/11755262/541961
Dmitriy
17

Eu não queria usar o require.js para esta tarefa simples, então usei a solução modificada do koorchik.

function require_template(templateName, cb) {
    var template = $('#template_' + templateName);
    if (template.length === 0) {
        var tmpl_dir = './templates';
        var tmpl_url = tmpl_dir + '/' + templateName + '.tmpl';
        var tmpl_string = '';

        $.ajax({
            url: tmpl_url,
            method: 'GET',
            contentType: 'text',
            complete: function (data, text) {
                tmpl_string = data.responseText;
                $('head').append('<script id="template_' + templateName + '" type="text/template">' + tmpl_string + '<\/script>');
                if (typeof cb === 'function')
                    cb('tmpl_added');
            }
        });
    } else {
        callback('tmpl_already_exists');
    }
}

require_template('a', function(resp) {
    if (resp == 'tmpl_added' || 'tmpl_already_exists') {
        // init your template 'a' rendering
    }
});
require_template('b', function(resp) {
    if (resp == 'tmpl_added' || 'tmpl_already_exists') {
        // init your template 'b' rendering
    }
});

Por que anexar modelos ao documento, em vez de armazená-los no objeto javascript? Como na versão de produção, eu gostaria de gerar um arquivo html com todos os modelos já incluídos, portanto não precisarei fazer solicitações adicionais de ajax. E, ao mesmo tempo, não precisarei fazer nenhuma refatoração no meu código, pois uso

this.template = _.template($('#template_name').html());

nas minhas visualizações Backbone.

Tyth
fonte
1
Usando isso também, é ótimo para o cenário em que estou tentando usar o Jasmine for TDD e desejo testar modelos antes de implementar o requirejs e seu plug-in textjs. Bem feito @Tramp
Nicholas Murray
A chamada para $ .ajax é assíncrona, qualquer coisa dependendo dos resultados, deve ser executada dentro do método realizado da promessa retornada.
precisa saber é o seguinte
Obrigado por isso. Eu usei. Uma sugestão: não há motivo para acrescentar como uma tag de script - basta seguir em frente e converter em um modelo e mantê-lo em um hash de pesquisa. Aqui está um exemplo de violino (não funcional): jsfiddle.net/PyzeF
webnesto
async: falseestá obsoleto agora
ProblemsOfSumit
Como async: falseestá obsoleto, aprimorei a resposta adicionando completeretorno de chamada.
Alexander Alexander
16

Isso pode ser um pouco fora do tópico, mas você pode usar o Grunt (http://gruntjs.com/) - que é executado no node.js (http://nodejs.org/, disponível para todas as principais plataformas) para executar tarefas no linha de comando. Existem vários plugins para essa ferramenta, como um compilador de modelos, https://npmjs.org/package/grunt-contrib-jst . Consulte a documentação no GitHub, https://github.com/gruntjs/grunt-contrib-jst . (Você também precisará entender como executar o gerenciador de pacotes de nós, https://npmjs.org/ . Não se preocupe, é incrivelmente fácil e versátil.)

Você pode manter todos os seus modelos em arquivos html separados, executar a ferramenta para pré-compilar todos eles usando sublinhado (o que acredito ser uma dependência do plug-in JST, mas não se preocupe, o gerenciador de pacotes do nó instalará automaticamente as dependências para você).

Isso compila todos os seus modelos em um script, digamos

templates.js

O carregamento do script definirá um global - "JST" por padrão - que é uma matriz de funções e pode ser acessado da seguinte forma:

JST['templates/listView.html']()

o que seria semelhante ao

_.template( $('#selector-to-your-script-template'))

se você colocar o conteúdo dessa tag de script em (templates /) listView.html

No entanto, o verdadeiro problema é o seguinte: o Grunt vem com esta tarefa chamada 'watch', que basicamente monitora as alterações nos arquivos que você definiu no arquivo grunt.js local (que é basicamente um arquivo de configuração para o seu projeto Grunt, em javascript ) Se você tiver grunhido, inicie esta tarefa para você digitando:

grunt watch

a partir da linha de comando, o Grunt monitorará todas as alterações que você fizer nos arquivos e executará automaticamente todas as tarefas que você configurou nesse arquivo grunt.js se detectar alterações - como a tarefa jst descrita acima. Edite e salve seus arquivos, e todos os seus modelos serão recompilados em um arquivo js, ​​mesmo se estiverem espalhados por vários diretórios e subdiretórios.

Tarefas semelhantes podem ser configuradas para aprender a usar javascript, executar testes, concatenar e minificar / modificar os arquivos de script. E tudo pode ser vinculado à tarefa de observação, para que as alterações em seus arquivos disparem automaticamente uma nova 'compilação' do seu projeto.

Demora algum tempo para definir as coisas e entender como configurar o arquivo grunt.js, mas vale a pena o tempo investido e acho que você nunca voltará a uma maneira de trabalhar antes do trabalho

Mansiemans
fonte
Resposta favorita. Essa deve ser a resposta aceita. (não meu)
Brian Genisio
Ponto de entrada agradável para grunhir. Ele funciona muito bem para HTML simples, mas se eu tiver <% = price%> ou I get semelhante: token inesperado =, não conseguiu compilar a partir do grunhido
mcktimo
Estou gostando dessa abordagem (usando JST), exceto que estou tendo problemas para fazer isso template: JST['test.html']():, não parece estar carregando os dados do JST :( (veja minha pergunta aqui: stackoverflow.com/questions/29723392/… )
timhc22
15

Eu acho que é isso que pode ajudá-lo. Tudo na solução gira em torno da require.jsbiblioteca, que é um carregador de arquivos e módulos JavaScript.

O tutorial no link acima mostra muito bem como um projeto de backbone pode ser organizado. Uma implementação de amostra também é fornecida. Espero que isto ajude.

nayaab
fonte
3
Obrigado pela referência a meu site, para qualquer um que olha eu comecei um projeto que tenta implementar as melhores práticas backboneboilerplate.com
Thomas Davis
4

Fiquei interessado no modelo de javascript e agora estou dando os primeiros passos com o backbone. Isso é o que eu criei e parece funcionar muito bem.

window.App = {

    get : function(url) {
        var data = "<h1> failed to load url : " + url + "</h1>";
        $.ajax({
            async: false,
            url: url,
            success: function(response) {
                data = response;
            }
        });
        return data;
    }
}

App.ChromeView = Backbone.View.extend({
    template: _.template( App.get("tpl/chrome.html") ),
    render: function () {
        $(this.el).html(this.template());
        return this;
    },
});

App.chromeView = new App.ChromeView({ el : document.body });
App.chromeView.render();
j040p3d20
fonte
Em sua getfunção, eu provavelmente retornaria o $.ajaxpróprio objeto para que ele retornasse um objeto de promessa, para o caso de seu modelo não responder imediatamente.
Dennis Rongo
4

Eu tive que definir o tipo de dados como "texto" para fazê-lo funcionar para mim:

get : function(url) {
    var data = "<h1> failed to load url : " + url + "</h1>";
    $.ajax({
        async: false,
        dataType: "text",
        url: url,
        success: function(response) {
            data = response;
        }
    });
    return data;
}
user1828189
fonte
2

Encontrei uma solução que funciona para mim usando o jQuery.

Eu adiciono o código do modelo de sublinhado, com o método jQuery.load (), ao arquivo html principal.

Uma vez lá, estou usando-o para gerar os modelos. Tudo precisa acontecer de forma síncrona!

O conceito é:

Eu tenho um código de modelo de mapa sublinhado:

<!-- MAP TEMPLATE-->
<script type="text/template" id="game-map-template">
    <% _.each(rc, function(rowItem, index){ %>
      <ul class="map-row" data-row="<%- index %>">
        <li class="map-col <%- colItem.areaType ? 'active-area' : '' %>"></li>
        ...
</script>

E eu coloquei esse código em um arquivo chamado map-template.html

Depois disso, crio um wrapper para os arquivos de modelo.

<div id="templatesPool"></div>

Então eu incluo esse arquivo no meu arquivo html principal assim.

Na cabeça:

<!-- Template Loader -->
<script> 
    $(function(){
      $("#templatesPool").append($('<div>').load("map-template.html")); 
    });
</script> 

Felicidades.

Kaloyan Stamatov
fonte
1

Eu sei que essa pergunta é realmente antiga, mas surgiu como o primeiro resultado em uma pesquisa no Google por modelos de sublinhado ajax.

Eu estava cansado de não encontrar uma boa solução para isso, então criei a minha:

https://github.com/ziad-saab/underscore-async-templates

Além de carregar modelos de sublinhado usando AJAX, ele adiciona a funcionalidade <% include%>. Espero que possa ser útil para alguém.

ziad-saab
fonte
0

Eu estava um pouco desconfortável ao forçar o jQuery a funcionar de forma síncrona, então modifiquei o exemplo síncrono anterior usando promessas. É praticamente o mesmo, mas é executado de forma assíncrona. Estou usando modelos hbs neste exemplo:

var asyncRenderHbs= function(template_name, template_data) {
    if (!asyncRenderHbs.template_cache) { 
        asyncRenderHbs.template_cache= {};
    }

    var promise= undefined;

    if (!asyncRenderHbs.template_cache[template_name]) {
        promise= new Promise(function(resolve, reject) {
            var template_url= '/templates/' + template_name;
            $.ajax({
                url: template_url,
                method: 'GET',
                success: function(data) {
                    asyncRenderHbs.template_cache[template_name]= Handlebars.compile(data);
                    resolve(asyncRenderHbs.template_cache[template_name](template_data));
                },
                error: function(err, message) {
                    reject(err);
                }           
            });
        });
    } else {
        promise= Promise.resolve(asyncRenderHbs.template_cache[template_name](template_data));
    }

    return promise;
};

Então, para usar o html renderizado:

asyncRenderHbs('some_template.hbs', context)
    .then(function(html) {
        applicationMain.append(html);
        // Do other stuff here after html is rendered...
    })
    .catch(function(err) {
        // Handle errors
    });

NOTA: Conforme discutido por outras pessoas, seria preferível compilar todos os modelos em um único arquivo templates.js e carregá-lo no início, em vez de ter muitas chamadas AJAX síncronas pequenas para obter modelos quando a página da web é carregada.

Megatron
fonte
0

Avançar aviso - Aqui estão os dragões:

Menciono a abordagem mostrada abaixo simplesmente para ajudar aqueles que lutam para fazer com que as pilhas do ASP.NET (e estruturas semelhantes) funcionem harmoniosamente com o ecossistema de js-libs. Escusado será dizer que esta não é uma solução genérica. Tendo dito isto ...

/ endforwardwarning

Se você estiver usando o ASP.NET, poderá exteriorizar seus modelos simplesmente colocando-os em uma ou mais visualizações parciais próprias. Também dentro do seu .cshtml:

  @Html.Partial("path/to/template")

Dentro do seu template.cshtml:

   // this is razorview and thusly if you ever need to use the @ character in here  
   // you will have to either escape it as @@ or use the html codepoint which is &#64
   // http://stackoverflow.com/questions/3626250/escape-character-in-razor-view-engine
   <script type="text/x-template" id="someId">
        <span class="foo"><%= name %></span>
   </script>

E agora você pode usar o modelo como de costume:

  _.template($("#someId").html())({ name: "Foobar" });

Espero que essa abordagem ilusoriamente óbvia ajude alguém a economizar uma hora em coçar a cabeça.

XDS
fonte