modularity-rails 0.6.0 → 0.6.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.
data/.gitignore CHANGED
@@ -1,13 +1,15 @@
1
- *.gem
2
- *.rbc
3
1
  .bundle
4
2
  .config
3
+ .DS_Store
5
4
  .yardoc
6
- Gemfile.lock
7
- InstalledFiles
8
- _yardoc
5
+ *.gem
6
+ *.rbc
7
+ *.swp
8
+ *.swo
9
9
  coverage
10
10
  doc/
11
+ Gemfile.lock
12
+ InstalledFiles
11
13
  lib/bundler/man
12
14
  pkg
13
15
  rdoc
@@ -15,3 +17,4 @@ spec/reports
15
17
  test/tmp
16
18
  test/version_tmp
17
19
  tmp
20
+ _yardoc
data/Guardfile ADDED
@@ -0,0 +1,9 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'livereload' do
5
+ watch(%r{spec/javascripts/.+})
6
+ watch(%r{vendor/assets/javascripts/.+})
7
+ # Rails Assets Pipeline
8
+ # watch(%r{(app|vendor)/assets/\w+/(.+\.(css|js|html)).*}) { |m| "/assets/#{m[2]}" }
9
+ end
data/README.md CHANGED
@@ -3,8 +3,12 @@
3
3
  Makes the [Modularity CoffeeScript](http://github.com/kevgo/modularity-coffeescript) library available to
4
4
  Rails 3.1 applications.
5
5
 
6
+ Modularity is a pattern and framework for lightweight object-oriented JavaScript
7
+ that allows to compose functionally rich web pages in a clean and testable way
8
+ out of well structured and reusable components.
6
9
 
7
- ## Installation
10
+
11
+ # Installation
8
12
 
9
13
  Add this line to your application's Gemfile:
10
14
 
@@ -19,25 +23,39 @@ $ bundle
19
23
  ```
20
24
 
21
25
  Finally, you have to load the modularity file into your application's javascript.
22
- The easiest way is to add it to `application.js`:
26
+ The easiest way is to add it to `application.coffee`:
23
27
 
24
- ```javascript
25
- /**
26
- *= require jquery
27
- *= require modularity
28
- */
28
+ ```coffeescript
29
+ # require jquery
30
+ # require modularity
29
31
  ```
30
32
 
31
33
 
32
- ## Usage
34
+ # Usage
35
+
36
+ Modularity is a lightweight framework for building powerful AJAX applications.
37
+ Modularity avoids magic and heavyness. It focusses on providing a pragmatic and interoperable foundation
38
+ for clean hand-written code bases.
39
+ Modularity provides practices to create code bases of incredible complexity that are still
40
+ nicely manageable and perform very well.
41
+
42
+
43
+ # Modules
44
+
45
+ Modules are native CoffeeScript classes that are specialized for doing what most JavaScript running in browsers does:
46
+ managing a UI consisting of DOM elements, reacting to events that happen within that section,
47
+ representing application logic specific to that section, and providing high-level APIs for others to interact with the section.
48
+
49
+ Each module has a container. The container is the outermost DOM element of a section.
50
+ Everything the module does must happen inside this container.
51
+ The module is responsible for managing the inner DOM-structure of the container.
33
52
 
34
- See [http://github.com/kevgo/modularity-coffeescript].
35
53
 
36
54
  ## Mixins
37
55
 
38
56
  Similar to Ruby mixins, mixins in Modularity allow to include orthogonal functional aspects defined in separate objects into a class.
39
- ```coffeescript
40
57
 
58
+ ```coffeescript
41
59
  myMixin =
42
60
 
43
61
  # This will be called when an instance of a class that includes this mixin is created.
@@ -57,10 +75,28 @@ class MyModule extends Module
57
75
  super
58
76
 
59
77
  # ...
60
-
61
78
  ```
62
79
 
63
80
 
81
+ ## Reusable example modules and mixins.
82
+
83
+ Modularity comes bundled with a bunch of example modules and mixins that can be used in production code.
84
+ The example modules are located in `vendor/assets/javascripts/modules/` and _vendor/assets/javascripts/mixins_
85
+ and must be explicitly required in your Rails files using the `require` commands of the asset pipeline.
86
+
87
+
88
+ ### Modules
89
+
90
+ * __button.coffee__: A simple button. Fires the `clicked` event when anything inside the container is clicked. Uses the `clickable` mixin.
91
+ * __counter_button.coffee__: Similar to button, but includes the click count as data payload in the fired event.
92
+
93
+
94
+ ### Mixins
95
+
96
+ * __clickable.coffee__: Including this mixins adds a 'clickable' aspect to your module, i.e. turns it into a button. Clicking anywhere inside the container makes it fire the 'clicked' event.
97
+ * __closable.coffee__: Including this mixin makes a module closable. This means that when the user clicks on an embedded DOM element with the class 'CloseButton', a 'closing' event will be fired, and the whole module will be removed from the DOM.
98
+
99
+
64
100
  # Development
65
101
 
66
102
  ## Contributing
@@ -77,3 +113,16 @@ class MyModule extends Module
77
113
  ```bash
78
114
  $ evergreen run
79
115
  ```
116
+
117
+
118
+ ## Automatically refreshing the browser during development.
119
+
120
+ Modularity-Rails comes with support for [LifeReload](https://github.com/mockko/livereload) via [Guard](https://github.com/guard/guard).
121
+
122
+ * Install the LiveReload browser extension: [Chrome](https://chrome.google.com/webstore/detail/jnihajbhpnppcggbcgedagnkighmdlei)
123
+ * Run the evergreen server: ```$ evergreen run```
124
+ * Run the guard server: ``` $ bundle exec guard ```
125
+ * Start the LiveReload plugin in Chrome (button in address bar).
126
+ * Navigate to the test page that you want to observe.
127
+ * Change and save code and see the browser reload.
128
+
@@ -1,3 +1,3 @@
1
1
  module ModularityRails
2
- VERSION = "0.6.0"
2
+ VERSION = "0.6.1"
3
3
  end
@@ -13,6 +13,8 @@ Gem::Specification.new do |s|
13
13
  s.add_dependency "rails", ">= 3.1.0"
14
14
  s.add_development_dependency "capybara-webkit"
15
15
  s.add_development_dependency "evergreen"
16
+ s.add_development_dependency "rb-fsevent" if RUBY_PLATFORM =~ /darwin/i
17
+ s.add_development_dependency "guard-livereload"
16
18
 
17
19
  s.files = `git ls-files`.split("\n")
18
20
  s.executables = `git ls-files`.split("\n").select{|f| f =~ /^bin/}
@@ -0,0 +1,288 @@
1
+ var readFixtures = function() {
2
+ return jasmine.getFixtures().proxyCallTo_('read', arguments);
3
+ };
4
+
5
+ var preloadFixtures = function() {
6
+ jasmine.getFixtures().proxyCallTo_('preload', arguments);
7
+ };
8
+
9
+ var loadFixtures = function() {
10
+ jasmine.getFixtures().proxyCallTo_('load', arguments);
11
+ };
12
+
13
+ var setFixtures = function(html) {
14
+ jasmine.getFixtures().set(html);
15
+ };
16
+
17
+ var sandbox = function(attributes) {
18
+ return jasmine.getFixtures().sandbox(attributes);
19
+ };
20
+
21
+ var spyOnEvent = function(selector, eventName) {
22
+ jasmine.JQuery.events.spyOn(selector, eventName);
23
+ }
24
+
25
+ jasmine.getFixtures = function() {
26
+ return jasmine.currentFixtures_ = jasmine.currentFixtures_ || new jasmine.Fixtures();
27
+ };
28
+
29
+ jasmine.Fixtures = function() {
30
+ this.containerId = 'jasmine-fixtures';
31
+ this.fixturesCache_ = {};
32
+ this.fixturesPath = 'spec/javascripts/fixtures';
33
+ };
34
+
35
+ jasmine.Fixtures.prototype.set = function(html) {
36
+ this.cleanUp();
37
+ this.createContainer_(html);
38
+ };
39
+
40
+ jasmine.Fixtures.prototype.preload = function() {
41
+ this.read.apply(this, arguments);
42
+ };
43
+
44
+ jasmine.Fixtures.prototype.load = function() {
45
+ this.cleanUp();
46
+ this.createContainer_(this.read.apply(this, arguments));
47
+ };
48
+
49
+ jasmine.Fixtures.prototype.read = function() {
50
+ var htmlChunks = [];
51
+
52
+ var fixtureUrls = arguments;
53
+ for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) {
54
+ htmlChunks.push(this.getFixtureHtml_(fixtureUrls[urlIndex]));
55
+ }
56
+
57
+ return htmlChunks.join('');
58
+ };
59
+
60
+ jasmine.Fixtures.prototype.clearCache = function() {
61
+ this.fixturesCache_ = {};
62
+ };
63
+
64
+ jasmine.Fixtures.prototype.cleanUp = function() {
65
+ jQuery('#' + this.containerId).remove();
66
+ };
67
+
68
+ jasmine.Fixtures.prototype.sandbox = function(attributes) {
69
+ var attributesToSet = attributes || {};
70
+ return jQuery('<div id="sandbox" />').attr(attributesToSet);
71
+ };
72
+
73
+ jasmine.Fixtures.prototype.createContainer_ = function(html) {
74
+ var container;
75
+ if(html instanceof jQuery) {
76
+ container = jQuery('<div id="' + this.containerId + '" />');
77
+ container.html(html);
78
+ } else {
79
+ container = '<div id="' + this.containerId + '">' + html + '</div>'
80
+ }
81
+ jQuery('body').append(container);
82
+ };
83
+
84
+ jasmine.Fixtures.prototype.getFixtureHtml_ = function(url) {
85
+ if (typeof this.fixturesCache_[url] == 'undefined') {
86
+ this.loadFixtureIntoCache_(url);
87
+ }
88
+ return this.fixturesCache_[url];
89
+ };
90
+
91
+ jasmine.Fixtures.prototype.loadFixtureIntoCache_ = function(relativeUrl) {
92
+ var self = this;
93
+ var url = this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl;
94
+ jQuery.ajax({
95
+ async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded
96
+ cache: false,
97
+ dataType: 'html',
98
+ url: url,
99
+ success: function(data) {
100
+ self.fixturesCache_[relativeUrl] = data;
101
+ },
102
+ error: function(jqXHR, status, errorThrown) {
103
+ throw Error('Fixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + errorThrown.message + ')');
104
+ }
105
+ });
106
+ };
107
+
108
+ jasmine.Fixtures.prototype.proxyCallTo_ = function(methodName, passedArguments) {
109
+ return this[methodName].apply(this, passedArguments);
110
+ };
111
+
112
+
113
+ jasmine.JQuery = function() {};
114
+
115
+ jasmine.JQuery.browserTagCaseIndependentHtml = function(html) {
116
+ return jQuery('<div/>').append(html).html();
117
+ };
118
+
119
+ jasmine.JQuery.elementToString = function(element) {
120
+ return jQuery('<div />').append(element.clone()).html();
121
+ };
122
+
123
+ jasmine.JQuery.matchersClass = {};
124
+
125
+ (function(namespace) {
126
+ var data = {
127
+ spiedEvents: {},
128
+ handlers: []
129
+ };
130
+
131
+ namespace.events = {
132
+ spyOn: function(selector, eventName) {
133
+ var handler = function(e) {
134
+ data.spiedEvents[[selector, eventName]] = e;
135
+ };
136
+ jQuery(selector).bind(eventName, handler);
137
+ data.handlers.push(handler);
138
+ },
139
+
140
+ wasTriggered: function(selector, eventName) {
141
+ return !!(data.spiedEvents[[selector, eventName]]);
142
+ },
143
+
144
+ cleanUp: function() {
145
+ data.spiedEvents = {};
146
+ data.handlers = [];
147
+ }
148
+ }
149
+ })(jasmine.JQuery);
150
+
151
+ (function(){
152
+ var jQueryMatchers = {
153
+ toHaveClass: function(className) {
154
+ return this.actual.hasClass(className);
155
+ },
156
+
157
+ toBeVisible: function() {
158
+ return this.actual.is(':visible');
159
+ },
160
+
161
+ toBeHidden: function() {
162
+ return this.actual.is(':hidden');
163
+ },
164
+
165
+ toBeSelected: function() {
166
+ return this.actual.is(':selected');
167
+ },
168
+
169
+ toBeChecked: function() {
170
+ return this.actual.is(':checked');
171
+ },
172
+
173
+ toBeEmpty: function() {
174
+ return this.actual.is(':empty');
175
+ },
176
+
177
+ toExist: function() {
178
+ return this.actual.size() > 0;
179
+ },
180
+
181
+ toHaveAttr: function(attributeName, expectedAttributeValue) {
182
+ return hasProperty(this.actual.attr(attributeName), expectedAttributeValue);
183
+ },
184
+
185
+ toHaveId: function(id) {
186
+ return this.actual.attr('id') == id;
187
+ },
188
+
189
+ toHaveHtml: function(html) {
190
+ return this.actual.html() == jasmine.JQuery.browserTagCaseIndependentHtml(html);
191
+ },
192
+
193
+ toHaveText: function(text) {
194
+ if (text && jQuery.isFunction(text.test)) {
195
+ return text.test(this.actual.text());
196
+ } else {
197
+ return this.actual.text() == text;
198
+ }
199
+ },
200
+
201
+ toHaveValue: function(value) {
202
+ return this.actual.val() == value;
203
+ },
204
+
205
+ toHaveData: function(key, expectedValue) {
206
+ return hasProperty(this.actual.data(key), expectedValue);
207
+ },
208
+
209
+ toBe: function(selector) {
210
+ return this.actual.is(selector);
211
+ },
212
+
213
+ toContain: function(selector) {
214
+ return this.actual.find(selector).size() > 0;
215
+ },
216
+
217
+ toBeDisabled: function(selector){
218
+ return this.actual.is(':disabled');
219
+ },
220
+
221
+ // tests the existence of a specific event binding
222
+ toHandle: function(eventName) {
223
+ var events = this.actual.data("events");
224
+ return events && events[eventName].length > 0;
225
+ },
226
+
227
+ // tests the existence of a specific event binding + handler
228
+ toHandleWith: function(eventName, eventHandler) {
229
+ var stack = this.actual.data("events")[eventName];
230
+ var i;
231
+ for (i = 0; i < stack.length; i++) {
232
+ if (stack[i].handler == eventHandler) {
233
+ return true;
234
+ }
235
+ }
236
+ return false;
237
+ }
238
+ };
239
+
240
+ var hasProperty = function(actualValue, expectedValue) {
241
+ if (expectedValue === undefined) {
242
+ return actualValue !== undefined;
243
+ }
244
+ return actualValue == expectedValue;
245
+ };
246
+
247
+ var bindMatcher = function(methodName) {
248
+ var builtInMatcher = jasmine.Matchers.prototype[methodName];
249
+
250
+ jasmine.JQuery.matchersClass[methodName] = function() {
251
+ if (this.actual instanceof jQuery) {
252
+ var result = jQueryMatchers[methodName].apply(this, arguments);
253
+ this.actual = jasmine.JQuery.elementToString(this.actual);
254
+ return result;
255
+ }
256
+
257
+ if (builtInMatcher) {
258
+ return builtInMatcher.apply(this, arguments);
259
+ }
260
+
261
+ return false;
262
+ };
263
+ };
264
+
265
+ for(var methodName in jQueryMatchers) {
266
+ bindMatcher(methodName);
267
+ }
268
+ })();
269
+
270
+ beforeEach(function() {
271
+ this.addMatchers(jasmine.JQuery.matchersClass);
272
+ this.addMatchers({
273
+ toHaveBeenTriggeredOn: function(selector) {
274
+ this.message = function() {
275
+ return [
276
+ "Expected event " + this.actual + " to have been triggered on" + selector,
277
+ "Expected event " + this.actual + " not to have been triggered on" + selector
278
+ ];
279
+ };
280
+ return jasmine.JQuery.events.wasTriggered(selector, this.actual);
281
+ }
282
+ })
283
+ });
284
+
285
+ afterEach(function() {
286
+ jasmine.getFixtures().cleanUp();
287
+ jasmine.JQuery.events.cleanUp();
288
+ });
@@ -0,0 +1,41 @@
1
+ #= require spec_helper
2
+
3
+ describe 'test environment setup', ->
4
+
5
+ it 'loading libraries', ->
6
+ load_modularity()
7
+ loadCS "/vendor/assets/javascripts/mixins/closable.coffee"
8
+
9
+ class window.ClosableModuleStandard extends Module
10
+ @mixin closable
11
+
12
+
13
+ describe 'Closable', ->
14
+ template 'closable.html'
15
+
16
+ beforeEach ->
17
+
18
+ describe 'embedded close button', ->
19
+ it 'finds the close button div automatically', ->
20
+ new ClosableModuleStandard('#test #closable1')
21
+ $('#test #closable1 .CloseButton').click()
22
+
23
+ expect($('#test #closable1').length).toEqual(0)
24
+
25
+
26
+ describe 'when clicking the close button', ->
27
+ it 'removes the container', ->
28
+ new ClosableModuleStandard('#test #closable1')
29
+ $('#test #closable1 .CloseButton').click()
30
+
31
+ expect($('#test #closable1').length).toEqual(0)
32
+
33
+
34
+ it "fires the 'closed' event", ->
35
+ closable_module = new ClosableModuleStandard('#test #closable1')
36
+ closable_module.bind_event('closed', (spy = jasmine.createSpy()))
37
+
38
+ $('#test #closable1 .CloseButton').click()
39
+
40
+ expect(spy).toHaveBeenCalled()
41
+ expect(spy.callCount).toEqual(1)
@@ -1,21 +1,12 @@
1
- require('/spec/javascripts/external/jquery.min.js')
2
- require('/spec/javascripts/external/coffee-script.js')
3
-
4
- # Helper method to circumvent that Evergreen doesn't load CoffeeScript files.
5
- loadCS = (url, callback) ->
6
- $.ajax
7
- url: url
8
- async: false
9
- success: (data) ->
10
- eval CoffeeScript.compile data
11
- callback() if callback
12
-
13
- # Helper method to load the modularity library before the tests.
14
- describe 'modularity loader', ->
15
- it 'loading Modularity library ...', ->
16
- loadCS '/vendor/assets/javascripts/modularity.js.coffee?'+(new Date()).getTime()
17
-
18
- # Test class.
1
+ #= require spec_helper
2
+
3
+ describe 'setting up test environment', ->
4
+
5
+ it 'loading libraries', ->
6
+ load_modularity()
7
+
8
+ it 'defining test classes', ->
9
+
19
10
  class window.TestModule extends Module
20
11
  constructor: (container) ->
21
12
  super
@@ -38,7 +29,7 @@ describe 'modularity', ->
38
29
  expect(alert).toHaveBeenCalled()
39
30
 
40
31
  it 'tries to load the DOM if the given container is a string', ->
41
- new TestModule('#module_container')
32
+ new TestModule('#test #module_container')
42
33
  expect(alert).not.toHaveBeenCalled()
43
34
 
44
35
  it 'shows an error if the container is not a jQuery object', ->
@@ -46,11 +37,11 @@ describe 'modularity', ->
46
37
  expect(alert).toHaveBeenCalled()
47
38
 
48
39
  it 'shows an error if the container is an empty jQuery object', ->
49
- new TestModule($('.zonk'))
40
+ new TestModule($('#test .zonk'))
50
41
  expect(alert).toHaveBeenCalled()
51
42
 
52
43
  it 'shows an error if the container has more than one elements', ->
53
- new TestModule($('.double'))
44
+ new TestModule($('#test .double'))
54
45
  expect(alert).toHaveBeenCalled()
55
46
 
56
47
  it "allows to provide 'testing' in tests", ->
@@ -100,11 +91,11 @@ describe 'modularity', ->
100
91
  spyOn window, 'alert'
101
92
 
102
93
  it 'works', ->
103
- $('#module_container').module(TestModule)
94
+ $('#test #module_container').module(TestModule)
104
95
  expect(alert).not.toHaveBeenCalled()
105
96
 
106
97
  it 'returns the created instance', ->
107
- result = $('#module_container').module(Module)
98
+ result = $('#test #module_container').module(Module)
108
99
  expect(result).toBeDefined()
109
100
  expect(typeof result).toEqual('object')
110
101
 
@@ -113,7 +104,7 @@ describe 'modularity', ->
113
104
  expect(alert).toHaveBeenCalled()
114
105
 
115
106
  it 'returns an error if the jQuery object is empty.', ->
116
- $('#zonk').module(Module)
107
+ $('#test #zonk').module(Module)
117
108
  expect(alert).toHaveBeenCalled()
118
109
 
119
110
 
@@ -124,7 +115,7 @@ describe 'modularity', ->
124
115
  mockContainer = null
125
116
 
126
117
  beforeEach ->
127
- mockContainer = $('#module_container')
118
+ mockContainer = $('#test #module_container')
128
119
  module = new TestModule(mockContainer)
129
120
  spyOn window, 'alert'
130
121
 
@@ -203,7 +194,7 @@ describe 'modularity', ->
203
194
  mockGlobalContainer = null
204
195
 
205
196
  beforeEach ->
206
- mockGlobalContainer = $('#module_container')
197
+ mockGlobalContainer = $('#test #module_container')
207
198
  spyOn(Module, 'global_event_container').andReturn(mockGlobalContainer)
208
199
 
209
200
  describe 'bind_global_event', ->
@@ -0,0 +1,45 @@
1
+ #= require spec_helper
2
+
3
+ describe 'test environment setup', ->
4
+
5
+ it 'loading libraries', ->
6
+ load_modularity()
7
+ loadCS "/vendor/assets/javascripts/mixins/clickable.coffee"
8
+ loadCS "/vendor/assets/javascripts/modules/button.coffee"
9
+
10
+
11
+ describe 'Button', ->
12
+ template 'button.html'
13
+
14
+ describe 'manual clicks', ->
15
+
16
+ it 'fires when clicking on the container directly', ->
17
+ button = new window.Button($('#test #button1'))
18
+ button.bind_event('clicked', (spy = jasmine.createSpy()))
19
+
20
+ button.container.click()
21
+
22
+ expect(spy).toHaveBeenCalled()
23
+ expect(spy.callCount).toEqual(1)
24
+
25
+
26
+ it 'fires when clicking embedded elements of the button', ->
27
+ button = new window.Button($('#test #button2'))
28
+ button.bind_event('clicked', (spy = jasmine.createSpy()))
29
+
30
+ button.container.find('.embedded').click()
31
+
32
+ expect(spy).toHaveBeenCalled()
33
+ expect(spy.callCount).toEqual(1)
34
+
35
+
36
+ describe 'programmatic clicks', ->
37
+
38
+ it 'programmatically clicks the button', ->
39
+ button = new window.Button($('#test #button2'))
40
+ spy = jasmine.createSpy()
41
+ button.bind_event('clicked', spy)
42
+
43
+ button.click()
44
+
45
+ expect(spy).toHaveBeenCalled()
@@ -0,0 +1,64 @@
1
+ #= require spec_helper
2
+
3
+ describe 'test environment setup', ->
4
+
5
+ it 'loading libraries', ->
6
+ load_modularity()
7
+ loadCS "/vendor/assets/javascripts/mixins/clickable.coffee"
8
+ loadCS "/vendor/assets/javascripts/modules/button.coffee"
9
+ loadCS "/vendor/assets/javascripts/modules/counter_button.coffee"
10
+
11
+
12
+ describe 'CounterButton', ->
13
+ template 'button.html'
14
+
15
+ describe 'standard button functionality', ->
16
+
17
+ describe 'manual clicks', ->
18
+
19
+ it 'fires when clicking on the container directly', ->
20
+ button = new window.CounterButton('#test #button1')
21
+ button.bind_event('clicked', (spy = jasmine.createSpy()))
22
+
23
+ button.container.click()
24
+
25
+ expect(spy).toHaveBeenCalled()
26
+ expect(spy.callCount).toEqual(1)
27
+
28
+
29
+ it 'fires when clicking embedded elements of the button', ->
30
+ button = new window.CounterButton('#test #button2')
31
+ button.bind_event('clicked', (spy = jasmine.createSpy()))
32
+
33
+ button.container.find('.embedded').click()
34
+
35
+ expect(spy).toHaveBeenCalled()
36
+ expect(spy.callCount).toEqual(1)
37
+
38
+ describe 'programmatic clicks', ->
39
+
40
+ it 'programmatically clicks the button', ->
41
+ button = new window.CounterButton('#test #button2')
42
+ spy = jasmine.createSpy()
43
+ button.bind_event('clicked', spy)
44
+
45
+ button.click()
46
+
47
+ expect(spy).toHaveBeenCalled()
48
+
49
+ describe 'CounterButton specific functionality', ->
50
+
51
+ it 'provides the click counter as the event payload', ->
52
+ button = new window.CounterButton('#test #button1')
53
+ button.bind_event('clicked', (spy = jasmine.createSpy()))
54
+
55
+ button.container.click()
56
+ button.container.click()
57
+ button.container.click()
58
+
59
+ expect(spy.callCount).toEqual(3)
60
+ expect(spy.argsForCall[0][1]).toEqual(1)
61
+ expect(spy.argsForCall[1][1]).toEqual(2)
62
+ expect(spy.argsForCall[2][1]).toEqual(3)
63
+
64
+
@@ -0,0 +1,22 @@
1
+ require('/spec/javascripts/external/jquery.min.js')
2
+ require('/spec/javascripts/external/jasmine-jquery.js')
3
+ require('/spec/javascripts/external/coffee-script.js')
4
+
5
+ # Helper method to circumvent that Evergreen doesn't load CoffeeScript files.
6
+ window.loadCS = (url, callback) ->
7
+ $.ajax
8
+ url: url
9
+ async: false
10
+ success: (data) ->
11
+ eval CoffeeScript.compile data
12
+ callback() if callback
13
+
14
+
15
+ window.load_modularity = ->
16
+ loadCS "/vendor/assets/javascripts/modularity.js.coffee?#{(new Date()).getTime()}"
17
+
18
+
19
+ beforeEach ->
20
+ @addMatchers
21
+ toHaveLength: (expected) ->
22
+ this.length == expected
@@ -0,0 +1,3 @@
1
+ <div id="button1">A normal button.</div>
2
+ <div id="button2">A button with <span class="embedded"></span> elements.</div>
3
+
@@ -0,0 +1,10 @@
1
+ <div id="closable1">
2
+ A closable section.
3
+ <div class="CloseButton"></div>
4
+ </div>
5
+
6
+ <div id="closable2">
7
+ A closable section with a custom close button.
8
+ <div class="CustomCloseButton"></div>
9
+ </div>
10
+
@@ -0,0 +1,21 @@
1
+ # This mixin adds a 'clickable' aspect to modules.
2
+ # This means clicking anywhere on the module fires the 'clicked' event.
3
+ window.clickable =
4
+
5
+ constructor: ->
6
+ @container.click @container_clicked
7
+
8
+
9
+ # Events that are fired by this mixin.
10
+ events:
11
+ clicked: 'clicked'
12
+
13
+
14
+ # Programmatically click this clickable element.
15
+ # For testing and scripting.
16
+ click: -> @container.click()
17
+
18
+
19
+ # Event handler for clicks on this clickable element.
20
+ container_clicked: -> @fire_event clickable.events.clicked
21
+
@@ -0,0 +1,18 @@
1
+ window.closable =
2
+
3
+ constructor: (container, close_button = undefined) ->
4
+ console.log close_button
5
+ close_button ||= @container.find('.CloseButton')
6
+ # TODO: add alert if no close button found.
7
+ close_button.click @close_button_clicked
8
+
9
+
10
+ # The events that can be fired by closable objects.
11
+ events:
12
+ closed: 'closed'
13
+
14
+
15
+ # Called when the button got clicked by the user or programmatically.
16
+ close_button_clicked: ->
17
+ @fire_event 'closed'
18
+ @container.remove()
@@ -19,14 +19,15 @@ class window.Module
19
19
 
20
20
 
21
21
  if @mixins?
22
- for mixin in @mixins
22
+ for mixin_data in @mixins
23
23
 
24
24
  # Attach all properties from mixin to the prototype.
25
- for methodName, method of mixin
26
- @[methodName] = method
25
+ for methodName, method of mixin_data.mixin
26
+ unless @[methodName]
27
+ @[methodName] = => method.call(@, mixin_data.params)
27
28
 
28
29
  # Call constructor function from mixin.
29
- mixin.constructor.apply @, arguments
30
+ mixin_data.mixin.constructor.apply @, arguments
30
31
 
31
32
 
32
33
  # Checks whether the given condition is true.
@@ -53,9 +54,10 @@ class window.Module
53
54
 
54
55
  # mixin = constructor of Draggable
55
56
  # self = Card
56
- @mixin: (mixin) ->
57
- @prototype.mixins = [] unless @prototype.mixins?
58
- @prototype.mixins.push mixin
57
+ @mixin: (mixin, p...) ->
58
+ @prototype.mixins or= []
59
+ console.log p
60
+ @prototype.mixins.push({mixin: mixin, params: p})
59
61
 
60
62
 
61
63
  # GLOBAL EVENTS.
@@ -0,0 +1,6 @@
1
+ # A button module.
2
+ # Responds to clicks on the container element.
3
+ # The container element is expected to already be populated.
4
+ class window.Button extends Module
5
+ @mixin clickable
6
+
@@ -0,0 +1,15 @@
1
+ # A button that counts how often it is clicked.
2
+ # This is implemented as a subclass of Button,
3
+ # to take advantage of the already existing functionality there.
4
+ class window.CounterButton extends Button
5
+
6
+ constructor: ->
7
+ super
8
+
9
+ # Counts how often this button has been clicked so far.
10
+ @click_count = 0
11
+
12
+
13
+ # We override the event handler for the 'clicked' event here.
14
+ container_clicked: =>
15
+ @fire_event 'clicked', ++@click_count
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: modularity-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.6.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-24 00:00:00.000000000 Z
12
+ date: 2012-04-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
16
- requirement: &70150578537300 !ruby/object:Gem::Requirement
16
+ requirement: &70322603498400 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 3.1.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70150578537300
24
+ version_requirements: *70322603498400
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: capybara-webkit
27
- requirement: &70150578536860 !ruby/object:Gem::Requirement
27
+ requirement: &70322603497980 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70150578536860
35
+ version_requirements: *70322603497980
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: evergreen
38
- requirement: &70150578536380 !ruby/object:Gem::Requirement
38
+ requirement: &70322603497520 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,7 +43,29 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70150578536380
46
+ version_requirements: *70322603497520
47
+ - !ruby/object:Gem::Dependency
48
+ name: rb-fsevent
49
+ requirement: &70322603497060 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70322603497060
58
+ - !ruby/object:Gem::Dependency
59
+ name: guard-livereload
60
+ requirement: &70322603496640 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70322603496640
47
69
  description: Description of ModularityRails.
48
70
  email:
49
71
  - kevin.goslar@gmail.com
@@ -55,6 +77,7 @@ files:
55
77
  - .gitignore
56
78
  - .travis.yml
57
79
  - Gemfile
80
+ - Guardfile
58
81
  - LICENSE
59
82
  - README.md
60
83
  - Rakefile
@@ -63,10 +86,21 @@ files:
63
86
  - lib/modularity-rails/version.rb
64
87
  - modularity-rails.gemspec
65
88
  - spec/javascripts/external/coffee-script.js
89
+ - spec/javascripts/external/jasmine-jquery.js
66
90
  - spec/javascripts/external/jquery.min.js
91
+ - spec/javascripts/mixins/closable_spec.coffee
67
92
  - spec/javascripts/modularity_spec.coffee
93
+ - spec/javascripts/modules/button_spec.coffee
94
+ - spec/javascripts/modules/counter_button_spec.coffee
95
+ - spec/javascripts/spec_helper.coffee
96
+ - spec/javascripts/templates/button.html
97
+ - spec/javascripts/templates/closable.html
68
98
  - spec/javascripts/templates/test.html
99
+ - vendor/assets/javascripts/mixins/clickable.coffee
100
+ - vendor/assets/javascripts/mixins/closable.coffee
69
101
  - vendor/assets/javascripts/modularity.js.coffee
102
+ - vendor/assets/javascripts/modules/button.coffee
103
+ - vendor/assets/javascripts/modules/counter_button.coffee
70
104
  homepage: http://github.com/kevgo/modularity-rails
71
105
  licenses: []
72
106
  post_install_message: