下划线中的外部模板


121

我使用Underscore模板。是否可以将外部文件作为模板

在骨干视图中,我有:

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

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

在我的html中是:

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

它运作良好。但是我需要外部模板。我尝试:

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

要么

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

要么

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

但它没有用。

Answers:


51

编辑:这个答案是旧的和过时的。我将其删除,但这是“已接受”的答案。我会发表我的意见。

我不再提倡这样做。相反,我会将所有模板都分成单独的HTML文件。有些人建议异步加载这些文件(Require.js或各种模板缓存)。这在小型项目上效果很好,但在具有大量模板的大型项目上,您发现自己在页面加载时发出了大量的小型异步请求,这是我非常不喜欢的。(嗯...好吧,您可以通过使用r.js预编译初始依赖项来使用Require.js来解决它,但是对于模板,这对我来说仍然是错误的)

我喜欢使用grunt任务(grunt-contrib-jst)将所有HTML模板编译为一个template.js文件,并将其包含在内。您将获得世界上最好的IMO ...模板,并将它们保存在文件中,对这些模板的编译在构建时(而不是运行时)进行,并且在页面启动时没有一百个微小的异步请求。

下面的东西都是垃圾

对我来说,我更喜欢在模板中包含JS文件的简单性。因此,我可能会创建一个名为view_template.js的文件,该文件包含模板作为变量:

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

然后,就像添加一个脚本文件一样简单,然后在您的视图中使用它即可:

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

更进一步,我实际上使用coffeescript,因此我的代码实际上看起来像这样,并避免了行尾转义字符:

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

使用这种方法可以避免在确实没有必要的地方使用require.js。


46
这种方法将失去ide提供的任何语法高亮,重新格式化和重构功能。虽然没有投票。
Kinjal Dixit 2012年

1
对不起,但是我不得不拒绝这个答案。它非常笨拙,因为它仍将模板文件保留为脚本文件,只是有点像模板。模板必须是模板,因此如果您必须引入Require.js或在下面使用koorchik出色的解决方案,我认为绝对值得。
TommiForsström'13年

3
@TommiForsström我同意。我已经放弃了这种方法。哇!2011年12月4日是Backbone.js开发世界中很久以前的事情了:)
Brian Genisio 2013年

实际上,我想删除此答案,但不能,因为它是可接受的答案。它已经过时,并且有比这更好的解决方案。今天,我会将它们作为单独的模板文件使用,并使用grunt任务(例如JST)将它们构建为单独的template.js文件,以避免单独获取它们的异步特性。IMO是两全其美的方法。
Brian Genisio 2014年

好吧,如果没有很多模板,我认为前一种解决方案确实是最有效的。
silkAdmin 2014年

107

这是一个简单的解决方案:

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);
}

在这里使用“ async:false”并不是一个坏方法,因为在任何情况下,您都必须等待模板加载完毕。

因此,“渲染”功能

  1. 允许您将每个模板存储在静态目录中的单独html文件中
  2. 非常轻巧
  3. 编译和缓存模板
  4. 摘要模板加载逻辑。例如,将来您可以使用预加载和预编译的模板。
  5. 易于使用

[我编辑答案而不是发表评论,因为我认为这很重要。]

如果模板未在本机应用程序中显示,您会看到HIERARCHY_REQUEST_ERROR: DOM Exception 3Dave Robinson的回答,究竟是什么会导致“ HIERARCHY_REQUEST_ERR:DOM异常3”错误?

基本上,您必须添加

dataType: 'html'

到$ .ajax请求。


3
@BinaryNights-为了以防万一,我们是否应始终将其添加dataType: 'html'到我们的ajax请求中?
马特

这对嵌套视图也起作用吗?显然,如果一个视图引用另一个视图,则无法正常工作。
T. Rossi

1
是的,它也应该适用于嵌套模板。只需添加渲染助手,然后像这样调用它即可:<%= render('nested_template',data)%>
koorchik 2014年

您好,您能否进一步说明“编译和缓存模板”?当我尝试调用render函数时,它没有将tmpl_data添加到返回值中,而是按原样传递了它。在那之后,我不得不调用“ Handlebars.compile”方法。谢谢。
cdagli

18

这个mixin允许您以非常简单的方式使用Underscore渲染外部模板_.templateFromUrl(url, [data], [settings])。方法API与Underscore _.template()几乎相同。包括缓存。

_.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);
}});

用法:

var someHtml = _.templateFromUrl("http://example.com/template.html", {"var": "value"});

2
真的很不错,小混混很整齐!:)分享欢呼
Nick White

非常酷的D,这是我一直在寻找的解决方案。而且我认为可以将一组模板设为私有。
bigmadwolf 2014年

@abhi答案中提供了它。另外,您需要jQuery来加载模板,但是您可以使用任何其他库来重写通过AJAX加载模板的代码的一部分。
德米特里(Dmitriy)

@Dmitriy async:不建议使用false,因此,如果我不使用async参数进行调用就无法正常工作,我认为这是因为默认情况下async为true意味着调用syncronisilly,那么您是否有解决此问题的方法
Abhi

@abhi,它适用于jQuery的1 *也看到这个答案stackoverflow.com/a/11755262/541961
德米特里

17

我不想将require.js用于此简单任务,因此我使用了改进的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
    }
});

