| @@ -64,23 +64,27 @@ Enumerable Sections use the same syntax as condition sections do. `{{#shopping_i | |||||
| ### View Partials | ### View Partials | ||||
| mustache.js supports a quite powerful but yet simple view partial mechanism. Use the following syntax for partials: `{{<partial_name}}` | 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 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: | output will be: | ||||
| Welcome, Joe! You just won $1000 (which is $600 after tax) | 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 | ## 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}}}`. | 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> | <h1>{{title}}</h1> | ||||
| {{<inner_partial}} | |||||
| {{<partial}} | |||||
| @@ -1,7 +1,8 @@ | |||||
| var template_partial = { | |||||
| var partial_context = { | |||||
| title: function() { | title: function() { | ||||
| return "Welcome"; | return "Welcome"; | ||||
| } | |||||
| }, | |||||
| partial: { | |||||
| again: "Goodbye" | |||||
| } | |||||
| } | } | ||||
| var inner_partial = "Again, {{title}}!"; | |||||
| @@ -1,2 +1,2 @@ | |||||
| <h1>Welcome</h1> | <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> | <h1>{{greeting}}</h1> | ||||
| {{<simple}} | |||||
| {{<partial}} | |||||
| <h3>{{farewell}}</h3> | <h3>{{farewell}}</h3> | ||||
| @@ -1,24 +1,19 @@ | |||||
| var view_partial = { | |||||
| var partial_context = { | |||||
| greeting: function() { | greeting: function() { | ||||
| return "Welcome"; | return "Welcome"; | ||||
| }, | }, | ||||
| farewell: function() { | farewell: function() { | ||||
| return "Fair enough, right?"; | 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: "{{", | otag: "{{", | ||||
| ctag: "}}", | ctag: "}}", | ||||
| render: function(template, context) { | |||||
| render: function(template, context, partials) { | |||||
| // fail fast | // fail fast | ||||
| if(template.indexOf(this.otag) == -1) { | if(template.indexOf(this.otag) == -1) { | ||||
| return template; | 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 | 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 | Renders boolean and enumerable sections | ||||
| */ | */ | ||||
| render_section: function(template, context) { | |||||
| render_section: function(template, context, partials) { | |||||
| if(template.indexOf(this.otag + "#") == -1) { | if(template.indexOf(this.otag + "#") == -1) { | ||||
| return template; | return template; | ||||
| } | } | ||||
| @@ -59,10 +55,11 @@ var Mustache = function() { | |||||
| var value = that.find(name, context); | var value = that.find(name, context); | ||||
| if(that.is_array(value)) { // Enumerable, Let's loop! | if(that.is_array(value)) { // Enumerable, Let's loop! | ||||
| return that.map(value, function(row) { | 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(''); | }).join(''); | ||||
| } else if(value) { // boolean section | } else if(value) { // boolean section | ||||
| return that.render(content, context); | |||||
| return that.render(content, context, partials); | |||||
| } else { | } else { | ||||
| return ""; | return ""; | ||||
| } | } | ||||
| @@ -72,7 +69,7 @@ var Mustache = function() { | |||||
| /* | /* | ||||
| Replace {{foo}} and friends with values from our view | 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 lines = template.split("\n"); | ||||
| var new_regex = function() { | var new_regex = function() { | ||||
| @@ -96,7 +93,7 @@ var Mustache = function() { | |||||
| i--; | i--; | ||||
| return ""; | return ""; | ||||
| case "<": // render partial | case "<": // render partial | ||||
| return that.render_partial(name, context); | |||||
| return that.render_partial(name, context, partials); | |||||
| case "{": // the triple mustache is unescaped | case "{": // the triple mustache is unescaped | ||||
| return that.find(name, context); | return that.find(name, context); | ||||
| default: // escape the value | default: // escape the value | ||||
| @@ -233,8 +230,8 @@ var Mustache = function() { | |||||
| /* | /* | ||||
| Turns a template and view into HTML | 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' | File.basename name, '.js' | ||||
| end | 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 | describe "mustache" do | ||||
| before(:all) do | before(:all) do | ||||
| @mustache = File.read(__DIR__ + "/../mustache.js") | @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" | run_js(js).should == "ERROR: Can't find x in [object Object]\n" | ||||
| end | end | ||||
| testnames.each do |testname| | |||||
| describe testname do | |||||
| non_partials.each do |testname| | |||||
| describe testname do | |||||
| it "should generate the correct html" 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 | 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 | JS | ||||
| run_js(runner).should == expect | run_js(runner).should == expect | ||||
| end | end | ||||
| end | 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) | def run_js(js) | ||||
| File.open("runner.js", 'w') {|f| f << js} | File.open("runner.js", 'w') {|f| f << js} | ||||
| `js runner.js` | `js runner.js` | ||||