booster 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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}}