为什么要在文档中附加模板,而不是将模板存储在javascript对象中?因为在生产版本中,我想生成已经包含所有模板的html文件,所以我不需要提出任何其他的ajax请求。同时,我不需要在代码中进行任何重构,因为我使用了

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

在我的骨干网视图中。


1
同样,也使用此功能,对于我尝试使用Jasmine进行TDD并希望在实现requirejs及其textjs插件之前测试模板的场景而言,非常有用。做得好@Tramp
Nicholas Murray

$ .ajax的调用是异步的,取决于结果的任何操作都应在返回的promise的done方法中执行。
JoshRoss 2013年

谢谢你 我用了 一个建议:没有理由附加为脚本标签-可以继续并将其转换为模板,并将其保留在查找哈希中。这是一个(非功能性的)提琴示例: jsfiddle.net/PyzeF
webnesto 2014年

async: false现在已弃用
ProblemsOfSumit

由于async: false不推荐使用,我通过添加complete回调改善了答案。
亚历山大

16

这可能与主题不符,但是您可以使用Grunt(http://gruntjs.com/)-在node.js(http://nodejs.org/,适用于所有主要平台)上运行,以从命令行。该工具有很多插件,例如模板编译器https://npmjs.org/package/grunt-contrib-jst。请参阅GitHub上的文档,https://github.com/gruntjs/grunt-contrib-jst。(您还需要了解如何运行节点程序包管理器https://npmjs.org/。不要担心,它非常简单并且用途广泛。)

然后,您可以将所有模板保存在单独的html文件中,运行该工具以使用下划线对所有模板进行预编译(我相信这是JST插件的依赖项,但请放心,节点包管理器会为您自动安装依赖项)。

例如,这会将所有模板编译为一个脚本

templates.js

加载脚本将设置一个全局-默认情况下为“ JST”-这是一个函数数组,可以这样访问:

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

这将类似于

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

如果您将该脚本标记的内容放在(templates /)listView.html中

但是,真正的关键是:Grunt附带了名为“ watch”的任务,该任务基本上将监视对您在本地grunt.js文件(基本上是javascript中Grunt项目的配置文件)中定义的文件的更改。 )。如果您不寒而栗,请键入以下命令为您启动此任务:

grunt watch

从命令行,Grunt将监视您对文件所做的所有更改,并在检测到更改后自动执行在该grunt.js文件中为此文件设置的所有任务,例如上述的jst任务。编辑然后保存文件,即使所有模板分散在多个目录和子目录中,所有模板也会重新编译为一个js文件。

可以配置类似的任务来添加javascript,运行测试,连接和缩小/丑化脚本文件。而且所有操作都可以与监视任务联系在一起,因此对文件的更改将自动触发项目的新“构建”。

花费一些时间进行设置并了解如何配置grunt.js文件,但这很值得,值得花时间投入,而且我认为您永远都不会回到预先准备好的工作方式


最喜欢的答案。这应该是公认的答案。(不是我的)
Brian Genisio 2014年

不错的入口点。对于纯HTML来说,它可以正常工作,但是如果我有<%= price%>或类似的代码,我会得到:意外的标记=,无法通过咕
unt

我喜欢这种方法(使用JST),除了我在执行此操作时遇到问题: template: JST['test.html'](),它似乎没有从JST中加载数据:((请参阅此处的问题:stackoverflow.com/questions/29723392 / ...
timhc22

15

我认为可能对您有帮助。解决方案中的所有内容都围绕着require.js库,该库是一个JavaScript文件和模块加载器。

上面链接上的教程很好地展示了如何组织骨干项目。一个简单的实现也被提供。希望这可以帮助。


3
感谢您的参考我的网站,任何人都希望我已经开始它试图实现最佳实践项目backboneboilerplate.com
托马斯·戴维斯

4

我对javascript模板很感兴趣,现在我开始着手骨干。这是我想出的,并且似乎工作得很好。

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();

在您的get函数上,我可能会返回$.ajax自身,因此它会返回一个promise对象,以防万一您的模板没有立即响应。
丹尼斯·朗戈

4

我必须将数据类型设置为“文本”以使其对我有用:

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;
}

2

我找到了一个适合使用jQuery的解决方案。

我将带有jQuery.load()方法的下划线模板代码添加到主html文件中。

一旦存在,我就用它来生成模板。所有需要同步发生!

这个概念是:

我有一个下划线地图模板代码:

<!-- 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>

然后将代码放在名为map-template.html的文件中

之后,我为模板文件创建一个包装器。

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

然后像这样将文件包含在我的主要html文件中。

在头上:

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

干杯。



0

我强迫jQuery同步运行有点不安,因此我使用了promises修改了前面的同步示例。几乎相同,但异步运行。在此示例中,我使用的是hbs模板:

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;
};

然后使用呈现的html:

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

注意:正如其他人所讨论的那样,最好将所有模板编译成一个templates.js文件并在开始时进行加载,而不是在加载网页时进行许多小的同步AJAX调用来获取模板。


0

前方警告-这是巨龙:

我提到下面显示的方法,只是为了帮助那些努力使ASP.NET堆栈(和类似框架)与js-lib生态系统和谐地工作的人。毋庸置疑,这不是通用解决方案。话说回来 ...

/ endforwardwarning

如果您使用的是ASP.NET,则只需将模板放在一个或多个自己的部分视图中即可将其外部化。也称为.cshtml:

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

在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>

现在您可以像往常一样使用模板:

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

希望这种难以捉摸的方法可以帮助某人节省一个小时的时间。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.