Git Product home page Git Product logo

flavoursaver's Introduction

FlavourSaver

Handlebars.js without the .js

Gem Version Build Status Maintainability

WAT?

FlavourSaver is a ruby-based implementation of the Handlebars.js templating language. FlavourSaver supports Handlebars template rendering natively on Rails and on other frameworks (such as Sinatra) via Tilt.

Please use it, break it, and send issues/PR's for improvement.

License

FlavourSaver is Copyright (c) 2013 Resistor Limited and licensed under the terms of the MIT Public License (see the LICENSE file included with this distribution for more details).

Installation

Add this line to your application's Gemfile:

gem 'flavour_saver'

And then execute:

$ bundle

Or install it yourself as:

$ gem install flavour_saver

Usage

FlavourSaver provides an interface to the amazing Tilt templating library, meaning that it should work with anything that has Tilt support (Sinatra, etc) and has a native Rails template handler.

Status

FlavourSaver is in its infancy, your pull requests are greatly appreciated.

Currently supported:

  • Full support of Mustache and Handlebars templates.
  • Expressions:
    • with object-paths ({{some.method.chain}})
    • containing object-literals ({{object.['index'].method}}): Ruby's :[](index) method is called for literals, making FlavourSaver compatible with Hash and hashlike objects.
    • with list arguments ({{method arg1 "arg2"}})
    • with hash arguments ({{method foo=bar bar="baz"}})
    • with list and hash arguments ({{method arg1 arg2 foo=bar bar="baz"}}) provided that the hash is the last argument.
    • Comments ({{! a comment}})
    • Expression output is HTML escaped
  • Safe expressions
    • Expressions wrapped in triple-stashes are not HTML escaped ({{{an expression}}})
  • Block expressions
    • Simple API for adding block helpers.
    • Block expressions with inverse blocks
    • Inverse blocks
  • Partials
  • Raw content ({{{{raw}}}} not parsed or validated {{{{/raw}}}})
  • Subexpressions ({{sum 1 (sum 1 1)}} returns 3)

Helpers

FlavourSaver implements the following helpers by default:

#with

Yields its argument into the context of the block contents:

