| @@ -64,23 +64,27 @@ Enumerable Sections use the same syntax as condition sections do. `{{#shopping_i | |||
| ### View Partials | |||
| mustache.js supports a quite powerful but yet simple view partial mechanism. Use the following syntax for partials: `{{<partial_name}}` | |||
| var view = {name: "Joe"} | |||
| var view = { | |||
| name: "Joe", | |||
| winnings: { | |||
| value: 1000, | |||
| taxed_value: function() { | |||
| return this.value - (this.value * 0.4); | |||
| } | |||
| } | |||
| }; | |||
| var template = "Welcome, {{jow}}! {{<winnings}}" | |||
| var partials = {winnings: "You just won ${{value}} (which is ${{taxed_value}} after tax)"}; | |||
| var winnings = {value: 1000, | |||
| taxed_value: function() { | |||
| return this.value - (this.value * 0.4); | |||
| } | |||
| } | |||
| var winnings_template = "You just won ${{value}} (which is ${{taxed_value}} after tax)" | |||
| var output = Mustache.to_html(template, view) | |||
| var output = Mustache.to_html(template, view, partials) | |||
| output will be: | |||
| Welcome, Joe! You just won $1000 (which is $600 after tax) | |||
| You invoke a partial with `{{<name}}`. When `name` is an object, mustache.js will look for a JavaScript object called `name_template` and uses this for the template and `name` for the view. If `name` is a simple string, mustache.js will simply render the strings context like a normal template. | |||
| You invoke a partial with `{{<name}}`. Invoking the partial `name` will tell | |||
| mustache.js to look for a object in the context's property `name`. It will then | |||
| use that object as the context for the template found in `partials` for `name`. | |||
| ## Escaping | |||
| mustache.js does escape all values when using the standard double mustache syntax. Characters which will be escaped: `& \ " < >`. To disable escaping, simply use tripple mustaches like `{{{unescaped_variable}}}`. | |||
| @@ -0,0 +1 @@ | |||
| Again, {{again}}! | |||
| @@ -1,2 +1,2 @@ | |||
| <h1>{{title}}</h1> | |||
| {{<inner_partial}} | |||
| {{<partial}} | |||
| @@ -1,7 +1,8 @@ | |||
| var template_partial = { | |||
| var partial_context = { | |||
| title: function() { | |||
| return "Welcome"; | |||
| } | |||
| }, | |||
| partial: { | |||
| again: "Goodbye" | |||
| } | |||
| } | |||
| var inner_partial = "Again, {{title}}!"; | |||
| @@ -1,2 +1,2 @@ | |||
| <h1>Welcome</h1> | |||
| Again, Welcome! | |||
| Again, Goodbye! | |||
| @@ -0,0 +1,5 @@ | |||
| Hello {{name}} | |||
| You have just won ${{value}}! | |||
| {{#in_ca}} | |||
| Well, ${{ taxed_value }}, after taxes. | |||
| {{/in_ca}} | |||
| @@ -1,3 +1,3 @@ | |||
| <h1>{{greeting}}</h1> | |||
| {{<simple}} | |||
| {{<partial}} | |||
| <h3>{{farewell}}</h3> | |||
| @@ -1,24 +1,19 @@ | |||
| var view_partial = { | |||
| var partial_context = { | |||
| greeting: function() { | |||
| return "Welcome"; | |||
| }, | |||
| farewell: function() { | |||
| return "Fair enough, right?"; | |||
| } | |||
| }; | |||
| var simple = { | |||
| name: "Chris", | |||
| value: 10000, | |||
| taxed_value: function() { | |||
| return this.value - (this.value * 0.4); | |||
| }, | |||
| in_ca: true | |||
| partial: { | |||
| name: "Chris", | |||
| value: 10000, | |||
| taxed_value: function() { | |||
| return this.value - (this.value * 0.4); | |||
| }, | |||
| in_ca: true | |||
| } | |||
| }; | |||
| var simple_template = "Hello {{name}}\n" + | |||
| "You have just won ${{value}}!\n" + | |||
| "{{#in_ca}}\n" + | |||
| "Well, ${{ taxed_value }}, after taxes.\n" + | |||
| "{{/in_ca}}\n"; | |||
| @@ -15,37 +15,33 @@ var Mustache = function() { | |||
| otag: "{{", | |||
| ctag: "}}", | |||
| render: function(template, context) { | |||
| render: function(template, context, partials) { | |||
| // fail fast | |||
| if(template.indexOf(this.otag) == -1) { | |||
| return template; | |||
| } | |||
| var html = this.render_section(template, context); | |||
| return this.render_tags(html, context); | |||
| var html = this.render_section(template, context, partials); | |||
| return this.render_tags(html, context, partials); | |||
| }, | |||
| /* | |||
| Tries to find a partial in the global scope and render it | |||
| */ | |||
| render_partial: function(name, context) { | |||
| // FIXME: too hacky | |||
| var evil_name = eval(name); | |||
| switch(typeof evil_name) { | |||
| case "string": // a string partial, we simply render | |||
| return this.render(evil_name, context); | |||
| case "object": // a view partial needs a `name_template` template | |||
| var tpl = name + "_template"; | |||
| return this.render(eval(tpl), evil_name); | |||
| default: // should not happen #famouslastwords | |||
| throw("Unknown partial type."); | |||
| render_partial: function(name, context, partials) { | |||
| if(typeof(context[name]) != "object") { | |||
| throw({message: "subcontext for '" + name + "' is not an object"}); | |||
| } | |||
| if(!partials || !partials[name]) { | |||
| throw({message: "unknown_partial"}); | |||
| } | |||
| return this.render(partials[name], context[name], partials); | |||
| }, | |||
| /* | |||
| Renders boolean and enumerable sections | |||
| */ | |||
| render_section: function(template, context) { | |||
| render_section: function(template, context, partials) { | |||
| if(template.indexOf(this.otag + "#") == -1) { | |||
| return template; | |||
| } | |||
| @@ -59,10 +55,11 @@ var Mustache = function() { | |||
| var value = that.find(name, context); | |||
| if(that.is_array(value)) { // Enumerable, Let's loop! | |||
| return that.map(value, function(row) { | |||
| return that.render(content, that.merge(context, that.create_context(row))); | |||
| return that.render(content, that.merge(context, | |||
| that.create_context(row)), partials); | |||
| }).join(''); | |||
| } else if(value) { // boolean section | |||
| return that.render(content, context); | |||
| return that.render(content, context, partials); | |||
| } else { | |||
| return ""; | |||
| } | |||
| @@ -72,7 +69,7 @@ var Mustache = function() { | |||
| /* | |||
| Replace {{foo}} and friends with values from our view | |||
| */ | |||
| render_tags: function(template, context) { | |||
| render_tags: function(template, context, partials) { | |||
| var lines = template.split("\n"); | |||
| var new_regex = function() { | |||
| @@ -96,7 +93,7 @@ var Mustache = function() { | |||
| i--; | |||
| return ""; | |||
| case "<": // render partial | |||
| return that.render_partial(name, context); | |||
| return that.render_partial(name, context, partials); | |||
| case "{": // the triple mustache is unescaped | |||
| return that.find(name, context); | |||
| default: // escape the value | |||
| @@ -233,8 +230,8 @@ var Mustache = function() { | |||
| /* | |||
| Turns a template and view into HTML | |||
| */ | |||
| to_html: function(template, view) { | |||
| return new Renderer().render(template, view); | |||
| to_html: function(template, view, partials) { | |||
| return new Renderer().render(template, view, partials); | |||
| } | |||
| }); | |||
| }(); | |||
| @@ -7,6 +7,21 @@ testnames = Dir.glob(__DIR__ + '/../examples/*.js').map do |name| | |||
| File.basename name, '.js' | |||
| end | |||
| non_partials = testnames.select{|t| not t.include? "partial"} | |||
| partials = testnames.select{|t| t.include? "partial"} | |||
| def load_test(dir, name, partial=false) | |||
| view = File.read(dir + "/../examples/#{name}.js") | |||
| template = File.read(dir + "/../examples/#{name}.html").to_json | |||
| expect = File.read(dir + "/../examples/#{name}.txt") | |||
| if not partial | |||
| [view, template, expect] | |||
| else | |||
| partial = File.read(dir + "/../examples/#{name}.2.html").to_json | |||
| [view, template, partial, expect] | |||
| end | |||
| end | |||
| describe "mustache" do | |||
| before(:all) do | |||
| @mustache = File.read(__DIR__ + "/../mustache.js") | |||
| @@ -25,32 +40,54 @@ describe "mustache" do | |||
| run_js(js).should == "ERROR: Can't find x in [object Object]\n" | |||
| end | |||
| testnames.each do |testname| | |||
| describe testname do | |||
| non_partials.each do |testname| | |||
| describe testname do | |||
| it "should generate the correct html" do | |||
| view = File.read(__DIR__ + "/../examples/#{testname}.js") | |||
| template = File.read(__DIR__ + "/../examples/#{testname}.html").to_json | |||
| expect = File.read(__DIR__ + "/../examples/#{testname}.txt") | |||
| view, template, expect = load_test(__DIR__, testname) | |||
| runner = <<-JS | |||
| try { | |||
| #{@mustache} | |||
| #{view} | |||
| var template = #{template}; | |||
| var result = Mustache.to_html(template, #{testname}); | |||
| print(result); | |||
| } catch(e) { | |||
| print('ERROR: ' + e.message); | |||
| } | |||
| try { | |||
| #{@mustache} | |||
| #{view} | |||
| var template = #{template}; | |||
| var result = Mustache.to_html(template, #{testname}); | |||
| print(result); | |||
| } catch(e) { | |||
| print('ERROR: ' + e.message); | |||
| } | |||
| JS | |||
| run_js(runner).should == expect | |||
| end | |||
| end | |||
| end | |||
| partials.each do |testname| | |||
| describe testname do | |||
| it "should generate the correct html" do | |||
| view, template, partial, expect = | |||
| load_test(__DIR__, testname, true) | |||
| runner = <<-JS | |||
| try { | |||
| #{@mustache} | |||
| #{view}; | |||
| var template = #{template}; | |||
| var partials = {"partial": #{partial}}; | |||
| var result = Mustache.to_html(template, partial_context, partials); | |||
| print(result); | |||
| } catch(e) { | |||
| print('ERROR: ' + e.message); | |||
| } | |||
| JS | |||
| run_js(runner).should == expect | |||
| end | |||
| end | |||
| end | |||
| def run_js(js) | |||
| File.open("runner.js", 'w') {|f| f << js} | |||
| `js runner.js` | |||