booster 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +7 -0
  3. data/Gemfile.lock +106 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +174 -0
  6. data/Rakefile +7 -0
  7. data/booster.gemspec +24 -0
  8. data/booster.tmbundle/Snippets/untitled.tmSnippet +14 -0
  9. data/booster.tmbundle/Syntaxes/Booster.tmLanguage +46 -0
  10. data/booster.tmbundle/info.plist +10 -0
  11. data/lib/assets/javascripts/booster-core.js +5 -0
  12. data/lib/assets/javascripts/booster-support.js +1 -0
  13. data/lib/assets/javascripts/booster.js +2 -0
  14. data/lib/assets/javascripts/booster/collection.js.boost +1 -0
  15. data/lib/assets/javascripts/booster/model.js.boost +30 -0
  16. data/lib/assets/javascripts/booster/router.js.boost +107 -0
  17. data/lib/assets/javascripts/booster/support/binding.js.boost +1 -0
  18. data/lib/assets/javascripts/booster/support/helpers.js.boost +109 -0
  19. data/lib/assets/javascripts/booster/support/i18n.js.boost +136 -0
  20. data/lib/assets/javascripts/booster/support/observer.js.boost +81 -0
  21. data/lib/assets/javascripts/booster/support/schema.js.boost +117 -0
  22. data/lib/assets/javascripts/booster/view.js.boost +45 -0
  23. data/lib/assets/javascripts/booster/views/composite.js.boost +59 -0
  24. data/lib/assets/javascripts/booster/views/layout.js.boost +94 -0
  25. data/lib/booster.rb +7 -0
  26. data/lib/booster/engine.rb +8 -0
  27. data/lib/booster/handlebars.rb +50 -0
  28. data/lib/booster/template.rb +59 -0
  29. data/lib/booster/version.rb +3 -0
  30. data/test/booster/tilt_test.rb +35 -0
  31. data/test/dummy/Rakefile +7 -0
  32. data/test/dummy/app/assets/javascripts/application.js.boost +18 -0
  33. data/test/dummy/app/assets/javascripts/booster/support/helpers_spec.js.boost +69 -0
  34. data/test/dummy/app/assets/javascripts/booster/support/i18n_spec.js.boost +0 -0
  35. data/test/dummy/app/assets/javascripts/booster/support/observer_spec.js.boost +56 -0
  36. data/test/dummy/app/assets/javascripts/booster/support/router_spec.js.boost +19 -0
  37. data/test/dummy/app/assets/javascripts/booster/support/schema_spec.js.boost +79 -0
  38. data/test/dummy/app/assets/javascripts/booster/views/layout_spec.js.boost +30 -0
  39. data/test/dummy/app/controllers/application_controller.rb +7 -0
  40. data/test/dummy/app/views/layouts/application.html.erb +29 -0
  41. data/test/dummy/config.ru +4 -0
  42. data/test/dummy/config/application.rb +30 -0
  43. data/test/dummy/config/boot.rb +10 -0
  44. data/test/dummy/config/environment.rb +5 -0
  45. data/test/dummy/config/environments/development.rb +27 -0
  46. data/test/dummy/config/environments/production.rb +29 -0
  47. data/test/dummy/config/environments/test.rb +34 -0
  48. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  49. data/test/dummy/config/initializers/inflections.rb +10 -0
  50. data/test/dummy/config/initializers/mime_types.rb +5 -0
  51. data/test/dummy/config/initializers/secret_token.rb +7 -0
  52. data/test/dummy/config/initializers/session_store.rb +8 -0
  53. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  54. data/test/dummy/config/locales/en.yml +5 -0
  55. data/test/dummy/config/routes.rb +3 -0
  56. data/test/dummy/public/404.html +26 -0
  57. data/test/dummy/public/422.html +26 -0
  58. data/test/dummy/public/500.html +26 -0
  59. data/test/dummy/public/favicon.ico +0 -0
  60. data/test/dummy/script/rails +6 -0
  61. data/test/test_helper.rb +4 -0
  62. data/vendor/assets/javascripts/backbone.js +1158 -0
  63. data/vendor/assets/javascripts/handlebars-helpers.js +56 -0
  64. data/vendor/assets/javascripts/handlebars-vm.js +191 -0
  65. data/vendor/assets/javascripts/handlebars.js +1561 -0
  66. data/vendor/assets/javascripts/jasmine-html.js +190 -0
  67. data/vendor/assets/javascripts/jasmine-jquery.js +288 -0
  68. data/vendor/assets/javascripts/jasmine.js +2471 -0
  69. data/vendor/assets/javascripts/stitch.js +57 -0
  70. data/vendor/assets/javascripts/underscore.js +981 -0
  71. data/vendor/assets/stylesheets/jasmine.css +166 -0
  72. metadata +172 -0
