Template eksternal di Garis Bawah

121

Saya menggunakan template Underscore . Apakah mungkin untuk melampirkan file eksternal sebagai template ?

Di Backbone View saya punya:

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

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

Di html saya adalah:

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

Ini bekerja dengan baik. Tapi saya butuh template eksternal . Saya coba:

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

atau

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

atau

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

tapi tidak berhasil.

Tomáš
sumber

Jawaban:

51

EDIT: Jawaban ini sudah tua dan ketinggalan zaman. Saya akan menghapusnya, tetapi itu adalah jawaban yang "diterima". Saya akan menyuntikkan pendapat saya sebagai gantinya.

Saya tidak akan menganjurkan melakukan ini lagi. Sebaliknya, saya akan memisahkan semua template menjadi file HTML individual. Beberapa akan menyarankan untuk memuat ini secara asinkron (Require.js atau semacam cache template). Itu berfungsi dengan baik pada proyek kecil tetapi pada proyek besar dengan banyak templat, Anda mendapati diri Anda membuat banyak permintaan asinkron kecil pada pemuatan halaman yang sangat tidak saya sukai. (ugh ... ok, Anda bisa menyiasatinya dengan Require.js dengan melakukan pra-kompilasi dependensi awal Anda dengan r.js, tetapi untuk template, ini masih terasa salah bagi saya)

Saya suka menggunakan grunt task (grunt-contrib-jst) untuk mengkompilasi semua template HTML menjadi satu file templates.js dan memasukkannya. Anda mendapatkan yang terbaik dari semua template IMO ... dunia yang ada dalam file, kompilasi template tersebut terjadi pada waktu build (bukan runtime), dan Anda tidak memiliki seratus permintaan asinkron kecil saat halaman dimulai.

Segala sesuatu di bawah ini adalah sampah

Bagi saya, saya lebih suka kesederhanaan menyertakan file JS dengan template saya. Jadi, saya mungkin membuat file bernama view_template.js yang menyertakan template sebagai variabel:

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

Kemudian, sesederhana memasukkan file skrip seperti yang biasa dan kemudian menggunakannya dalam tampilan Anda:

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

Mengambil langkah lebih jauh, saya benar-benar menggunakan coffeescript, jadi kode saya sebenarnya lebih terlihat seperti ini dan menghindari karakter pelarian akhir baris:

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

Menggunakan pendekatan ini menghindari brining di require.js yang sebenarnya tidak diperlukan.

Brian Genisio
sumber
46
pendekatan ini akan kehilangan fungsi penyorotan sintaks, pemformatan ulang, dan pemfaktoran ulang apa pun yang tersedia dengan ide. tidak memberikan suara.
Kinjal Dixit
1
Maaf, tapi saya harus meremehkan jawaban ini. Ini sangat kikuk karena masih akan menyimpan file template sebagai file skrip, hanya dipaksa untuk terlihat seperti template. Template harus berupa template jadi jika Anda harus membawa Require.js atau menggunakan solusi brilian koorchik di bawah ini, saya merasa itu sangat berharga.
Tommi Forsström
3
@ TommiForsström Saya setuju. Saya telah menjauh dari pendekatan ini. Wow! 4 Des 2011 adalah waktu yang sangat lama di dunia pengembangan Backbone.js :)
Brian Genisio
Sebenarnya, saya ingin menghapus jawaban ini tetapi saya tidak bisa karena itu adalah jawaban yang diterima. Itu sudah ketinggalan zaman dan ada solusi yang jauh lebih baik dari ini. Hari ini, saya akan memilikinya sebagai file template terpisah dan menggunakan grunt task (JST, misalnya) untuk membangunnya menjadi file templates.js terpisah untuk menghindari sifat asinkron dalam mengambil semuanya satu per satu. Ini adalah yang terbaik dari kedua pendekatan dunia IMO.
Brian Genisio
baik jika tidak ada banyak template, saya pikir solusi sebelumnya benar-benar paling efisien.
sutraAdmin
107

Inilah solusi sederhana:

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

