page_specific_js 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .bundle
2
+ Gemfile.lock
3
+ /pkg/*
4
+ /.idea
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use --create 1.9.2@page_specific_js
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in page_specific_js.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,85 @@
1
+ Page Specific JavaScript Framework
2
+ ==================================
3
+
4
+ This is a page-specific JavaScript framework (primarily for Rails).
5
+
6
+ It allows you to target JS to run on all pages, for a specific action on all pages (e.g. index),
7
+ for all actions for a specific controller, or for one page (specific controller/action).
8
+
9
+ Dependencies
10
+ ------------
11
+ * Rails 3.1+
12
+ * JQuery
13
+
14
+ Installation
15
+ ------------
16
+
17
+ 1. Require the gem. Add the following to your Gemfile, then run 'bundle install':
18
+
19
+ gem 'page_specific_js'
20
+
21
+ 2. Cause the page-specific JS framework to be loaded. For Rails 3.1: in your
22
+ app/assets/javascripts/application.css.scss:
23
+
24
+ //= require page_specific
25
+
26
+ 3. Create a JS namespace for your page-specific code, and call PageSpecific.init() on page load.
27
+ This system assumes you follow the best-practise of namespacing all of your application JavaScript.
28
+ For example let's assume you are namespacing all your JS under class "MyApplication".
29
+ Put this in an appropriate spot in your JavaScript:
30
+
31
+ MyApplication.PageSpecific = {};
32
+
33
+ $(document).ready(function() {
34
+ PageSpecific.init(MyApplication);
35
+ });
36
+
37
+ 4. Set data-controller_name and data-action_name attributes on your document body, e.g. if you are using ERB,
38
+ in application.html.erb:
39
+
40
+ <body data-controller_name="<%= underscored_controller_name %>",
41
+ data-action_name="<%= action_name %>">
42
+
43
+ or if you are using HAML, in application.html.haml:
44
+
45
+ %body{ :'data-controller_name' => underscored_controller_name, :'data-action_name' => action_name }
46
+
47
+ Usage
48
+ -----
49
+
50
+ Write controller or page specific JavaScript as follows:
51
+
52
+ MyApplication.PageSpecific['AllControllers'] = {
53
+
54
+ allActions: function() {
55
+ // JS to be executed on every page
56
+ },
57
+
58
+ indexAction: function() {
59
+ // code to be executed on all index pages
60
+ }
61
+ };
62
+
63
+ MyApplication.PageSpecific['FooController'] = {
64
+
65
+ allActions: function() {
66
+ // code to be executed on all pages of FooController
67
+ },
68
+
69
+ barAction: function() {
70
+ // code to be executed on FooController#bar page
71
+ }
72
+ }
73
+
74
+ Notes
75
+ -----
76
+
77
+ * This is not an original teqhnique. I learned it from others while developing at Pivotal Labs and have
78
+ implemented it several times over the years. This just my cleaned up and organized version.
79
+
80
+ * All you JavaScript is still going to get loaded on every page; this only impacts what is executed.
81
+ In practice this is not an issue as your javascript should be aggregated, minified, compressed and cached.
82
+
83
+ License
84
+ -------
85
+ MIT. See LICENSE.txt
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ begin
4
+ require 'jasmine'
5
+ load 'jasmine/tasks/jasmine.rake'
6
+ rescue LoadError
7
+ task :jasmine do
8
+ abort "Jasmine is not available. In order to run jasmine, you must: (sudo) gem install jasmine"
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ require "page_specific_js/version"
2
+ require "page_specific_js/helper"
3
+
4
+ module PageSpecificJs
5
+ class Engine < ::Rails::Engine
6
+ # This causes Rails to search for assets in our vendor/assets folder
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module ::ApplicationHelper
2
+ def underscored_controller_name
3
+ controller.class.name.underscore.gsub('/', '_')
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module PageSpecificJs
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "page_specific_js/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "page_specific_js"
7
+ s.version = PageSpecificJs::VERSION
8
+ s.authors = ["Sam Pierson"]
9
+ s.email = ["sam@sampierson.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{ Page-specific JavaScript framework for Rails }
12
+ s.description = %q{ It allows you to target JS to run on all pages,
13
+ for a particular action on all pages (e.g. index),
14
+ for all actions for a specific controller,
15
+ or for one specific controller/action (i.e. a single page).
16
+ }
17
+
18
+ s.rubyforge_project = "page_specific_js"
19
+
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
+ s.require_paths = ["lib"]
24
+
25
+ # specify any dependencies here; for example:
26
+ s.add_development_dependency "rake"
27
+ s.add_development_dependency "jasmine"
28
+
29
+ # jQuery is a dependency, but I'm leaving this commented for those folks not using jquery-rails.
30
+ # s.add_runtime_dependency "jquery-rails"
31
+ end
File without changes
@@ -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
+ });