@@ -0,0 +1,45 @@
1
+ var observer = require('./support/observer');
2
+
3
+ /**
4
+ * Extension of {Backbone.View} which supports the concept of entering
5
+ * and leaving a parent view -- a Composite in Booster. Leaving a composite
6
+ * means disappearing from DOM and likely involves unbinding from any events
7
+ * bound to the underlying model or collection.
8
+ *
9
+ * @extends {Backbone.View}
10
+ * @constructor
11
+ */
12
+
13
+ exports.View = Backbone.View.extend({
14
+
15
+ /**
16
+ * Invoked when the view has entered a parent view (a composite). The default
17
+ * behavior is to store a link to the composite for future reference.
18
+ *
19
+ * @param {Backbone.View} parent The composite view to which this view is added.
20
+ */
21
+
22
+ enter: function(parent) {
23
+ this.parent = parent;
24
+ return this;
25
+ },
26
+
27
+ /**
28
+ * Invoked when the view is to leave the parent composite. The default behavior is
29
+ * to remove the view from the DOM and unbind any event listeners, as well as any
30
+ * bindings to {Backbone.Events} sources.
31
+ *
32
+ * This method should not normally be invoked directly. Instead, invoke `remove` on
33
+ * the parent view which, in turn, will invoke `leave`.
34
+ */
35
+
36
+ leave: function() {
37
+ $(this.el).remove();
38
+ this.unobserve();
39
+ this.unbind();
40
+ return this;
41
+ }
42
+
43
+ });
44
+
45
+ _.extend(exports.View.prototype, observer.mixin());
@@ -0,0 +1,59 @@
1
+ var base = require('../view');
2
+
3
+ /**
4
+ * @extends {base.View}
5
+ * @constructor
6
+ */
7
+
8
+ exports.View = base.View.extend({
9
+
10
+ /**
11
+ * Adds a new child view to the composite. This does not add the view to the DOM tree,
12
+ * which needs to be handled by the inheriting view. The added view will get its
13
+ * `enter()` function invoked if it wants to register a link back to this composite.
14
+ *
15
+ * @param {base.View} view The child view to be added to the composite.
16
+ * @return {base.View} The child view that was passed in.
17
+ */
18
+
19
+ add: function(view) {
20
+ this.children || (this.children = []);
21
+ this.children.push(view);
22
+ return view.enter(this);
23
+ },
24
+
25
+ /**
26
+ * Removes the child view from the composite. This also removes the view from the
27
+ * DOM tree by invoking its `leave()` function, which also unbinds from any underlying
28
+ * model or collection.
29
+ *
30
+ * @param {base.View} view The child view to remove from the composite.
31
+ * @param {base.View|undefined} The view that was removed or `undefined` if it was not in the composite.
32
+ */
33
+
34
+ remove: function(view) {
35
+ var index = this.children.indexOf(view);
36
+ if (index > -1) {
37
+ list.splice(index, 1);
38
+ delete view.parent;
39
+ return view.leave();
40
+ }
41
+ },
42
+
43
+
44
+ /**
45
+ * Removes the composite from the parent. Overridden to recursively invoke `leave` on all
46
+ * the child views first.
47
+ *
48
+ * @override
49
+ */
50
+
51
+ leave: function() {
52
+ _.each(this.children, function(view) {
53
+ view.leave();
54
+ });
55
+
56
+ base.View.prototype.leave.call(this);
57
+ }
58
+
59
+ });
@@ -0,0 +1,94 @@
1
+ var composite = require('./composite');
2
+
3
+ /**
4
+ * A {Backbone.View} layout that contains one or more named slots that can be yielded to
5
+ * child views of this composite view. The layout view ensures that a view in an
6
+ * area that is yielded to another view is properly cleaned up by invoking its
7
+ * `leave()` function.
8
+ *
9
+ * @extends {composite.View}
10
+ * @constructor
11
+ */
12
+
13
+ exports.View = composite.View.extend({
14
+
15
+ /**
16
+ * @type {String} The CSS class of the DIV element that is auto-generated for this
17
+ * view, if not overridden, and no element is specified in the options.
18
+ */
19
+
20
+ className: 'booster-views-layout',
21
+
22
+ /**
23
+ * Initializes the layout view by accepting a template function in the options that
24
+ * is to be used to render the view. The template function marks the available slots
25
+ * to be yielded to child views of the layout using the `data-yield` attribute.
26
+ *
27
+ * For example:
28
+ *
29
+ * <aside data-yield="sidebar"></aside>
30
+ *
31
+ * @param {Object} options The mandatory options hash containing at least the key
32
+ * `template` which is the template function to use during
33
+ * rendering, in addition to the optional {Backbone.View} options.
34
+ */
35
+
36
+ initialize: function(options) {
37
+ this._template = options.template;
38
+ this.views = { };
39
+ this.render();
40
+ },
41
+
42
+
43
+ /**
44
+ * Renders the layout into the auto-generated or options-supplied `this.el` using
45
+ * the template function given in the initialization options. If any child views
46
+ * are already inserted into the layout, these are added to the corresponding slot.
47
+ */
48
+
49
+ render: function() {
50
+ $(this.el).html(this._template());
51
+ },
52
+
53
+ /**
54
+ * Sets one or more views in the layout, notifying any existing views in the
55
+ * corresponding areas that they are about to leave, ensuring that event bindings
56
+ * are properly cleared.
57
+ *
58
+ * @param {Object.<string, Backbone.View>} views A set of views to be inserted into the
59
+ * layout, keyed by the name of the slot that
60
+ * this particular view exposes. If a slot with
61
+ * the given name cannot be found in this.el, an
62
+ * error is thrown.
63
+ */
64
+
65
+ set: function(views) {
66
+ _.each(views, _.bind(this._insert, this));
67
+ },
68
+
69
+ /**
70
+ * Inserts the given view into the given slot inside the layout.
71
+ *
72
+ * @param {Backbone.View} view The view to insert into the layout.
73
+ * @param {string} yield The name of the slot that should receive the view.
74
+ * @private
75
+ */
76
+
77
+ _insert: function(view, yield) {
78
+ var slot = this.$('[data-yield="#{yield}"]'),
79
+ views = this.views;
80
+
81
+ if (slot.length === 0) {
82
+ throw new Error('The layout template does not contain any slot named #{yield}');
83
+ }
84
+
85
+ if (views[yield]) {
86
+ this.remove(views[yield]);
87
+ }
88
+
89
+ slot.html(view.el);
90
+ view.enter(this)
91
+ views[yield] = this.add(view);
92
+ }
93
+
94
+ });
@@ -0,0 +1,7 @@
1
+ require 'booster/version'
2
+ require 'booster/engine'
3
+
4
+ module Booster
5
+ autoload(:Handlebars, 'booster/handlebars')
6
+ autoload(:Template, 'booster/template')
7
+ end
@@ -0,0 +1,8 @@
1
+ module Booster
2
+ class Engine < Rails::Engine
3
+ initializer 'sprockets.booster', :after => 'sprockets.environment' do |app|
4
+ next unless app.assets
5
+ app.assets.register_engine('.boost', Template)
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,50 @@
1
+ # Based on https://github.com/josh/ruby-coffee-script
2
+ require 'execjs'
3
+ require 'pathname'
4
+
5
+ module Booster
6
+
7
+ # Support class for compiling Handlebars templates to JavaScript
8
+ # source code using the Handlebars.js library with ExecJS.
9
+ class Handlebars
10
+ class << self
11
+ def precompile(*args)
12
+ context.call('Handlebars.precompile', *args)
13
+ end
14
+
15
+ private
16
+
17
+ def context
18
+ @context ||= ExecJS.compile(source)
19
+ end
20
+
21
+ def source
22
+ @source ||= path.read + patched_source
23
+ end
24
+
25
+ # Renames the built-in nameLookup method to make Handlebars.js support name lookups not
26
+ # only directly on an object (object.title), but also via the `Backbone.Model#get()` which
27
+ # is the way Backbone.Model exposes data attributes for a particular model.
28
+ #
29
+ # `Hello {{name}}` will then try `context.name` and then `context.get('name')`
30
+ # if `context.name` returns `undefined`.
31
+ def patched_source
32
+ <<-PATCHED_SOURCE
33
+ Handlebars.JavaScriptCompiler.prototype.nameLookupOriginal = Handlebars.JavaScriptCompiler.prototype.nameLookup;
34
+ Handlebars.JavaScriptCompiler.prototype.nameLookup = function(parent, name) {
35
+ return "((" + parent + ".get ? " + parent + ".get('" + name + "') : undefined) || " +
36
+ this.nameLookupOriginal(parent, name) + ")";
37
+ }
38
+ PATCHED_SOURCE
39
+ end
40
+
41
+ def path
42
+ @path ||= assets_path.join('javascripts', 'handlebars.js')
43
+ end
44
+
45
+ def assets_path
46
+ @assets_path ||= Pathname(__FILE__).dirname.join('..','..','vendor','assets')
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,59 @@
1
+ require 'tilt'
2
+
3
+ module Booster
4
+
5
+ # Tilt integration which pre-processes `.booster` files by wrapping them in a
6
+ # CommonJS-like closure and converts inline templates to JavaScript functions
7
+ # using the Handlebars compiler. While it is at it, it also does string interpolation
8
+ # on regular JavaScript strings.
9
+ class Template < Tilt::Template
10
+
11
+ # Regex for capturing sections of template code to be compiled to JavaScript
12
+ TEMPLATE_SECTION = /@@\s*([A-Za-z0-9]*)\n(((?!@@).)*)/m
13
+
14
+ # Regex for capturing string interpolations
15
+ # STRING_INTERPOLATION = /('|")(.*)#\{([\s\S]+?)\}/
16
+
17
+ STRING_INTERPOLATION = /#\{([\s\S]+?)\}/
18
+
19
+ # Processing Booster files result in plain JavaScript.
20
+ def self.default_mime_type
21
+ 'application/javascript'
22
+ end
23
+
24
+ def evaluate(scope, locals, &block)
25
+ # Replace template code with compiled JavaScript
26
+ data.gsub!(TEMPLATE_SECTION) do
27
+ "var #{$1} = Handlebars.template(#{Handlebars.precompile($2.strip!)});\n\n"
28
+ end
29
+
30
+ # Convert string interpolation to string concatenation with rudimentary
31
+ # guessing of quote type ($1). Since the template functions compiled in the
32
+ # previous step are just functions it is possible to use interpolations there
33
+ # as well, even if Handlebars does not support them by default. This is not
34
+ # recommended though although it works.
35
+ data.gsub!(STRING_INTERPOLATION, '\' + (\1) + \'')
36
+
37
+ # Indent the module content if in development mode for increased readability
38
+ if (Rails.env == 'development')
39
+ data.gsub!(/^(.)/, ' \1')
40
+ end
41
+
42
+ # Wrap the whole thing in a closure and register it as a module (https://gist.github.com/1153919)
43
+ @output ||= "require.define({'#{ module_name(scope) }': function(exports, require, module) {#{data}}});\n"
44
+ end
45
+
46
+ protected
47
+
48
+ def module_name(scope)
49
+ scope.logical_path #.split('/')[1..-1].join('/')
50
+ end
51
+
52
+ def basename(path)
53
+ path.gsub(%r{.*/}, '')
54
+ end
55
+
56
+ def prepare
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,3 @@
1
+ module Booster
2
+ VERSION = '0.0.1'.freeze
3
+ end
@@ -0,0 +1,35 @@
1
+ require 'test_helper'
2
+
3
+ module Booster
4
+ class TiltTest < Test::Unit::TestCase
5
+ def test_render
6
+ scope = Object.new
7
+ class << scope
8
+ def logical_path
9
+ 'models/user'
10
+ end
11
+ end
12
+
13
+ template = Booster::Tilt.new('/myapp/app/assets/javascripts/models/user.js.booster') {
14
+ <<-FILE
15
+ exports.Index = booster.View.extend({
16
+ greeting: 'This is Booster \#{this.version}!'
17
+ });
18
+
19
+ @@ layout
20
+ <h1 data-yield="title"></h1>
21
+ <div data-yield="content"></div>
22
+
23
+ @@ index
24
+ <ul>
25
+ {{#each models}}
26
+ <li>{{name}}</li>
27
+ {{/each}}
28
+ </ul>
29
+ FILE
30
+ }
31
+
32
+ puts template.render(scope, {})
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env rake
2
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
3
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
4
+
5
+ require File.expand_path('../config/application', __FILE__)
6
+
7
+ Dummy::Application.load_tasks
@@ -0,0 +1,18 @@
1
+ //= require jquery
2
+ //= require booster
3
+ //= require_tree .
4
+
5
+ require('booster/support/schema_spec');
6
+ require('booster/support/router_spec');
7
+ require('booster/support/helpers_spec');
8
+ require('booster/support/observer_spec');
9
+ require('booster/views/layout_spec');
10
+
11
+ describe('inline templates', function() {
12
+ it('should compile inline templates to JavaScript functions', function() {
13
+ expect(greeting({message: 'Too weird?'})).toEqual('<h1>Too weird?</h1>');
14
+ });
15
+ });
16
+
17
+ @@ greeting
18
+ <h1>{{message}}</h1>
@@ -0,0 +1,69 @@
1
+ var helpers = require('booster/support/helpers');
2
+
3
+ describe('helpers', function() {
4
+ var Model = Backbone.Model.extend({
5
+ statusOptionsObject: {
6
+ 7: 'Lucky',
7
+ 42: 'Meaningful'
8
+ },
9
+ statusOptionsArray: [7, 42],
10
+ });
11
+
12
+ beforeEach(function() {
13
+ this.model = new Model({
14
+ title: 'Boyachaka',
15
+ status: 42
16
+ });
17
+ })
18
+
19
+ it('should include a form input helper', function() {
20
+ expect(input(this.model)).toEqual(
21
+ '<input class="test" data-cid="#{this.model.cid}" name="title" value="Boyachaka"></input>'
22
+ );
23
+ });
24
+
25
+ it('should include a form textarea helper', function() {
26
+ expect(textarea(this.model)).toEqual(
27
+ '<textarea class="test" data-cid="#{this.model.cid}" name="title">Boyachaka</textarea>'
28
+ );
29
+ });
30
+
31
+ it('should include a form radio button helper', function() {
32
+ expect(radio(this.model)).toEqual(
33
+ '<input class="test" name="status" value="7" type="radio"></input>\n' +
34
+ ' <input class="test" name="status" value="42" type="radio" checked="true"></input>'
35
+ );
36
+ });
37
+
38
+ it('should include a form select helper', function() {
39
+ expect(selectObject(this.model)).toEqual(
40
+ '<select class="test" data-cid="#{this.model.cid}" name="status">\n' +
41
+ '<option value="7" >Lucky</option>\n' +
42
+ '<option value="42" selected>Meaningful</option>\n' +
43
+ '</select>'
44
+ );
45
+
46
+ expect(selectArray(this.model)).toEqual(
47
+ '<select class="test" data-cid="#{this.model.cid}" name="status">\n' +
48
+ '<option value="7" >7</option>\n' +
49
+ '<option value="42" selected>42</option>\n' +
50
+ '</select>'
51
+ );
52
+ });
53
+ })
54
+
55
+ @@ input
56
+ {{input "title" class="test" data-cid=cid}}
57
+
58
+ @@ textarea
59
+ {{textarea "title" class="test" data-cid=cid}}
60
+
61
+ @@ radio
62
+ {{radio "status" 7 class="test"}}
63
+ {{radio "status" 42 class="test"}}
64
+
65
+ @@ selectObject
66
+ {{select "status" statusOptionsObject class="test" data-cid=cid}}
67
+
68
+ @@ selectArray
69
+ {{select "status" statusOptionsArray class="test" data-cid=cid}}