handlebarer 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.md +283 -0
- data/Rakefile +14 -0
- data/lib/handlebarer/compiler.rb +69 -0
- data/lib/handlebarer/configuration.rb +28 -0
- data/lib/handlebarer/engine.rb +22 -0
- data/lib/handlebarer/renderer.rb +40 -0
- data/lib/handlebarer/serialize.rb +107 -0
- data/lib/handlebarer/source.rb +26 -0
- data/lib/handlebarer/template.rb +30 -0
- data/lib/handlebarer/version.rb +3 -0
- data/lib/handlebarer.rb +12 -0
- data/lib/tasks/handlebarer_tasks.rake +4 -0
- metadata +187 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2013 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,283 @@
|
|
1
|
+
# Handlerbarer - Share your Handlerbars templates between client and server
|
2
|
+
|
3
|
+
Handlerbars is a very popular templating engine, recently given much deserved attention due to the awesome Ember.js Framework.
|
4
|
+
Handlebarer gives you ability to easily use Handlerbars templates for both server and client side in your Rails project.
|
5
|
+
|
6
|
+
On the client-side your templates should be used with Rails' JST engine. On the server side, you can render your
|
7
|
+
Handlerbars templates as Rails views (similar to how you'd render ERB or HAML templates).
|
8
|
+
|
9
|
+
## Writing your templates
|
10
|
+
|
11
|
+
Lets assume you have a users controller `app/controllers/users_controller.rb` which in turn renders a list of users.
|
12
|
+
We'd like to share that view between the client and the server.
|
13
|
+
|
14
|
+
### Server-Side code
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
class UsersController < ApplicationController
|
18
|
+
|
19
|
+
def index
|
20
|
+
@users = User.all
|
21
|
+
respond_to do |format|
|
22
|
+
format.html
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
To share our template between client and server, we need to place it under `app/assets/javascripts` for Sprockets' JST engine.
|
30
|
+
|
31
|
+
Lets create a views directory for our shared templates `app/assets/javascripts/views` and add our template there, following Rails' naming convensions.
|
32
|
+
|
33
|
+
The full template path should look like this: `app/assets/javascripts/views/users/index.jst.hsb`
|
34
|
+
|
35
|
+
### Template code
|
36
|
+
|
37
|
+
The most significant differences between using standard server-side Ruby-based engines like ERB or HAML and using Handlerbarer are:
|
38
|
+
|
39
|
+
* No access to server-side view helpers (such as url_for)
|
40
|
+
* No ruby-style instance variable like `@users`
|
41
|
+
* Template code is not Ruby and has to follow Handlerbars' syntax rather than embedded Ruby syntax
|
42
|
+
* No partials or includes (for now)
|
43
|
+
|
44
|
+
Our template code should look like this:
|
45
|
+
|
46
|
+
```html
|
47
|
+
<ul class="users">
|
48
|
+
{{#users}}
|
49
|
+
<li>{{{link_to this}}}</li>
|
50
|
+
{{/users}}
|
51
|
+
</ul>
|
52
|
+
```
|
53
|
+
|
54
|
+
Note that rendering this template server-side, will be done inside your application's layout. You can write your views layout file in ERB / HAML
|
55
|
+
and the call to `=yield` will render your Handlerbars template above.
|
56
|
+
|
57
|
+
### Sharing template code
|
58
|
+
|
59
|
+
Since Rails doesn't expect server-side templates to live under `app/assets` we need to add our client-side views path to Rails views lookup path.
|
60
|
+
|
61
|
+
Assuming we have an initializer `app/config/initializers/jader.rb` we can add our client-side views directory like this:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
Handlerbarer.configure do |config|
|
65
|
+
# make your client-side views directory discoverable to Rails
|
66
|
+
config.views_path = Rails.root.join('app','assets','javascripts','views')
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
Internally, this adds a `before_filter` to `ApplicationController::Base` that prepends the provided path to `ActionView::Context` .
|
71
|
+
|
72
|
+
### Client-side code
|
73
|
+
|
74
|
+
To render the same template from the client, we need to fetch our users list from the server and then call our JST template with that list.
|
75
|
+
|
76
|
+
First, lets change our controller to return a JSON formatted list of users when called from the client:
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
class UsersController < ApplicationController
|
80
|
+
|
81
|
+
def index
|
82
|
+
@users = User.all
|
83
|
+
respond_to do |format|
|
84
|
+
format.html
|
85
|
+
format.json {
|
86
|
+
render :json => @users.to_hbs
|
87
|
+
}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
Note the call to `to_hbs` on the `@users` collection. This ensures our users are properly serialized for use inside our template.
|
95
|
+
See the [Serialization](https://github.com/zohararad/jader#serialization) section below for more details.
|
96
|
+
|
97
|
+
In our `application.js` file lets write the following:
|
98
|
+
|
99
|
+
```javascript
|
100
|
+
//= require handlebars/runtime
|
101
|
+
//= require views/users/index
|
102
|
+
|
103
|
+
$.getJSON('/users', function(users){
|
104
|
+
$('body').html(JST['views/users/index']({users:users}));
|
105
|
+
});
|
106
|
+
```
|
107
|
+
|
108
|
+
## Serialization
|
109
|
+
|
110
|
+
To help Handlerbarer access Ruby and Rails variables inside the template, we need to employ some sort of JSON serializing before passing
|
111
|
+
these variables to the template. On the server-side, this happens automagically before the template is rendered.
|
112
|
+
|
113
|
+
Internally, Handlerbarer will try to call the `to_hbs` method on each instance variable that's passed to the template. Ruby's Hash, Array and Object classes have been extended
|
114
|
+
to support this functionality. Arrays and Hashes will attempt to call the `to_hbs` method on their members when `to_hbs` is invoked on their instances. For
|
115
|
+
other collection-like variables, the `to_hbs` method will only be invoked if they respond to a `to_a` method. This allows ActiveModel / ActiveRecord instance variables to
|
116
|
+
automatically serialize their members before rendering.
|
117
|
+
|
118
|
+
### Serializing models
|
119
|
+
|
120
|
+
Handlerbarer does not assume your Rails models should be serialized by default. Instead, it expects you to enable serializing on desired models explicitly.
|
121
|
+
|
122
|
+
To enable this behaviour, consider the following example:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
class User < ActiveRecord::Base
|
126
|
+
|
127
|
+
include Handlerbarer::Serialize
|
128
|
+
|
129
|
+
hbs_serializable :name, :email, :favorites, :merge => false
|
130
|
+
|
131
|
+
end
|
132
|
+
```
|
133
|
+
|
134
|
+
The call to `include Handlerbarer::Serialize` mixes Handlerbarer::Serializer capabilities into our model class.
|
135
|
+
|
136
|
+
We can then tell the serializer which attributes we'd like to serialize, and how we'd like the serialization to work.
|
137
|
+
|
138
|
+
By default, calling `hbs_serializable` with no arguments will serialize all your model attributes. Lets look at two examples:
|
139
|
+
|
140
|
+
Consider the following code:
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
# define our model
|
144
|
+
class User < ActiveRecord::Base
|
145
|
+
|
146
|
+
include Handlerbarer::Serialize
|
147
|
+
|
148
|
+
hbs_serializable
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
# access in controller
|
153
|
+
class UsersController < ApplicationController
|
154
|
+
|
155
|
+
def index
|
156
|
+
@users = User.all
|
157
|
+
@users.to_hbs # => all available user attributes (users table columns) will be serialized
|
158
|
+
end
|
159
|
+
|
160
|
+
def active
|
161
|
+
@users = User.where('active = 1').select('name, email')
|
162
|
+
@users.to_hbs # => only name and email attributes are serialized
|
163
|
+
end
|
164
|
+
end
|
165
|
+
```
|
166
|
+
|
167
|
+
For better control over which attributes are serialized, and when serializing model relationships, we can tell the serializer
|
168
|
+
which attributes should always be serialized, and whether we'd like these attributes to be merged with the default attributes or not.
|
169
|
+
|
170
|
+
Consider the following code:
|
171
|
+
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
# define our models
|
175
|
+
|
176
|
+
class Favorite < ActiveRecord::Base
|
177
|
+
|
178
|
+
include Handlerbarer::Serialize
|
179
|
+
|
180
|
+
hbs_serializable
|
181
|
+
|
182
|
+
belongs_to :user
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
class User < ActiveRecord::Base
|
187
|
+
|
188
|
+
include Handlerbarer::Serialize
|
189
|
+
|
190
|
+
hbs_serializable :favorites, :merge => true
|
191
|
+
|
192
|
+
has_many :favorites
|
193
|
+
end
|
194
|
+
|
195
|
+
# access in controller
|
196
|
+
class UsersController < ApplicationController
|
197
|
+
|
198
|
+
def active
|
199
|
+
@users = User.where('active = 1').select('name, email')
|
200
|
+
@users.to_hbs # => only name, email and favorites attributes are serialized
|
201
|
+
end
|
202
|
+
end
|
203
|
+
```
|
204
|
+
|
205
|
+
In the above, we defined serialization for the `User` model to include the `:favorites` attribute, which is available because of the `has_many` relationship
|
206
|
+
to the `Favorite` model. Additionally, we specified that serialization should merge model default attributes with the specified attributes, by setting `:merge => true` .
|
207
|
+
|
208
|
+
This will result in merging `self.attributes` and `self.favorites` on any instance of the `User` model when calling the `to_hbs` method on it.
|
209
|
+
|
210
|
+
To only serialize the specified attributes, call `hbs_serializable` with `:merge => false` .
|
211
|
+
|
212
|
+
Invokation format for `hbs_serializable` is:
|
213
|
+
|
214
|
+
```ruby
|
215
|
+
hbs_serializable :attr1, :attr2, :attr3 ...., :merge => true/false
|
216
|
+
```
|
217
|
+
|
218
|
+
By default, `hbs_serializable` will operate with `:merge => true` and merge instnace attributes with specified attributes.
|
219
|
+
|
220
|
+
## Helpers
|
221
|
+
|
222
|
+
Handlerbars has built in support for helper function registration, which is great for client-side rendering, but alas requires a
|
223
|
+
bit of extra work for server-side rendering.
|
224
|
+
|
225
|
+
Lets assume you have registered Handlebars helpers reside under `app/assets/javascripts/helpers/link.js`
|
226
|
+
that might look something like this:
|
227
|
+
|
228
|
+
```javascript
|
229
|
+
Handlebars.registerHelper('link_to', function(context) {
|
230
|
+
return '<a href="/users/' + context.id + '">' + context.name + '</a>';
|
231
|
+
});
|
232
|
+
```
|
233
|
+
|
234
|
+
On the client-side, you might add to the asset pipeline like this:
|
235
|
+
|
236
|
+
```javascript
|
237
|
+
//= require handlebars/runtime
|
238
|
+
//= require helpers/link.js
|
239
|
+
```
|
240
|
+
|
241
|
+
To use the same helper on the server-side, you'll need to configre Handlebarer like so:
|
242
|
+
|
243
|
+
```ruby
|
244
|
+
Handlerbarer.configure do |config|
|
245
|
+
config.helpers_path = Rails.root.join('app','assets','javascripts','helpers')
|
246
|
+
end
|
247
|
+
```
|
248
|
+
|
249
|
+
When rendering your Handlebars template server-side, Handlerbarer will look for any Javascript file in the helpers path and
|
250
|
+
include it in the rendering context.
|
251
|
+
|
252
|
+
Please note that at the moment, Handlebarer only supports Javascript helper files rather than both Javascript and CoffeeScript.
|
253
|
+
|
254
|
+
## Configuration
|
255
|
+
|
256
|
+
Its recommended to configure Handlerbarer inside a Rails initializer so that configuration is defined at boot time.
|
257
|
+
|
258
|
+
Assuming we have an initializer `app/config/initializers/jader.rb` it should include:
|
259
|
+
|
260
|
+
```ruby
|
261
|
+
Handlerbarer.configure do |config|
|
262
|
+
# tell Handlebarer where to find your Handlebars helpers
|
263
|
+
config.helpers_path = Rails.root.join('app','assets','javascripts','helpers')
|
264
|
+
# make your client-side views directory discoverable to Rails
|
265
|
+
config.views_path = Rails.root.join('app','assets','javascripts','views')
|
266
|
+
end
|
267
|
+
```
|
268
|
+
|
269
|
+
## Asset Pipeline
|
270
|
+
|
271
|
+
In case your Rails asset pipeline is configured **not** to load the entire Rails environment when calling `rake assets:precompile`, you should include Handlerbarer's configuration initalizer in your Rakefile.
|
272
|
+
|
273
|
+
Simply add `require File.expand_path('../config/initializers/handlebarer', __FILE__)` before `require File.expand_path('../config/application', __FILE__)` in your Rakefile, and ensure Handlerbarer is properly configured when your assets are precompiled
|
274
|
+
|
275
|
+
# License
|
276
|
+
|
277
|
+
Copyright (c) 2013 Zohar Arad <zohar@zohararad.com>
|
278
|
+
|
279
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
280
|
+
|
281
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
282
|
+
|
283
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'bundler/gem_tasks'
|
9
|
+
require 'rspec/core/rake_task'
|
10
|
+
|
11
|
+
RSpec::Core::RakeTask.new('spec')
|
12
|
+
|
13
|
+
Bundler::GemHelper.install_tasks
|
14
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'v8'
|
2
|
+
|
3
|
+
module Handlebarer
|
4
|
+
class Compiler
|
5
|
+
|
6
|
+
# Handlerbars template engine Javascript source code used to compile templates in ExecJS
|
7
|
+
# @return [String] Handlerbars source code
|
8
|
+
def source
|
9
|
+
@source ||= Handlebarer::Source::handlebars
|
10
|
+
end
|
11
|
+
|
12
|
+
# V8 context with Handlerbars code compiled
|
13
|
+
# @yield [context] V8::Context compiled Handlerbars source code in V8 context
|
14
|
+
def v8_context
|
15
|
+
V8::C::Locker() do
|
16
|
+
context = V8::Context.new
|
17
|
+
context.eval(source)
|
18
|
+
yield context
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Handlerbars Javascript engine version
|
23
|
+
# @return [String] version of Handlerbars javascript engine installed in `vendor/assets/javascripts`
|
24
|
+
def handlebars_version
|
25
|
+
v8_context do |context|
|
26
|
+
context.eval("Handlebars.VERSION")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Compile a Handlerbars template for client-side use with JST
|
31
|
+
# @param [String, File] template Handlerbars template file or text to compile
|
32
|
+
# @return [String] Handlerbars template compiled into Javascript and wrapped inside an anonymous function for JST
|
33
|
+
def compile(template)
|
34
|
+
v8_context do |context|
|
35
|
+
template = template.read if template.respond_to?(:read)
|
36
|
+
compiled_handlebars = context.eval("Handlebars.precompile(#{template.to_json})")
|
37
|
+
"Handlebars.template(#{compiled_handlebars});"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Compile and evaluate a Handlerbars template for server-side rendering
|
42
|
+
# @param [String] template Handlerbars template text to render
|
43
|
+
# @param [Hash] vars controller instance variables passed to the template
|
44
|
+
# @return [String] HTML output of compiled Handlerbars template
|
45
|
+
def render(template, vars = {})
|
46
|
+
v8_context do |context|
|
47
|
+
unless Handlebarer.configuration.nil?
|
48
|
+
helpers = handlebars_helpers
|
49
|
+
context.eval(helpers.join("\n")) if helpers.any?
|
50
|
+
end
|
51
|
+
context.eval("var fn = Handlebars.compile(#{template.to_json})")
|
52
|
+
context.eval("fn(#{vars.to_hbs.to_json})")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Handlebars helpers
|
57
|
+
# @return [Array<String>] array of Handlebars helpers to use with a Handlebars template rendered by a Rails controller
|
58
|
+
def handlebars_helpers
|
59
|
+
helpers = []
|
60
|
+
unless Handlebarer.configuration.helpers_path.nil?
|
61
|
+
Dir["#{Handlebarer.configuration.helpers_path}/*.js"].each do |f|
|
62
|
+
helpers << IO.read(f)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
helpers
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Handlebarer
|
2
|
+
class << self
|
3
|
+
attr_accessor :configuration
|
4
|
+
end
|
5
|
+
|
6
|
+
# Configure Handlebarer
|
7
|
+
# @yield [config] Handlebarer::Configuration instance
|
8
|
+
# @example
|
9
|
+
# Handlebarer.configure do |config|
|
10
|
+
# config.helpers_path = Rails.root.join('app','assets','javascripts','helpers')
|
11
|
+
# config.views_path = Rails.root.join('app','assets','javascripts','views')
|
12
|
+
# end
|
13
|
+
def self.configure
|
14
|
+
self.configuration ||= Configuration.new
|
15
|
+
yield(configuration)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Jader configuration class
|
19
|
+
class Configuration
|
20
|
+
attr_accessor :helpers_path, :views_path
|
21
|
+
|
22
|
+
# Initialize Jader::Configuration class with default values
|
23
|
+
def initialize
|
24
|
+
@helpers_path = nil
|
25
|
+
@views_path = nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'sprockets'
|
2
|
+
require 'sprockets/engines'
|
3
|
+
|
4
|
+
module Handlebarer
|
5
|
+
class Engine < Rails::Engine
|
6
|
+
initializer 'handlebarer.configure_rails_initialization', :before => 'sprockets.environment', :group => :all do |app|
|
7
|
+
next unless app.config.assets.enabled
|
8
|
+
Sprockets.register_engine '.hbs', ::Handlebarer::Template
|
9
|
+
Sprockets.register_engine '.handlebars', ::Handlebarer::Template
|
10
|
+
end
|
11
|
+
|
12
|
+
initializer 'handlebarer.prepend_views_path', :after => :add_view_paths do |app|
|
13
|
+
next if Handlebarer::configuration.nil? or Handlebarer::configuration.views_path.nil?
|
14
|
+
ActionController::Base.class_eval do
|
15
|
+
before_filter do |controller|
|
16
|
+
prepend_view_path Handlebarer::configuration.views_path
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Handlebarer
|
2
|
+
# Server side Jade templates renderer
|
3
|
+
module Renderer
|
4
|
+
|
5
|
+
# Convert Handlebars template to HTML output for rendering as a Rails view
|
6
|
+
# @param [String] template_text Handlebars template text to convert
|
7
|
+
# @param [String] controller_name name of Rails controller rendering the view
|
8
|
+
# @param [Hash] vars controller instance variables passed to the template
|
9
|
+
# @return [String] HTML output of evaluated template
|
10
|
+
# @see Handlebarer::Compiler#render
|
11
|
+
def self.convert_template(template_text, vars = {})
|
12
|
+
compiler = Handlebarer::Compiler.new
|
13
|
+
compiler.render(template_text, vars)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Prepare controller instance variables for the template and execute template conversion.
|
17
|
+
# Called as an ActionView::Template registered template
|
18
|
+
# @param [ActionView::Template] template currently rendered ActionView::Template instance
|
19
|
+
# @see Handlebarer::Renderer#convert_template
|
20
|
+
def self.call(template)
|
21
|
+
#template.source.gsub!(/\#\{([^\}]+)\}/,"\\\#{\\1}") # escape Handlebars' #{somevariable} syntax
|
22
|
+
%{
|
23
|
+
template_source = %{#{template.source}}
|
24
|
+
variable_names = controller.instance_variable_names
|
25
|
+
variable_names -= %w[@template]
|
26
|
+
if controller.respond_to?(:protected_instance_variables)
|
27
|
+
variable_names -= controller.protected_instance_variables
|
28
|
+
end
|
29
|
+
|
30
|
+
variables = {}
|
31
|
+
variable_names.each do |name|
|
32
|
+
next if name.include? '@_'
|
33
|
+
variables[name.sub(/^@/, "")] = controller.instance_variable_get(name)
|
34
|
+
end
|
35
|
+
Handlebarer::Renderer.convert_template(template_source, variables.merge(local_assigns))
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Handlebarer
|
2
|
+
|
3
|
+
module Serialize
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
|
7
|
+
# Enable serialization on ActiveModel classes
|
8
|
+
# @param [Array<Symbol>] args model attribute names to serialize
|
9
|
+
# @param [Hash] args options serializing mode
|
10
|
+
# @option args [Boolean] :merge should serialized attributes be merged with `self.attributes`
|
11
|
+
# @example
|
12
|
+
# class User < ActiveRecord::Base
|
13
|
+
# include Handlebarer::Serialize
|
14
|
+
# hbs_serializable :name, :email, :merge => false
|
15
|
+
# end
|
16
|
+
def hbs_serializable(*args)
|
17
|
+
serialize = {
|
18
|
+
:attrs => [],
|
19
|
+
:merge => true
|
20
|
+
}
|
21
|
+
args.each do |arg|
|
22
|
+
if arg.is_a? Symbol
|
23
|
+
serialize[:attrs] << arg
|
24
|
+
elsif arg.is_a? Hash
|
25
|
+
serialize[:merge] = arg[:merge] if arg.include?(:merge)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
class_variable_set(:@@serialize, serialize)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
#nodoc
|
34
|
+
def self.included(base)
|
35
|
+
base.extend ClassMethods
|
36
|
+
end
|
37
|
+
|
38
|
+
# Serialize instance attributes to a Hash based on serializable attributes defined on Model class.
|
39
|
+
# @return [Hash] hash of model instance attributes
|
40
|
+
def to_hbs
|
41
|
+
h = {:model => self.class.name.downcase}
|
42
|
+
self.hbs_attributes.each do |attr|
|
43
|
+
h[attr] = self.send(attr)
|
44
|
+
|
45
|
+
ans = h[attr].class.ancestors
|
46
|
+
if h[attr].class.respond_to?(:hbs_serializable) || ans.include?(Enumerable) || ans.include?(ActiveModel::Validations)
|
47
|
+
h[attr] = h[attr].to_hbs
|
48
|
+
else
|
49
|
+
end
|
50
|
+
end
|
51
|
+
h
|
52
|
+
end
|
53
|
+
|
54
|
+
# List of Model attributes that should be serialized when called `to_hbs` on Model instance
|
55
|
+
# @return [Array] list of serializable attributes
|
56
|
+
def hbs_attributes
|
57
|
+
s = self.class.class_variable_get(:@@serialize)
|
58
|
+
if s[:merge]
|
59
|
+
attrs = s[:attrs] + self.attributes.keys
|
60
|
+
else
|
61
|
+
attrs = s[:attrs]
|
62
|
+
end
|
63
|
+
attrs.collect{|attr| attr.to_sym}.uniq
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
class Object
|
70
|
+
# Serialize Object to Jade format. Invoke `self.to_hbs` if instance responds to `to_hbs`
|
71
|
+
def to_hbs
|
72
|
+
if self.respond_to? :to_a
|
73
|
+
self.to_a.to_hbs
|
74
|
+
else
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
#nodoc
|
81
|
+
[FalseClass, TrueClass, Numeric, String].each do |cls|
|
82
|
+
cls.class_eval do
|
83
|
+
def to_hbs
|
84
|
+
self
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class Array
|
90
|
+
|
91
|
+
# Serialize Array to Handlebarer format. Invoke `to_hbs` on array members
|
92
|
+
def to_hbs
|
93
|
+
map {|a| a.respond_to?(:to_hbs) ? a.to_hbs : a }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class Hash
|
98
|
+
|
99
|
+
# Serialize Hash to Handlebarer format. Invoke `to_hbs` on members
|
100
|
+
def to_hbs
|
101
|
+
res = {}
|
102
|
+
each_pair do |key, value|
|
103
|
+
res[key] = (value.respond_to?(:to_hbs) ? value.to_hbs : value)
|
104
|
+
end
|
105
|
+
res
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Handlebarer
|
2
|
+
# Handlebars template engine Javascript source code
|
3
|
+
module Source
|
4
|
+
|
5
|
+
# Handlebars source code
|
6
|
+
def self.handlebars
|
7
|
+
IO.read handlebars_path
|
8
|
+
end
|
9
|
+
|
10
|
+
# Handlebars source code path
|
11
|
+
def self.handlebars_path
|
12
|
+
File.expand_path("../../../vendor/assets/javascripts/handlebars/handlebars.js", __FILE__)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Handlebars runtime source code
|
16
|
+
def self.runtime
|
17
|
+
IO.read runtime_path
|
18
|
+
end
|
19
|
+
|
20
|
+
# Handlebars runtime source code path
|
21
|
+
def self.runtime_path
|
22
|
+
File.expand_path("../../../vendor/assets/javascripts/handlebars/runtime.js", __FILE__)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'tilt/template'
|
2
|
+
|
3
|
+
module Handlebarer
|
4
|
+
|
5
|
+
# Handlebarer Tilt template for use with JST
|
6
|
+
class Template < Tilt::Template
|
7
|
+
self.default_mime_type = 'application/javascript'
|
8
|
+
|
9
|
+
# Ensure V8 is available when engine is initialized
|
10
|
+
def self.engine_initialized?
|
11
|
+
defined? ::V8
|
12
|
+
end
|
13
|
+
|
14
|
+
# Require 'execjs' when initializing engine
|
15
|
+
def initialize_engine
|
16
|
+
require_template_library 'v8'
|
17
|
+
end
|
18
|
+
|
19
|
+
def prepare
|
20
|
+
end
|
21
|
+
|
22
|
+
# Evaluate the template. Compiles the template for JST
|
23
|
+
# @return [String] JST-compliant compiled version of the Handlebars template being rendered
|
24
|
+
def evaluate(scope, locals, &block)
|
25
|
+
c = Handlebarer::Compiler.new
|
26
|
+
c.compile(data)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
data/lib/handlebarer.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'handlebarer/source'
|
2
|
+
require 'handlebarer/compiler'
|
3
|
+
require 'handlebarer/template'
|
4
|
+
require 'handlebarer/engine' if defined?(::Rails)
|
5
|
+
require 'handlebarer/renderer'
|
6
|
+
require 'handlebarer/serialize'
|
7
|
+
require 'handlebarer/configuration'
|
8
|
+
|
9
|
+
ActionView::Template.register_template_handler :handlebars, Handlebarer::Renderer
|
10
|
+
ActionView::Template.register_template_handler :hbs, Handlebarer::Renderer
|
11
|
+
ActionView::Template.register_template_handler 'jst.handlebars', Handlebarer::Renderer
|
12
|
+
ActionView::Template.register_template_handler 'jst.hbs', Handlebarer::Renderer
|
metadata
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: handlebarer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Zohar Arad
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-12 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: tilt
|
16
|
+
prerelease: false
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
none: false
|
23
|
+
type: :runtime
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ! '>='
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '0'
|
29
|
+
none: false
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: sprockets
|
32
|
+
prerelease: false
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
none: false
|
39
|
+
type: :runtime
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
none: false
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: therubyracer
|
48
|
+
prerelease: false
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
none: false
|
55
|
+
type: :runtime
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ! '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
none: false
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: libv8
|
64
|
+
prerelease: false
|
65
|
+
requirement: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - '='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 3.11.8
|
70
|
+
none: false
|
71
|
+
type: :runtime
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - '='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 3.11.8
|
77
|
+
none: false
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rspec
|
80
|
+
prerelease: false
|
81
|
+
requirement: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
none: false
|
87
|
+
type: :development
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
none: false
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: rspec-rails
|
96
|
+
prerelease: false
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
none: false
|
103
|
+
type: :development
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ! '>='
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
none: false
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rails
|
112
|
+
prerelease: false
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 3.2.11
|
118
|
+
none: false
|
119
|
+
type: :development
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ~>
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 3.2.11
|
125
|
+
none: false
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: sqlite3
|
128
|
+
prerelease: false
|
129
|
+
requirement: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
none: false
|
135
|
+
type: :development
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ! '>='
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
none: false
|
142
|
+
description: Share your Handlebars views between client and server, eliminate code
|
143
|
+
duplication and make your single-page app SEO friendly
|
144
|
+
email:
|
145
|
+
- zohar@zohararad.com
|
146
|
+
executables: []
|
147
|
+
extensions: []
|
148
|
+
extra_rdoc_files: []
|
149
|
+
files:
|
150
|
+
- lib/handlebarer/compiler.rb
|
151
|
+
- lib/handlebarer/configuration.rb
|
152
|
+
- lib/handlebarer/engine.rb
|
153
|
+
- lib/handlebarer/renderer.rb
|
154
|
+
- lib/handlebarer/serialize.rb
|
155
|
+
- lib/handlebarer/source.rb
|
156
|
+
- lib/handlebarer/template.rb
|
157
|
+
- lib/handlebarer/version.rb
|
158
|
+
- lib/handlebarer.rb
|
159
|
+
- lib/tasks/handlebarer_tasks.rake
|
160
|
+
- MIT-LICENSE
|
161
|
+
- Rakefile
|
162
|
+
- README.md
|
163
|
+
homepage: https://github.com/zohararad/handlebarer
|
164
|
+
licenses: []
|
165
|
+
post_install_message:
|
166
|
+
rdoc_options: []
|
167
|
+
require_paths:
|
168
|
+
- lib
|
169
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ! '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
none: false
|
175
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - ! '>='
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '0'
|
180
|
+
none: false
|
181
|
+
requirements: []
|
182
|
+
rubyforge_project:
|
183
|
+
rubygems_version: 1.8.24
|
184
|
+
signing_key:
|
185
|
+
specification_version: 3
|
186
|
+
summary: JST and Rails views compiler for Handlebars templates
|
187
|
+
test_files: []
|