Menggunakan "async: false" di sini bukanlah cara yang buruk karena bagaimanapun Anda harus menunggu hingga template dimuat.

Jadi, fungsi "render"

  1. memungkinkan Anda untuk menyimpan setiap template dalam file html terpisah di dir statis
  2. sangat ringan
  3. mengkompilasi dan menyimpan template
  4. abstrak template memuat logika. Misalnya, di masa mendatang Anda dapat menggunakan template yang dimuat sebelumnya dan yang telah dikompilasi sebelumnya.
  5. mudah digunakan

[Saya mengedit jawaban daripada meninggalkan komentar karena saya percaya ini penting.]

jika template tidak muncul di aplikasi asli , dan Anda lihat HIERARCHY_REQUEST_ERROR: DOM Exception 3, lihat jawaban oleh Dave Robinson untuk Apa sebenarnya yang dapat menyebabkan "HIERARCHY_REQUEST_ERR: DOM Exception 3" -Error? .

Pada dasarnya, Anda harus menambahkan

dataType: 'html'

ke permintaan $ .ajax.

koorchik
sumber
3
@BinaryNights - haruskah kita selalu menambahkan dataType: 'html'permintaan ajax kita, untuk berjaga-jaga?
Matt
Apakah ini juga berfungsi untuk tampilan bersarang? Rupanya saya tidak dapat membuatnya berfungsi jika tampilan merujuk ke tampilan lain.
T. Rossi
1
Ya, ini juga harus berfungsi untuk template bersarang. Cukup tambahkan penolong render dan beri nama seperti: <% = render ('nested_template', data)%>
koorchik
Halo, dapatkah Anda menjelaskan sedikit lebih banyak tentang "kompilasi dan templat cache"? Ketika saya mencoba memanggil fungsi render, itu tidak menambahkan tmpl_data untuk mengembalikan nilai, itu hanya meneruskannya seperti apa adanya. Saya harus memanggil metode "Handlebars.compile" setelah itu. Terima kasih.
cdagli
18

Mixin ini memungkinkan Anda untuk membuat template eksternal menggunakan Menggarisbawahi dengan cara yang sangat sederhana: _.templateFromUrl(url, [data], [settings]). Metode API hampir sama dengan _.template () Underscore . Caching disertakan.

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

Pemakaian:

var someHtml = _.templateFromUrl("http://example.com/template.html", {"var": "value"});
Dmitriy
sumber
2
Campuran kecil yang sangat bagus di sana sangat rapi! :) bersorak untuk berbagi
Nick White
Sangat keren D, ini adalah jenis solusi yang saya cari. dan menurut saya dapat digunakan untuk menyimpan satu set template pribadi.
bigmadwolf
@abhi itu disediakan dalam jawaban. Selain itu, Anda memerlukan jQuery untuk memuat template, tetapi Anda dapat menulis ulang bagian kode yang memuat template melalui AJAX sesuai selera Anda menggunakan pustaka lain.
Dmitriy
@Dmitriy async: false sudah tidak digunakan lagi, jadi jika saya memanggil tanpa parameter async itu tidak berfungsi, saya pikir ini karena secara default async adalah benar yang berarti memanggil syncronisilly, jadi apakah Anda punya solusi untuk masalah ini
abhi
@abhi, ini berfungsi untuk jQuery 1. * Lihat juga jawaban ini stackoverflow.com/a/11755262/541961
Dmitriy
17

Saya tidak ingin menggunakan require.js untuk tugas sederhana ini, jadi saya menggunakan solusi koorchik yang dimodifikasi.

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

Mengapa menambahkan template ke dokumen, daripada menyimpannya dalam objek javascript? Karena dalam versi produksi saya ingin membuat file html dengan semua template sudah disertakan, jadi saya tidak perlu membuat permintaan ajax tambahan. Dan pada saat yang sama saya tidak perlu melakukan pemfaktoran ulang dalam kode saya, seperti yang saya gunakan

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

dalam tampilan Backbone saya.