{{#with person}}
  {{name}}
{{/with}}

#each

Takes a single collection argument and yields the block's contents once for each member of the collection:

{{#each people}}
  {{name}}
{{/each}}

#if

Takes a single argument and yields the contents of the block if that argument is truthy.

{{#if person}}
  Hi {{person.name}}!
{{/if}}

It can also handle a special case {{else}} expression:

{{#if person}}
  Hi {{person.name}}!
{{else}}
  Nobody to say hi to.
{{/if}}

#unless

Exactly the same is #if but backwards.

this

In JavaScript this is a native keyword, in Ruby not-so-much. FlavourSaver's this helper returns self:

{{#each names}}
  {{this}}
{{/each}}

log

Writes log output. The destination can be changed by assigning a Logger instance to FlavourSaver.logger=. On Rails FlavourSaver.logger automatically points at Rails.logger.

Adding additional helpers

Additional helpers can easy be added by calling FS.register_helper, eg:

FS.register_helper(:whom) { 'world' }

Now if you were to render the following template:

<h1>Hello {{whom}}!</h1>

You would receive the following output:

<h1>Hello world!</h1>

Adding block helpers

Creating a block helper works exactly like adding a regular helper, except that the helper implementation can call yield.contents one or more times, with an optional argument setting the context of the block execution:

FS.register_helper(:three_times) do
  yield.contents
  yield.contents
  yield.contents
end

Which when called with the following template:

{{#three_times}}
  hello
{{/three_times}}

would result in the following output:

  hello
  hello
  hello

Implementing a simple iterator is dead easy:

FS.register_helper(:list_people) do |people|
  people.each do |person|
    yield.contents person
  end
end

Which could be used like so:

{{#list_people people}}
  <b>{{name}}<b><br />
  Age: {{age}}<br />
  Sex: {{sex}}<br />
{{/list_people}}

Block helpers can also contain an {{else}} statement, which, when used creates a second set of block contents (called inverse) which can be yielded to the output:

FS.register_helper(:isFemale) do |person,&block|
  if person.sex == 'female'
    block.call.contents
  else
    block.call.inverse
  end
end

You can also register an existing method:

def isFemale(person)
  if person.sex == 'female'
    yield.contents
  else
    yield.inverse
  end
end

FS.register_helper(method(:isFemale))

Which could be used like so:

{{#isFemale person}}
  {{person.name}} is female.
{{else}}
  {{person.name}} is male.
{{/isFemale}}

Subexpressions

You can use a subexpression as any value for a helper, and it will be executed before it is ran. You can also nest them, and use them in assignment of variables.

Below are some examples, utilizing a "sum" helper than adds together two numbers.

{{sum (sum 5 10) (sum 2 (sum 1 4))}}
#=> 22

{{#if (sum 1 2) > 2}}its more{{/if}}
#=> its more

{{#student_heights size=(sum boys girls)}}

Raw Content

Sometimes you don't want a section of content to be evaluted as handlebars, such as when you want to display it in a page that renders with handlebars. FlavourSaver offers a raw helper, that will allow you to pass anything through wrapped in those elements, and it will not be evaluated.

{{{{raw}}}}
{{if} this tries to parse, it will break on syntax
{{{{/raw}}}}
=> {{if} this tries to parse, it will break on syntax

Its important to note that while this looks like a block helper, it is not in practice. This is why you must omit the use of a # when writing it.

Using Partials

Handlebars allows you to register a partial either as a function or a string template with the engine before compiling, FlavourSaver retains this behaviour (with the notable exception of within Rails - see below).

To register a partial you call FlavourSaver.register_partial with a name and a string:

FlavourSaver.register_partial(:my_partial, "{{this}} is a partial")

You can then use this partial within your templates:

{{#each people}}{{> my_partial this}}{{/each}}

Using with Rails

One potential gotcha of using FlavourSaver with Rails is that FlavourSaver doesn't let you have any access to the controller's instance variables. This is done to maintain compatibility with the original JavaScript implementation of Handlebars so that templates can be used on both the server and client side without any change.

When accessing controller instance variables you should access them by way of a helper method or a presenter object.

For example, in ApplicationController.rb you may have a before_filter which authenticates the current user's session cookie and stores it in the controller's @current_user instance variable.

To access this variable you could create a simple helper method in ApplicationHelpers:

def current_user
  @current_user
end

Which would mean that you are able to access it in your template:

{{#if current_user}}
  Welcome back, {{current_user.first_name}}!
{{/if}}

Using the Tilt Interface Directly

You can use the registered Tilt interface directly to render template strings with a hash of template variables.

The Tilt template's render method expects an object that can respond to messages using dot notation. In the following example, the template variable {{foo}} will result in a call to .foo on the data object. For this reason the data object can't be a simple hash. A model would work, but if you have a plain old Ruby hash, use it to create a new OpenStruct object, which will provide the dot notation needed.

template = Tilt['handlebars'].new { "{{foo}} {{bar}}" }
data = OpenStruct.new foo: "hello", bar: "world"

template.render data # => "hello world"

Special behaviour of Handlebars' partial syntax

In Handlebars.js all partial templates must be pre-registered with the engine before they are able to be used. When running inside Rails FlavourSaver modifies this behaviour to use Rails' render partial helper:

{{> my_partial}}

Will be translated into:

render :partial => 'my_partial'

Handlebars allows you to send a context object into the partial, which sets the execution context of the partial. In Rails this behaviour would be confusing and non-standard, so instead any argument passed to the partial is evaluated and passed to the partial's :object argument:

{{> my_partial my_context}}
render :partial => 'my_partial', :object => my_context

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Added some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

flavoursaver's People

Contributors

ahsan1447 avatar bluegod avatar claytonpassmore avatar edubkendo avatar fbcouch avatar flexoid avatar grumbleafrican avatar jcreamer898 avatar jimsynz avatar libo avatar lucasprag avatar meesterdude avatar nehresma avatar olleolleolle avatar rsutphin avatar snikch avatar ursm avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

flavoursaver's Issues

DEPRECATION WARNING: Single arity template handlers are deprecated

I'm seeing this deprecation warning with 2.0.1:

DEPRECATION WARNING: Single arity template handlers are deprecated. Template handlers must                                                                                                                                                                                                                   
now accept two parameters, the view object and the source for the view object.                                                                        
Change:                                                                                                                                                                                                                                                                                                      
  >> #<Proc:0x0000aaaaf09310c0 /bundle/ruby/3.0.0/gems/flavour_saver-2.0.1/lib/flavour_saver.rb:20>.call(template)                                                                                                                                                                                           
To:                                                                                                                                                   
  >> #<Proc:0x0000aaaaf09310c0 /bundle/ruby/3.0.0/gems/flavour_saver-2.0.1/lib/flavour_saver.rb:20>.call(template, source)                                                                                                                                                                                   
 (called from block (2 levels) in require at /bundle/gems/bundler-2.2.25/lib/bundler/runtime.rb:60)                                                   
DEPRECATION WARNING: Single arity template handlers are deprecated. Template handlers must                                                                                                                                                                                                                   
now accept two parameters, the view object and the source for the view object.                                                                                                                                                                                                                               
Change:                                                                                                                                                                                                                                                                                                      
  >> #<Proc:0x0000aaaaf09310c0 /bundle/ruby/3.0.0/gems/flavour_saver-2.0.1/lib/flavour_saver.rb:20>.call(template)                                                                                                                                                                                           
To:                                                                                                                                                                                                                                                                                                          
  >> #<Proc:0x0000aaaaf09310c0 /bundle/ruby/3.0.0/gems/flavour_saver-2.0.1/lib/flavour_saver.rb:20>.call(template, source)                                                                                                                                                                                   
 (called from block (2 levels) in require at /bundle/gems/bundler-2.2.25/lib/bundler/runtime.rb:60)      

Is there a fix for this?

Validate template without template data?

The latest security patch has broken the way I was validating a template (templates are stored in a database and re-used). Previously, I was rendering the template with no data to check for major problems with the template. Since there isn't data, any variables in the template will now raise a FlavourSaver::UnknownHelperException. Is there a better way to validate the template?

Add hash @key support inside #each block

Currently will get FlavourSaver::UndefinedPrivateVariableException (private variable not found @key): when trying to access the index/key of the current item while inside an each loop. Doesn't look like these "helpers" are supported.

Discussion where the support is added to handlebarsjs: handlebars-lang/handlebars.js#250 (comment)

Example JS usage:
http://jsfiddle.net/wMmHS/69/

Example template for hash:

<table>
  <tr>
    <th>key</th>
    <th>value</th>
  </tr>
  {{#each obj}}
  <tr>
    <td>{{@key}}:</td>
    <td>{{this}}</td>
  </tr>
  {{/each}}
</table>

ActionView Helper don't work in block

Hi,

let's say I have the following helper and template

# helpers/posts_helper.rb
module PostsHelper

  def posts
    @posts
  end

  def post_url(id)
    url_for controller: "posts", action: "show", id: id
  end

end
{{! views/posts/index.html.hbs }}
{{#each posts}}
<div>{{ post_url id }}</div>
{{/each}}

url_for will return nothing while the following template works just fine

{{! views/posts/index.html.hbs }}
<div>{{ post_url 1 }}</div>

Any ideas while the helper method won't work in the each block? What am I doing wrong?

Thanks for any help.

undefined method `[]' for nil:NilClass on calling render with empty params

To reproduce, try calling this:

  Tilt['handlebars'].new{'{{a.[0]}}'}.render

The error occurs, because @source in the following method is nil, but :[] method is tried to be called on it.

  def [](accessor)
    if array?
      if accessor.match /[0-9]+/
        return @source.at(accessor.to_i)
      end
    end
    @source[accessor]
  end

It can be checked explicitly, whether @source responds to :[]
@source = source || {} in the initializer could help too

What do you think?

Pass in locals when using handlebars partials?

Is it possible to pass in the local vars for a template like so:

render "templates/my_template.hbs", :locals => { :something => "asdf" }

And in the template:

{{something}}

I would be happy to implement this myself and pull request it if you think it would be useful for the gem.

":" in identifier name

If I understand correctly, according to this http://handlebarsjs.com/expressions.html (search "except for the following" on the page), it should be possible to use : (colon) as part of identifier name. We use colons in our templates, and I faced this issue while migrating our server-side handlebars compiling from V8 to FlavourSaver.
BTW, thanks for the great gem!

Inconsistency of IF helper with handlebars.js implementation.

The handlebars.js implementation and docs define the if helper as not rendering the block if the argument returns false, undefined, null, "", 0, or []. The implementation of if in FlavourSaver however will render the block if the argument is "" or 0, as both of these are "truthy" in Ruby. We're seeing issues with this as we're using handlebars templates that will be rendered both in Ruby and in our JS front-end app, and ideally the behaviour would be consistent.

Was this divergence intentional, and might it be fixed?

Documentation: Add Sinatra example

This is probably very basic, but I'm confused as to how to get FlavourSaver to work with Sinatra. I've got a route like this:

get '/handlebars' do
    handlebars :test
end

Not surprisingly, this gives an error: NoMethodError at /handlebars undefined method 'handlebars'...

I assume there is something I need to require or register, but I'm at a loss. It would be nice if a short example was added to README. (I'm extending Sinatra::Base if it makes any difference.)

Allow different instances of FlavourSaver

Currently, FlavourSaver is a singleton. This means that all uses have to share the same partials, helpers and so on. This is problematic, because it's sometimes desirable to have different instances in the same program, each with their own helpers, because these helpers can then close over local variables in the context they are created.

`Fixnum` deprecation warnings

lib/flavour_saver/nodes.rb:98: warning: constant ::Fixnum is deprecated
Maybe there is more

Integer can be used instead now.

TODO list

Hello James!

Thanks for the work on this gem. I was glad I found so quickly a pure impl of handlebars.

Do you have any sort of TODO list of stuff pending work on the gem? I'd love to get involved.

Thanks!

Add a ChangeLog

It's helpful when verifying when certain vulnerabilities were patched.

NoMethodError when rendering a template with partial directly with Tilt

It can be reproduced with the following example in rails console

FS.register_partial(:my_partial, 'I am a partial')

template = Tilt['handlebars'].new { '{{foo}} {{> my_partial}}' }
data = OpenStruct.new foo: 'Hello'

template.render(data)

It should output the string "Hello I am a partial", but it fail with this error

NoMethodError: undefined method `render' for #<OpenStruct foo="hello">
        from /flavour_saver-0.3.3/lib/flavour_saver/runtime.rb:107:in `evaluate_partial'
        from /flavour_saver-0.3.3/lib/flavour_saver/runtime.rb:88:in `evaluate_node'
        from /flavour_saver-0.3.3/lib/flavour_saver/runtime.rb:61:in `block in evaluate_node'
        from /flavour_saver-0.3.3/lib/flavour_saver/runtime.rb:61:in `map'
        from /flavour_saver-0.3.3/lib/flavour_saver/runtime.rb:61:in `evaluate_node'
        from /flavour_saver-0.3.3/lib/flavour_saver/runtime.rb:37:in `to_s'
        from /flavour_saver-0.3.3/lib/flavour_saver/runtime.rb:14:in `run'
        from /flavour_saver-0.3.3/lib/flavour_saver/template.rb:15:in `evaluate'
        from /tilt-1.4.1/lib

Bug with mixing literal and hash arguments.

FlavourSaver is throwing a StringNotInLanguage exception when it comes across helpers called with a mixture of literal and hash arguments, for example:

{{#linkTo "index" tagName="li"}}
  Home
{{/linkTo}}

Failing test committed in c19567c (args-bug branch).

Lexer does not accept identifier with an underscore prefix (e.g. `_3`)

If I'm reading the lexer source on the Handlebars library correctly, identifiers may utilize the underscore _ character.

The documentation also states:
Identifiers may be any unicode character except for the following: Whitespace ! " # % & ' ( ) * + , . / ; < = > @ [ \ ] ^ { | } ~`

However, I am unable to use an underscore in FlavourSaver:

>> Tilt['handlebars'].new { '{{_3}}' }
RLTK::LexingError: Unable to match string with any of the given rules:
        from /opt/boxen/rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/rltk-2.2.1/lib/rltk/lexer.rb:141:in `lex'
        from /opt/boxen/rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/flavour_saver-0.3.6/lib/flavour_saver/template.rb:11:in `prepare'
        from /opt/boxen/rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/tilt-1.4.1/lib/tilt/template.rb:79:in `initialize'
        from (irb):4:in `new'
        from (irb):4
        from /opt/boxen/rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/railties-4.2.1/lib/rails/commands/console.rb:110:in `start'
        from /opt/boxen/rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/railties-4.2.1/lib/rails/commands/console.rb:9:in `start'
        from /opt/boxen/rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/railties-4.2.1/lib/rails/commands/commands_tasks.rb:68:in `console'
        from /opt/boxen/rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/railties-4.2.1/lib/rails/commands/commands_tasks.rb:39:in `run_command!'
        from /opt/boxen/rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/railties-4.2.1/lib/rails/commands.rb:17:in `<top (required)>'
        from bin/rails:4:in `require'
        from bin/rails:4:in `<main>'
>> Tilt['handlebars'].new { '{{_3}}' }
RLTK::LexingError: Unable to match string with any of the given rules:
        from /opt/boxen/rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/rltk-2.2.1/lib/rltk/lexer.rb:141:in `lex'
        from /opt/boxen/rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/flavour_saver-0.3.6/lib/flavour_saver/template.rb:11:in `prepare'
        from /opt/boxen/rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/tilt-1.4.1/lib/tilt/template.rb:79:in `initialize'
        from (irb):5:in `new'
        from (irb):5
        from /opt/boxen/rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/railties-4.2.1/lib/rails/commands/console.rb:110:in `start'
        from /opt/boxen/rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/railties-4.2.1/lib/rails/commands/console.rb:9:in `start'
        from /opt/boxen/rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/railties-4.2.1/lib/rails/commands/commands_tasks.rb:68:in `console'
        from /opt/boxen/rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/railties-4.2.1/lib/rails/commands/commands_tasks.rb:39:in `run_command!'
        from /opt/boxen/rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/railties-4.2.1/lib/rails/commands.rb:17:in `<top (required)>'
        from bin/rails:4:in `require'
        from bin/rails:4:in `<main>'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.