backbone-support 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source :rubygems
2
+
3
+ gem 'jasmine'
4
+ gem 'headless'
5
+ gem 'rake'
data/Gemfile.lock ADDED
@@ -0,0 +1,38 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ childprocess (0.2.0)
5
+ ffi (~> 1.0.6)
6
+ diff-lcs (1.1.2)
7
+ ffi (1.0.9)
8
+ headless (0.1.0)
9
+ jasmine (1.0.2.1)
10
+ json_pure (>= 1.4.3)
11
+ rack (>= 1.1)
12
+ rspec (>= 1.3.1)
13
+ selenium-webdriver (>= 0.1.3)
14
+ json_pure (1.5.3)
15
+ rack (1.3.2)
16
+ rake (0.9.2)
17
+ rspec (2.6.0)
18
+ rspec-core (~> 2.6.0)
19
+ rspec-expectations (~> 2.6.0)
20
+ rspec-mocks (~> 2.6.0)
21
+ rspec-core (2.6.4)
22
+ rspec-expectations (2.6.0)
23
+ diff-lcs (~> 1.1.2)
24
+ rspec-mocks (2.6.0)
25
+ rubyzip (0.9.4)
26
+ selenium-webdriver (2.3.2)
27
+ childprocess (>= 0.1.9)
28
+ ffi (>= 1.0.7)
29
+ json_pure
30
+ rubyzip
31
+
32
+ PLATFORMS
33
+ ruby
34
+
35
+ DEPENDENCIES
36
+ headless
37
+ jasmine
38
+ rake
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2011 thoughtbot
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,176 @@
1
+ Helper and utility classes that fill out Backbone for serious development.
2
+
3
+ Inspired by our projects and the Backbone.js on Rails book:
4
+ http://workshops.thoughtbot.com/backbone-js-on-rails
5
+
6
+ The book contains complete instructions and in-depth coverage of the internals
7
+ of CompositeView and Swappingrouter, and an example application that shows
8
+ their usage
9
+
10
+ ### SwappingRouter
11
+
12
+ A Router subclass the provides a standard way to swap one view for another.
13
+
14
+ This introduces a convention that all views have a `leave()` function,
15
+ responsible for unbinding and cleaning up the view. And the convention that
16
+ all actions underneath the same `Router` share the same root element, and
17
+ define it as `el` on the router.
18
+
19
+ Now, a `SwappingRouter` can take advantage of the `leave()` function, and
20
+ clean up any existing views before swapping to a new one. It swaps into a new
21
+ view by rendering that view into its own `el`:
22
+
23
+ swap: function(newView) {
24
+ if (this.currentView && this.currentView.leave) {
25
+ this.currentView.leave();
26
+ }
27
+
28
+ this.currentView = newView;
29
+ this.currentView.render();
30
+ $(this.el).empty().append(this.currentView.el);
31
+ }
32
+
33
+ An example SwappingRouter would look as follows:
34
+
35
+ Trajectory.Routers.Stories = Support.SwappingRouter.extend({
36
+ initialize: function(options) {
37
+ this.el = $("div.primary_content");
38
+ },
39
+ routes: {
40
+ "stories": "index",
41
+ "stories/new": "newStory"
42
+ }
43
+ index: function(story_id) {
44
+ var view = new Trajectory.Views.StoriesIndex();
45
+ this.swap(view);
46
+ },
47
+ newStory: function() {
48
+ var view = new Trajectory.Views.StoryNew({ model: new Story() });
49
+ this.swap(view);
50
+ }
51
+ }
52
+
53
+ ### CompositeView
54
+
55
+ CompositeView provides a convention for a parent view that has one or more
56
+ child views.
57
+
58
+ This introduces a convention that all views have a `leave()` function,
59
+ responsible for unbinding and cleaning up the view.
60
+
61
+ `CompositeView` provides methods for adding and removing children from the
62
+ parent view.
63
+
64
+ `CompositeView` maintains an array of its immediate children as
65
+ `this.children`. With this reference in place, a parent view's `leave()`
66
+ method can invoke `leave()` on its children, ensuring that an entire tree of
67
+ composed views is cleaned up properly.
68
+
69
+ For child views that can dismiss themselves, such as dialog boxes, children
70
+ maintain a back-reference at `this.parent`. This is used to reach up and call
71
+ `this.parent.removeChild(this)` for these self-dismissing views.
72
+
73
+ ## Dependencies
74
+
75
+ You'll need these, but chances are you already have them in your app:
76
+
77
+ * jQuery
78
+ * Underscore
79
+ * Backbone
80
+
81
+ ## Development
82
+
83
+ First:
84
+
85
+ bundle
86
+
87
+ While TDD'ing:
88
+
89
+ bundle exec rake jasmine
90
+
91
+ To not open tests a browser window:
92
+
93
+ bundle exec rake jasmine:ci
94
+
95
+ ## Installation
96
+
97
+ The recommended usage is with Rails 3.1.
98
+
99
+ ### With Rails 3.1
100
+
101
+ Add the gem to your Gemfile
102
+
103
+ gem "backbone-support"
104
+
105
+ And then `bundle install`
106
+
107
+ In your application.js, or in whatever file your backbone.js assets are
108
+ required in, add the following:
109
+
110
+ //= require backbone-support
111
+
112
+ This should be _above_ any usage of SwappingController or SwappingRouter, but
113
+ below the inclusion of Backbone.js, Underscore, and jQuery.
114
+
115
+ If you do not wish to include all of backbone-support, you can include
116
+ individual pieces. First, require the main support file:
117
+
118
+ //= require backbone-support/support
119
+
120
+ Then require the individual assets you wish to use:
121
+
122
+ //= require backbone-support/swapping_router
123
+ //= require backbone-support/composite_view
124
+
125
+ ### With Jammit
126
+
127
+ First off:
128
+
129
+ rails plugin install git@github.com:thoughtbot/backbone-support.git
130
+
131
+ In your `config/application.rb`:
132
+
133
+ ``` ruby
134
+ config.middleware.use Rack::Static,
135
+ :urls => ['/vendor/plugins/backbone-support/lib/assets/javascripts']
136
+ ```
137
+
138
+ And in your `config/assets.yml`:
139
+
140
+ ``` yaml
141
+ javascripts:
142
+ common:
143
+ - public/javascripts/vendor/underscore.js
144
+ - public/javascripts/vendor/backbone.js
145
+ - vendor/plugins/backbone-support/lib/assets/**/*.js
146
+ ```
147
+
148
+ If you have evergreen tests, you will need to change your `require` statements
149
+ since these files live in `vendor/`. Change `config/evergreen.rb` to be this:
150
+
151
+ ``` ruby
152
+ Evergreen.configure do |config|
153
+ config.public_dir = '.'
154
+ end
155
+ ```
156
+
157
+ Your individual specs will then need the full root path in `require`. For
158
+ example:
159
+
160
+
161
+ ``` js
162
+ requirePublic = function(path) {
163
+ require("/public/javascripts/" + path);
164
+ };
165
+
166
+ requirePublic("vendor/underscore.js");
167
+ requirePublic("vendor/backbone.js");
168
+
169
+ require("/vendor/plugins/backbone-support/lib/assets/javascripts/backbone-support.js");
170
+ require("/vendor/plugins/backbone-support/lib/assets/javascripts/backbone-support/composite_view.js");
171
+ require("/vendor/plugins/backbone-support/lib/assets/javascripts/backbone-support/swapping_router.js");
172
+ ```
173
+
174
+ ## License
175
+
176
+ Copyright 2011 thoughtbot. Please check LICENSE for more details.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'jasmine'
2
+ load 'jasmine/tasks/jasmine.rake'
3
+
4
+ task :default => "jasmine:ci"
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "backbone-support/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'backbone-support'
7
+ s.version = BackboneSupport::VERSION.dup
8
+ s.authors = ['Chad Pytel', 'Joe Ferris', 'Jason Morrison', 'Nick Quaranto']
9
+ s.email = ['support@thoughtbot.com']
10
+ s.homepage = 'http://github.com/thoughtbot/backbone-support'
11
+ s.summary = 'SwappingController and CompositeView for Backbone.js'
12
+ s.description = 'SwappingController and CompositeView for Backbone.js'
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.require_paths = ["lib"]
18
+
19
+ s.add_development_dependency('jasmine')
20
+ end
@@ -0,0 +1,2 @@
1
+ //= require backbone-support/support
2
+ //= require_tree ./backbone-support
@@ -0,0 +1,48 @@
1
+ Support.CompositeView = function(options) {
2
+ this.children = _([]);
3
+ Backbone.View.apply(this, [options]);
4
+ };
5
+
6
+ _.extend(Support.CompositeView.prototype, Backbone.View.prototype, {
7
+ leave: function() {
8
+ this.unbind();
9
+ this.remove();
10
+ this._leaveChildren();
11
+ this._removeFromParent();
12
+ },
13
+
14
+ renderChild: function(view) {
15
+ view.render();
16
+ this.children.push(view);
17
+ view.parent = this;
18
+ },
19
+
20
+ appendChild: function(view) {
21
+ this.renderChild(view);
22
+ $(this.el).append(view.el);
23
+ },
24
+
25
+ renderChildInto: function(view, container) {
26
+ this.renderChild(view);
27
+ $(container).empty().append(view.el);
28
+ },
29
+
30
+ _leaveChildren: function() {
31
+ this.children.chain().clone().each(function(view) {
32
+ if (view.leave)
33
+ view.leave();
34
+ });
35
+ },
36
+
37
+ _removeFromParent: function() {
38
+ if (this.parent)
39
+ this.parent._removeChild(this);
40
+ },
41
+
42
+ _removeChild: function(view) {
43
+ var index = this.children.indexOf(view);
44
+ this.children.splice(index, 1);
45
+ }
46
+ });
47
+
48
+ Support.CompositeView.extend = Backbone.View.extend;
@@ -0,0 +1,2 @@
1
+ Support = {};
2
+ Support.VERSION = "0.0.1";
@@ -0,0 +1,17 @@
1
+ Support.SwappingRouter = function(options) {
2
+ Backbone.Router.apply(this, [options]);
3
+ };
4
+
5
+ _.extend(Support.SwappingRouter.prototype, Backbone.Router.prototype, {
6
+ swap: function(newView) {
7
+ if (this.currentView && this.currentView.leave) {
8
+ this.currentView.leave();
9
+ }
10
+
11
+ this.currentView = newView;
12
+ this.currentView.render();
13
+ $(this.el).empty().append(this.currentView.el);
14
+ }
15
+ });
16
+
17
+ Support.SwappingRouter.extend = Backbone.Router.extend;
@@ -0,0 +1,7 @@
1
+ module BackboneSupport
2
+ if defined?(Rails)
3
+ class Engine < ::Rails::Engine
4
+ require 'backbone-support/engine'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module BackboneSupport
2
+ class Engine < Rails::Engine
3
+ # auto wire
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module BackboneSupport
2
+ VERSION = '0.0.1'.freeze
3
+ end
@@ -0,0 +1,133 @@
1
+ describe("Support.CompositeView", function() {
2
+ var orangeView = Support.CompositeView.extend({
3
+ render: function() {
4
+ var text = this.make("span", {}, "Orange!");
5
+ $(this.el).append(text);
6
+ }
7
+ });
8
+
9
+ var blankView = Support.CompositeView.extend({
10
+ render: function() {
11
+ }
12
+ });
13
+
14
+ var normalView = Backbone.View.extend({
15
+ render: function() {
16
+ var text = this.make("span", {}, "Normal!");
17
+ $(this.el).append(text);
18
+ }
19
+ });
20
+
21
+ beforeEach(function() {
22
+ Helpers.setup();
23
+ Helpers.append("test1");
24
+ Helpers.append("test2");
25
+ });
26
+
27
+ afterEach(function() {
28
+ Helpers.teardown();
29
+ });
30
+
31
+ describe("#renderChild", function() {
32
+ it("renders children views", function() {
33
+ var view = new blankView();
34
+ view.renderChild(new orangeView({el: "#test1"}));
35
+ view.renderChild(new orangeView({el: "#test2"}));
36
+
37
+ expect($("#test1").text()).toEqual("Orange!");
38
+ expect($("#test2").text()).toEqual("Orange!");
39
+ });
40
+ });
41
+
42
+ describe("#appendChild", function() {
43
+ it("renders and appends children views", function() {
44
+ var view = new blankView({el: "#test"});
45
+ view.appendChild(new orangeView());
46
+ view.appendChild(new orangeView());
47
+
48
+ expect($("#test").text()).toEqual("Orange!Orange!");
49
+ });
50
+ });
51
+
52
+ describe("#renderChildInto", function() {
53
+ it("renders child into the given element and replaces content there", function() {
54
+ $("#test1").text("Replace this!");
55
+
56
+ var view = new blankView({el: "#test"});
57
+ view.renderChildInto(new orangeView(), "#test1");
58
+
59
+ expect($("#test").text()).toEqual("");
60
+ expect($("#test1").text()).toEqual("Orange!");
61
+ });
62
+ });
63
+
64
+ describe("#leave", function() {
65
+ it("removes elements and events when leave() is called", function() {
66
+ var view = new orangeView();
67
+ var spy = sinon.spy(view, "unbind");
68
+
69
+ runs(function() {
70
+ view.render();
71
+ $("#test").append(view.el);
72
+ });
73
+
74
+ Helpers.sleep();
75
+
76
+ runs(function() {
77
+ expect($("#test").text()).toEqual("Orange!");
78
+ });
79
+
80
+ Helpers.sleep();
81
+
82
+ runs(function() {
83
+ view.leave();
84
+ });
85
+
86
+ Helpers.sleep();
87
+
88
+ runs(function() {
89
+ expect($("#test").text()).toEqual("");
90
+ expect(spy.called).toBeTruthy();
91
+ });
92
+ });
93
+
94
+ it("removes children views on leave", function() {
95
+ var view = new blankView();
96
+ view.renderChild(new orangeView({el: "#test1"}));
97
+ view.renderChild(new orangeView({el: "#test2"}));
98
+
99
+ view.leave();
100
+
101
+ expect($("#test1").size()).toEqual(0);
102
+ expect($("#test2").size()).toEqual(0);
103
+ });
104
+
105
+ it("doesn't fail on normal backbone views that may be children", function() {
106
+ var view = new blankView();
107
+ view.renderChild(new orangeView({el: "#test1"}));
108
+ view.renderChild(new normalView({el: "#test2"}));
109
+
110
+ view.leave();
111
+
112
+ expect($("#test1").size()).toEqual(0);
113
+ expect($("#test2").size()).toEqual(1);
114
+ });
115
+
116
+ it("removes self from parent if invoked on a child view", function() {
117
+ var view = new blankView();
118
+ var childView = new orangeView({el: "#test1"});
119
+ view.renderChild(childView)
120
+ view.renderChild(new orangeView({el: "#test2"}));
121
+
122
+ expect($("#test1").size()).toEqual(1);
123
+ expect($("#test2").size()).toEqual(1);
124
+ expect(view.children.size()).toEqual(2);
125
+
126
+ childView.leave();
127
+
128
+ expect($("#test1").size()).toEqual(0);
129
+ expect($("#test2").size()).toEqual(1);
130
+ expect(view.children.size()).toEqual(1);
131
+ });
132
+ });
133
+ });