Tyth
sumber
1
Menggunakan ini juga, sangat bagus untuk scenerio di mana saya mencoba menggunakan Jasmine untuk TDD dan ingin menguji template sebelum saya menerapkan requirejs dan plugin textjs-nya. Bagus @Tramp
Nicholas Murray
Panggilan ke $ .ajax bersifat asinkron, apa pun yang bergantung pada hasilnya, harus dijalankan dalam metode done dari promise yang dikembalikan.
JoshRoss
Terima kasih untuk ini. Saya menggunakannya. Satu saran: tidak ada alasan untuk menambahkan sebagai tag skrip - cukup lanjutkan dan ubah ke template dan simpan dalam hash pencarian. Berikut adalah contoh biola (non-fungsional): jsfiddle.net/PyzeF
webnesto
async: falsetidak digunakan lagi sekarang
ProblemsOfSumit
Karena async: falsetidak digunakan lagi, saya meningkatkan jawabannya, dengan menambahkan completepanggilan balik.
Alexander
16

Ini mungkin sedikit keluar dari topik, tetapi Anda dapat menggunakan Grunt (http://gruntjs.com/) - yang berjalan di node.js (http://nodejs.org/, tersedia untuk semua platform utama) untuk menjalankan tugas dari garis komando. Ada banyak plugin untuk alat ini, seperti kompiler template, https://npmjs.org/package/grunt-contrib-jst . Lihat dokumentasi di GitHub, https://github.com/gruntjs/grunt-contrib-jst . (Anda juga perlu memahami cara menjalankan pengelola paket node, https://npmjs.org/ . Jangan khawatir, ini sangat mudah dan serbaguna.)

Anda kemudian dapat menyimpan semua templat Anda dalam file html terpisah, menjalankan alat untuk mengkompilasi semuanya menggunakan garis bawah (yang saya yakini merupakan ketergantungan untuk plugin JST, tapi jangan khawatir, pengelola paket node akan menginstal dependensi secara otomatis untuk Anda).

Ini mengkompilasi semua template Anda ke satu skrip, misalnya

templates.js

Memuat skrip akan menyetel global - "JST" secara default - yang merupakan larik fungsi, dan dapat diakses seperti ini:

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

yang mirip dengan

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

jika Anda meletakkan konten dari tag script itu di (templates /) listView.html

Namun, penendang sebenarnya adalah ini: Grunt hadir dengan tugas ini yang disebut 'watch', yang pada dasarnya akan memantau perubahan pada file yang telah Anda tentukan dalam file grunt.js lokal Anda (yang pada dasarnya adalah file konfigurasi untuk proyek Grunt Anda, dalam javascript ). Jika Anda memiliki grunt, mulailah tugas ini untuk Anda, dengan mengetik:

grunt watch

dari baris perintah, Grunt akan memantau semua perubahan yang Anda buat pada file dan secara otomatis menjalankan semua tugas yang telah Anda siapkan untuk itu di file grunt.js jika mendeteksi perubahan - seperti tugas jst yang dijelaskan di atas. Edit dan kemudian simpan file Anda, dan semua template Anda akan dikompilasi ulang menjadi satu file js, bahkan jika template tersebut tersebar di sejumlah direktori dan subdirektori.

Tugas serupa dapat dikonfigurasi untuk linting javascript Anda, menjalankan tes, menggabungkan dan meminimalkan / uglifying file skrip Anda. Dan semua dapat dikaitkan dengan tugas jam sehingga perubahan pada file Anda akan secara otomatis memicu 'build' baru dari proyek Anda.

Butuh beberapa waktu untuk mengatur dan memahami cara mengonfigurasi file grunt.js, tetapi itu baik, sepadan dengan waktu yang diinvestasikan, dan saya rasa Anda tidak akan pernah kembali ke cara kerja yang sebelumnya kasar

Mansiemans
sumber
Jawaban favorit. Ini harus menjadi jawaban yang diterima. (bukan milikku)
Brian Genisio
Titik masuk yang bagus untuk mendengus. Ini berfungsi dengan baik untuk HTML biasa tetapi jika saya memiliki <% = price%> atau serupa, saya mendapatkan: token tak terduga =, gagal dikompilasi dari grunt
mcktimo
Saya menyukai pendekatan ini (menggunakan JST), kecuali saya mengalami masalah dalam melakukan ini template: JST['test.html']():, tampaknya tidak memuat data dari JST :( (lihat pertanyaan saya di sini: stackoverflow.com/questions/29723392/… )
timhc22
15

Saya pikir inilah yang dapat membantu Anda. Segala sesuatu dalam solusi berkisar pada require.jspustaka yang merupakan file JavaScript dan pemuat modul.

Tutorial di tautan di atas menunjukkan dengan sangat baik bagaimana proyek tulang punggung dapat diatur. Sebuah contoh implementasi juga disediakan. Semoga ini membantu.

nayaab
sumber
3
Terima kasih atas referensi ke situs saya, untuk siapa pun yang melihat, saya telah memulai proyek yang mencoba menerapkan praktik terbaik backboneboilerplate.com
Thomas Davis
4

Saya tertarik pada javascript templating dan sekarang saya mengambil langkah pertama dengan backbone. Inilah yang saya pikirkan dan sepertinya bekerja dengan cukup baik.

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
sumber
Pada getfungsi Anda , saya mungkin akan mengembalikan $.ajaxdirinya sendiri sehingga mengembalikan objek promise jadi jika template Anda tidak langsung merespons.
Dennis Rongo
4

Saya harus menyetel jenis data ke "teks" agar berfungsi untuk saya:

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;
}
pengguna1828189
sumber
2

Saya menemukan solusi yang cocok untuk saya dengan menggunakan jQuery.

Saya menambahkan kode template garis bawah, dengan metode jQuery.load (), ke file html utama.

Setelah itu, saya menggunakannya untuk membuat template. Semua harus terjadi secara serempak!

Konsepnya adalah:

Saya memiliki kode template peta garis bawah:

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

Dan saya memasukkan kode itu ke dalam file bernama map-template.html

Setelah itu saya membuat pembungkus untuk file template.

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

Kemudian saya memasukkan file itu ke file html utama saya seperti itu.

Di kepala:

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

Bersulang.

Kaloyan Stamatov
sumber
1

Saya tahu pertanyaan ini benar-benar tua tetapi muncul sebagai hasil pertama pencarian google untuk templat ajax garis bawah.

Saya lelah karena tidak menemukan solusi yang baik untuk ini, jadi saya membuatnya sendiri:

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

Selain memuat template garis bawah menggunakan AJAX, ia menambahkan fungsionalitas <% include%>. Semoga bisa bermanfaat bagi seseorang.

ziad-saab
sumber
0

Saya agak tidak nyaman memaksa jQuery berfungsi secara sinkron, jadi saya memodifikasi contoh sinkron sebelumnya menggunakan promise. Ini hampir sama, tetapi berjalan secara asinkron. Saya menggunakan template hbs dalam contoh ini:

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

Kemudian untuk menggunakan html yang dirender:

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

CATATAN: Seperti yang didiskusikan oleh orang lain, akan lebih baik jika mengkompilasi semua template menjadi satu file templates.js dan memuatnya di awal daripada memiliki banyak panggilan AJAX sinkron kecil untuk mendapatkan template saat halaman web dimuat.

Megatron
sumber
0

Peringatan maju - Inilah naga:

Saya menyebutkan pendekatan yang ditunjukkan di bawah ini hanya untuk membantu mereka yang berjuang untuk membuat tumpukan ASP.NET (dan kerangka kerja serupa) bekerja secara harmonis dengan ekosistem js-libs. Sudah jelas bahwa ini bukan solusi umum. Karena itu ...

/ endforwardwarning

Jika Anda menggunakan ASP.NET, Anda dapat mengeksternalisasi template Anda hanya dengan menempatkannya di dalam satu atau beberapa tampilan parsial milik mereka. Aka di dalam .cshtml Anda:

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

Di dalam template.cshtml Anda:

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

Dan sekarang Anda bisa menggunakan template seperti biasa:

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

Semoga pendekatan yang sangat sulit dipahami ini membantu seseorang menghemat waktu satu jam untuk menggaruk-garuk kepala.

XDS
sumber