twitter-flight-rails 0.0.2

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 ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - ree
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Yousef Ourabi
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # Twitter Flight framework for Rails
2
+
3
+ This asset gem packages the [twitter flight](https://github.com/twitter/flight/) framework for the Rails asset pipeline.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'twitter-flight-rails'
10
+
11
+ or
12
+
13
+ gem 'twitter-flight-rails', :git => "git@github.com:yourabi/twitter-flight-rails.git"
14
+
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install twitter-flight-rails
23
+
24
+ ## Usage
25
+
26
+ To start using the twitter flight fraemwork in your rails app enable it via the asset pipeline (app/assets/javascripts/application.js).
27
+
28
+ Add the folllwing:
29
+
30
+ ```js
31
+
32
+ //= require twitter/flight
33
+
34
+ ```
35
+
36
+ Currently this version tracks flight master [commit e07b90c78d](https://github.com/twitter/flight/commit/e07b90c78d416549455354cbcd3e7f8a001c4fdf) and may support release tags in the future.
37
+
38
+ ## Dependencies
39
+
40
+ Flight uses [ES5-shim](https://github.com/kriskowal/es5-shim) to polyfill ES5 support for older browsers and [JQuery](http://jquery.com) for DOM manipulation API.
41
+
42
+ Note: as of version 0.0.2 the es5-shim dependency is not handled.
43
+
44
+ ## Contributing
45
+
46
+ 1. Fork it
47
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
48
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
49
+ 4. Push to the branch (`git push origin my-new-feature`)
50
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,7 @@
1
+ module Twitter
2
+ module Flight
3
+ module Rails
4
+ VERSION = "0.0.2"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ require "twitter-flight-rails/version"
2
+
3
+ module Twitter
4
+ module Flight
5
+ module Rails
6
+ class Engine < ::Rails::Engine
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'twitter-flight-rails/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "twitter-flight-rails"
8
+ gem.version = Twitter::Flight::Rails::VERSION
9
+ gem.authors = ["Yousef Ourabi"]
10
+ gem.email = ["yourabi@gmail.com"]
11
+ gem.description = %q{twitter-flight-rails flight framework for Rails asset pipeline}
12
+ gem.summary = %q{twitter-flight-rails packages the flight framework into an asset gem}
13
+ gem.homepage = "https://github.com/yourabi/twitter-flight-rails"
14
+
15
+ gem.add_dependency 'railties', '>= 3.1'
16
+ gem.add_dependency 'actionpack', '>= 3.1'
17
+ gem.add_dependency 'jquery-rails'
18
+
19
+ gem.add_development_dependency 'rails', '>= 3.1'
20
+
21
+ gem.files = `git ls-files`.split($/)
22
+ # gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
23
+ # gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
24
+ gem.require_paths = ["lib"]
25
+ end
@@ -0,0 +1,75 @@
1
+ // ==========================================
2
+ // Copyright 2013 Twitter, Inc
3
+ // Licensed under The MIT License
4
+ // http://opensource.org/licenses/MIT
5
+ // ==========================================
6
+
7
+ "use strict";
8
+
9
+ define(
10
+
11
+ [
12
+ './utils',
13
+ './compose'
14
+ ],
15
+
16
+ function (util, compose) {
17
+
18
+ var advice = {
19
+
20
+ around: function(base, wrapped) {
21
+ return function() {
22
+ var args = util.toArray(arguments);
23
+ return wrapped.apply(this, [base.bind(this)].concat(args));
24
+ }
25
+ },
26
+
27
+ before: function(base, before) {
28
+ return this.around(base, function() {
29
+ var args = util.toArray(arguments),
30
+ orig = args.shift(),
31
+ beforeFn;
32
+
33
+ beforeFn = (typeof before == 'function') ? before : before.obj[before.fnName];
34
+ beforeFn.apply(this, args);
35
+ return (orig).apply(this, args);
36
+ });
37
+ },
38
+
39
+ after: function(base, after) {
40
+ return this.around(base, function() {
41
+ var args = util.toArray(arguments),
42
+ orig = args.shift(),
43
+ afterFn;
44
+
45
+ // this is a separate statement for debugging purposes.
46
+ var res = (orig.unbound || orig).apply(this, args);
47
+
48
+ afterFn = (typeof after == 'function') ? after : after.obj[after.fnName];
49
+ afterFn.apply(this, args);
50
+ return res;
51
+ });
52
+ },
53
+
54
+ // a mixin that allows other mixins to augment existing functions by adding additional
55
+ // code before, after or around.
56
+ withAdvice: function() {
57
+ ['before', 'after', 'around'].forEach(function(m) {
58
+ this[m] = function(method, fn) {
59
+
60
+ compose.unlockProperty(this, method, function() {
61
+ if (typeof this[method] == 'function') {
62
+ return this[method] = advice[m](this[method], fn);
63
+ } else {
64
+ return this[method] = fn;
65
+ }
66
+ });
67
+
68
+ };
69
+ }, this);
70
+ }
71
+ };
72
+
73
+ return advice;
74
+ }
75
+ );
@@ -0,0 +1,262 @@
1
+ // ==========================================
2
+ // Copyright 2013 Twitter, Inc
3
+ // Licensed under The MIT License
4
+ // http://opensource.org/licenses/MIT
5
+ // ==========================================
6
+
7
+ "use strict";
8
+
9
+ define(
10
+
11
+ [
12
+ './advice',
13
+ './utils',
14
+ './compose',
15
+ './registry'
16
+ ],
17
+
18
+ function(advice, utils, compose, registry) {
19
+
20
+ function teardownInstance(instanceInfo){
21
+ instanceInfo.events.slice().forEach(function(event) {
22
+ var args = [event.type];
23
+
24
+ event.element && args.unshift(event.element);
25
+ (typeof event.callback == 'function') && args.push(event.callback);
26
+
27
+ this.off.apply(this, args);
28
+ }, instanceInfo.instance);
29
+ }
30
+
31
+
32
+ function teardown() {
33
+ this.trigger("componentTearDown");
34
+ teardownInstance(registry.findInstanceInfo(this));
35
+ }
36
+
37
+ //teardown for all instances of this constructor
38
+ function teardownAll() {
39
+ var componentInfo = registry.findComponentInfo(this);
40
+
41
+ componentInfo && componentInfo.instances.slice().forEach(function(info) {
42
+ info.instance.teardown();
43
+ });
44
+ }
45
+
46
+ //common mixin allocates basic functionality - used by all component prototypes
47
+ //callback context is bound to component
48
+ function withBaseComponent() {
49
+
50
+ // delegate trigger, bind and unbind to an element
51
+ // if $element not supplied, use component's node
52
+ // other arguments are passed on
53
+ this.trigger = function() {
54
+ var $element, type, data;
55
+ var args = utils.toArray(arguments);
56
+
57
+ if (typeof args[args.length - 1] != "string") {
58
+ data = args.pop();
59
+ }
60
+
61
+ $element = (args.length == 2) ? $(args.shift()) : this.$node;
62
+ type = args[0];
63
+
64
+ if (window.DEBUG && window.postMessage) {
65
+ try {
66
+ window.postMessage(data, '*');
67
+ } catch(e) {
68
+ console.log('unserializable data for event',type,':',data);
69
+ throw new Error(
70
+ ["The event", event.type, "on component", this.describe, "was triggered with non-serializable data"].join(" ")
71
+ );
72
+ }
73
+ }
74
+
75
+ if (typeof this.attr.eventData === 'object') {
76
+ data = $.extend(true, {}, this.attr.eventData, data);
77
+ }
78
+
79
+ return $element.trigger(type, data);
80
+ };
81
+
82
+ this.on = function() {
83
+ var $element, type, callback, originalCb;
84
+ var args = utils.toArray(arguments);
85
+
86
+ if (typeof args[args.length - 1] == "object") {
87
+ //delegate callback
88
+ originalCb = utils.delegate(
89
+ this.resolveDelegateRules(args.pop())
90
+ );
91
+ } else {
92
+ originalCb = args.pop();
93
+ }
94
+
95
+ callback = originalCb && originalCb.bind(this);
96
+ callback.target = originalCb;
97
+
98
+ // if the original callback is already branded by jQuery's guid, copy it to the context-bound version
99
+ if (originalCb.guid) {
100
+ callback.guid = originalCb.guid;
101
+ }
102
+
103
+ $element = (args.length == 2) ? $(args.shift()) : this.$node;
104
+ type = args[0];
105
+
106
+ if (typeof callback == 'undefined') {
107
+ throw new Error("Unable to bind to '" + type + "' because the given callback is undefined");
108
+ }
109
+
110
+ $element.on(type, callback);
111
+
112
+ // get jquery's guid from our bound fn, so unbinding will work
113
+ originalCb.guid = callback.guid;
114
+
115
+ return callback;
116
+ };
117
+
118
+ this.off = function() {
119
+ var $element, type, callback;
120
+ var args = utils.toArray(arguments);
121
+
122
+ if (typeof args[args.length - 1] == "function") {
123
+ callback = args.pop();
124
+ }
125
+
126
+ $element = (args.length == 2) ? $(args.shift()) : this.$node;
127
+ type = args[0];
128
+
129
+ return $element.off(type, callback);
130
+ };
131
+
132
+ this.resolveDelegateRules = function(ruleInfo) {
133
+ var rules = {};
134
+
135
+ Object.keys(ruleInfo).forEach(
136
+ function(r) {
137
+ if (!this.attr.hasOwnProperty(r)) {
138
+ throw new Error('Component "' + this.describe + '" wants to listen on "' + r + '" but no such attribute was defined.');
139
+ }
140
+ rules[this.attr[r]] = ruleInfo[r];
141
+ },
142
+ this
143
+ );
144
+
145
+ return rules;
146
+ };
147
+
148
+ this.defaultAttrs = function(defaults) {
149
+ utils.push(this.defaults, defaults, true) || (this.defaults = defaults);
150
+ };
151
+
152
+ this.select = function(attributeKey) {
153
+ return this.$node.find(this.attr[attributeKey]);
154
+ };
155
+
156
+ this.initialize = $.noop;
157
+ this.teardown = teardown;
158
+ }
159
+
160
+ function attachTo(selector/*, options args */) {
161
+ if (!selector) {
162
+ throw new Error("Component needs to be attachTo'd a jQuery object, native node or selector string");
163
+ }
164
+
165
+ var options = utils.merge.apply(utils, utils.toArray(arguments, 1));
166
+
167
+ $(selector).each(function(i, node) {
168
+ new this(node, options);
169
+ }.bind(this));
170
+ }
171
+
172
+ // define the constructor for a custom component type
173
+ // takes an unlimited number of mixin functions as arguments
174
+ // typical api call with 3 mixins: define(timeline, withTweetCapability, withScrollCapability);
175
+ function define(/*mixins*/) {
176
+ var mixins = utils.toArray(arguments);
177
+
178
+ Component.toString = function() {
179
+ var prettyPrintMixins = mixins.map(function(mixin) {
180
+ if ($.browser.msie) {
181
+ var m = mixin.toString().match(/function (.*?)\s?\(/);
182
+ return (m && m[1]) ? m[1] : "";
183
+ } else {
184
+ return mixin.name;
185
+ }
186
+ }).join(', ').replace(/\s\,/g,'');//weed out no-named mixins
187
+
188
+ return prettyPrintMixins;
189
+ };
190
+
191
+ Component.describe = Component.toString();
192
+
193
+ //'options' is optional hash to be merged with 'defaults' in the component definition
194
+ function Component(node, options) {
195
+ var fnCache = {}, uuid = 0;
196
+
197
+ if (!node) {
198
+ throw new Error("Component needs a node");
199
+ }
200
+
201
+ if (node.jquery) {
202
+ this.node = node[0];
203
+ this.$node = node;
204
+ } else {
205
+ this.node = node;
206
+ this.$node = $(node);
207
+ }
208
+
209
+ this.describe = this.constructor.describe;
210
+
211
+ this.bind = function(func) {
212
+ var bound;
213
+
214
+ if (func.uuid && (bound = fnCache[func.uuid])) {
215
+ return bound;
216
+ }
217
+
218
+ var bindArgs = utils.toArray(arguments, 1);
219
+ bindArgs.unshift(this); //prepend context
220
+
221
+ bound = func.bind.apply(func, bindArgs);
222
+ bound.target = func;
223
+ func.uuid = uuid++;
224
+ fnCache[func.uuid] = bound;
225
+
226
+ return bound;
227
+ };
228
+
229
+ //merge defaults with supplied options
230
+ this.attr = utils.merge(this.defaults, options);
231
+ this.defaults && Object.keys(this.defaults).forEach(function(key) {
232
+ if (this.defaults[key] === null && this.attr[key] === null) {
233
+ throw new Error('Required attribute "' + key + '" not specified in attachTo for component "' + this.describe + '".');
234
+ }
235
+ }, this);
236
+
237
+ this.initialize.call(this, options || {});
238
+
239
+ this.trigger('componentInitialized');
240
+ }
241
+
242
+ Component.attachTo = attachTo;
243
+ Component.teardownAll = teardownAll;
244
+
245
+ // prepend common mixins to supplied list, then mixin all flavors
246
+ mixins.unshift(withBaseComponent, advice.withAdvice, registry.withRegistration);
247
+
248
+ compose.mixin(Component.prototype, mixins);
249
+
250
+ return Component;
251
+ }
252
+
253
+ define.teardownAll = function() {
254
+ registry.components.slice().forEach(function(c) {
255
+ c.component.teardownAll();
256
+ });
257
+ registry.reset();
258
+ };
259
+
260
+ return define;
261
+ }
262
+ );
@@ -0,0 +1,86 @@
1
+ // ==========================================
2
+ // Copyright 2013 Twitter, Inc
3
+ // Licensed under The MIT License
4
+ // http://opensource.org/licenses/MIT
5
+ // ==========================================
6
+
7
+ "use strict";
8
+
9
+ define(
10
+
11
+ [
12
+ './utils',
13
+ '../tools/debug/debug'
14
+ ],
15
+
16
+ function(util, debug) {
17
+
18
+ //enumerables are shims - getOwnPropertyDescriptor shim doesn't work
19
+ var canWriteProtect = debug.enabled && !util.isEnumerable(Object, 'getOwnPropertyDescriptor');
20
+ //whitelist of unlockable property names
21
+ var dontLock = ['mixedIn'];
22
+
23
+ if (canWriteProtect) {
24
+ //IE8 getOwnPropertyDescriptor is built-in but throws exeption on non DOM objects
25
+ try {
26
+ Object.getOwnPropertyDescriptor(Object, 'keys');
27
+ } catch(e) {
28
+ canWriteProtect = false;
29
+ }
30
+ }
31
+
32
+ function setPropertyWritability(obj, isWritable) {
33
+ if (!canWriteProtect) {
34
+ return;
35
+ }
36
+
37
+ var props = Object.create(null);
38
+
39
+ Object.keys(obj).forEach(
40
+ function (key) {
41
+ if (dontLock.indexOf(key) < 0) {
42
+ var desc = Object.getOwnPropertyDescriptor(obj, key);
43
+ desc.writable = isWritable;
44
+ props[key] = desc;
45
+ }
46
+ }
47
+ );
48
+
49
+ Object.defineProperties(obj, props);
50
+ }
51
+
52
+ function unlockProperty(obj, prop, op) {
53
+ var writable;
54
+
55
+ if (!canWriteProtect || !obj.hasOwnProperty(prop)) {
56
+ op.call(obj);
57
+ return;
58
+ }
59
+
60
+ writable = Object.getOwnPropertyDescriptor(obj, prop).writable;
61
+ Object.defineProperty(obj, prop, { writable: true });
62
+ op.call(obj);
63
+ Object.defineProperty(obj, prop, { writable: writable });
64
+ }
65
+
66
+ function mixin(base, mixins) {
67
+ base.mixedIn = base.hasOwnProperty('mixedIn') ? base.mixedIn : [];
68
+
69
+ mixins.forEach(function(mixin) {
70
+ if (base.mixedIn.indexOf(mixin) == -1) {
71
+ setPropertyWritability(base, false);
72
+ mixin.call(base);
73
+ base.mixedIn.push(mixin);
74
+ }
75
+ });
76
+
77
+ setPropertyWritability(base, true);
78
+ }
79
+
80
+ return {
81
+ mixin: mixin,
82
+ unlockProperty: unlockProperty
83
+ };
84
+
85
+ }
86
+ );
@@ -0,0 +1,30 @@
1
+ // ==========================================
2
+ // Copyright 2013 Twitter, Inc
3
+ // Licensed under The MIT License
4
+ // http://opensource.org/licenses/MIT
5
+ // ==========================================
6
+
7
+ define(
8
+
9
+ [
10
+ './advice',
11
+ './component',
12
+ './compose',
13
+ './logger',
14
+ './registry',
15
+ './utils'
16
+ ],
17
+
18
+ function (advice, component, compose, logger, registry, utils) {
19
+
20
+ return {
21
+ advice: advice,
22
+ component: component,
23
+ compose: compose,
24
+ logger: logger,
25
+ registry: registry,
26
+ utils: utils
27
+ };
28
+
29
+ }
30
+ );
@@ -0,0 +1,93 @@
1
+ // ==========================================
2
+ // Copyright 2013 Twitter, Inc
3
+ // Licensed under The MIT License
4
+ // http://opensource.org/licenses/MIT
5
+ // ==========================================
6
+
7
+ "use strict";
8
+
9
+ define(
10
+
11
+ [
12
+ './compose',
13
+ './utils'
14
+ ],
15
+
16
+ function (compose, util) {
17
+
18
+ var actionSymbols = {
19
+ on:'<-',
20
+ trigger: '->',
21
+ off: 'x '
22
+ };
23
+
24
+ function elemToString(elem) {
25
+ var tagStr = elem.tagName ? elem.tagName.toLowerCase() : elem.toString();
26
+ var classStr = elem.className ? "." + (elem.className) : "";
27
+ var result = tagStr + classStr;
28
+ return elem.tagName ? ['\'', '\''].join(result) : result;
29
+ }
30
+
31
+ function log(action, component, eventArgs) {
32
+
33
+ var name, elem, fn, fnName, logFilter, toRegExp, actionLoggable, nameLoggable;
34
+
35
+ if (typeof eventArgs[eventArgs.length-1] == 'function') {
36
+ fn = eventArgs.pop();
37
+ fn = fn.unbound || fn; //use unbound version if any (better info)
38
+ }
39
+
40
+ if (typeof eventArgs[eventArgs.length - 1] == 'object') {
41
+ eventArgs.pop(); //trigger data arg - not logged right now
42
+ }
43
+
44
+ if (eventArgs.length == 2) {
45
+ elem = eventArgs[0];
46
+ name = eventArgs[1];
47
+ } else {
48
+ elem = component.$node[0];
49
+ name = eventArgs[0];
50
+ }
51
+
52
+ if (window.DEBUG) {
53
+ logFilter = DEBUG.events.logFilter;
54
+
55
+ // no regex for you, actions...
56
+ actionLoggable = logFilter.actions=="all" || (logFilter.actions.indexOf(action) > -1);
57
+ // event name filter allow wildcards or regex...
58
+ toRegExp = function(expr) {
59
+ return expr.test ? expr : new RegExp("^" + expr.replace(/\*/g, ".*") + "$");
60
+ };
61
+ nameLoggable =
62
+ logFilter.eventNames=="all" ||
63
+ logFilter.eventNames.some(function(e) {return toRegExp(e).test(name)});
64
+
65
+ if (actionLoggable && nameLoggable) {
66
+ console.info(
67
+ actionSymbols[action],
68
+ action,
69
+ '[' + name + ']',
70
+ elemToString(elem),
71
+ component.constructor.describe,
72
+ fn && (fnName = fn.name || fn.displayName) && '-> ' + fnName
73
+ );
74
+ }
75
+ }
76
+ }
77
+
78
+
79
+ function withLogging() {
80
+ this.before('trigger', function() {
81
+ log('trigger', this, util.toArray(arguments));
82
+ });
83
+ this.before('on', function() {
84
+ log('on', this, util.toArray(arguments));
85
+ });
86
+ this.before('off', function(eventArgs) {
87
+ log('off', this, util.toArray(arguments));
88
+ });
89
+ }
90
+
91
+ return withLogging;
92
+ }
93
+ );
@@ -0,0 +1,234 @@
1
+ // ==========================================
2
+ // Copyright 2013 Twitter, Inc
3
+ // Licensed under The MIT License
4
+ // http://opensource.org/licenses/MIT
5
+ // ==========================================
6
+
7
+ "use strict";
8
+
9
+ define(
10
+
11
+ [
12
+ './utils'
13
+ ],
14
+
15
+ function (util) {
16
+
17
+ function parseEventArgs(instance, args) {
18
+ var element, type, callback;
19
+
20
+ args = util.toArray(args);
21
+
22
+ if (typeof args[args.length-1] === 'function') {
23
+ callback = args.pop();
24
+ }
25
+
26
+ if (typeof args[args.length-1] === 'object') {
27
+ args.pop();
28
+ }
29
+
30
+ if (args.length == 2) {
31
+ element = args[0];
32
+ type = args[1];
33
+ } else {
34
+ element = instance.node;
35
+ type = args[0];
36
+ }
37
+
38
+ return {
39
+ element: element,
40
+ type: type,
41
+ callback: callback
42
+ };
43
+ }
44
+
45
+ function matchEvent(a, b) {
46
+ return (
47
+ (a.element == b.element) &&
48
+ (a.type == b.type) &&
49
+ (b.callback == null || (a.callback == b.callback))
50
+ );
51
+ }
52
+
53
+ function Registry() {
54
+
55
+ var registry = this;
56
+
57
+ (this.reset = function() {
58
+ this.components = [];
59
+ this.allInstances = [];
60
+ this.events = [];
61
+ }).call(this);
62
+
63
+ function ComponentInfo(component) {
64
+ this.component = component;
65
+ this.instances = [];
66
+
67
+ this.addInstance = function(instance) {
68
+ this.throwIfInstanceExistsOnNode(instance);
69
+
70
+ var instanceInfo = new InstanceInfo(instance);
71
+ this.instances.push(instanceInfo);
72
+
73
+ return instanceInfo;
74
+ }
75
+
76
+ this.throwIfInstanceExistsOnNode = function(instance) {
77
+ this.instances.forEach(function (instanceInfo) {
78
+ if (instanceInfo.instance.$node[0] === instance.$node[0]) {
79
+ throw new Error('Instance of ' + instance.constructor + ' already exists on node ' + instance.$node[0]);
80
+ }
81
+ });
82
+ }
83
+
84
+ this.removeInstance = function(instance) {
85
+ var instanceInfo = this.instances.filter(function(instanceInfo) {
86
+ return instanceInfo.instance == instance;
87
+ })[0];
88
+
89
+ var index = this.instances.indexOf(instanceInfo);
90
+
91
+ (index > -1) && this.instances.splice(index, 1);
92
+
93
+ if (!this.instances.length) {
94
+ //if I hold no more instances remove me from registry
95
+ registry.removeComponentInfo(this);
96
+ }
97
+ }
98
+ }
99
+
100
+ function InstanceInfo(instance) {
101
+ this.instance = instance;
102
+ this.events = [];
103
+
104
+ this.addTrigger = function() {};
105
+
106
+ this.addBind = function(event) {
107
+ this.events.push(event);
108
+ registry.events.push(event);
109
+ };
110
+
111
+ this.removeBind = function(event) {
112
+ for (var i = 0, e; e = this.events[i]; i++) {
113
+ if (matchEvent(e, event)) {
114
+ this.events.splice(i, 1);
115
+ }
116
+ }
117
+ }
118
+ }
119
+
120
+ this.addInstance = function(instance) {
121
+ var component = this.findComponentInfo(instance);
122
+
123
+ if (!component) {
124
+ component = new ComponentInfo(instance.constructor);
125
+ this.components.push(component);
126
+ }
127
+
128
+ var inst = component.addInstance(instance);
129
+
130
+ this.allInstances.push(inst);
131
+
132
+ return component;
133
+ };
134
+
135
+ this.removeInstance = function(instance) {
136
+ var index, instInfo = this.findInstanceInfo(instance);
137
+
138
+ //remove from component info
139
+ var componentInfo = this.findComponentInfo(instance);
140
+ componentInfo.removeInstance(instance);
141
+
142
+ //remove from registry
143
+ var index = this.allInstances.indexOf(instInfo);
144
+ (index > -1) && this.allInstances.splice(index, 1);
145
+ };
146
+
147
+ this.removeComponentInfo = function(componentInfo) {
148
+ var index = this.components.indexOf(componentInfo);
149
+ (index > -1) && this.components.splice(index, 1);
150
+ };
151
+
152
+ this.findComponentInfo = function(which) {
153
+ var component = which.attachTo ? which : which.constructor;
154
+
155
+ for (var i = 0, c; c = this.components[i]; i++) {
156
+ if (c.component === component) {
157
+ return c;
158
+ }
159
+ }
160
+
161
+ return null;
162
+ };
163
+
164
+ this.findInstanceInfo = function(which) {
165
+ var testFn;
166
+
167
+ if (which.node) {
168
+ //by instance (returns matched instance)
169
+ testFn = function(inst) {return inst.instance === which};
170
+ } else {
171
+ //by node (returns array of matches)
172
+ testFn = function(inst) {return inst.instance.node === which};
173
+ }
174
+
175
+ var matches = this.allInstances.filter(testFn);
176
+ if (!matches.length) {
177
+ return which.node ? null : [];
178
+ }
179
+ return which.node ? matches[0] : matches;
180
+ };
181
+
182
+ this.trigger = function() {
183
+ var event = parseEventArgs(this, arguments),
184
+ instance = registry.findInstanceInfo(this);
185
+
186
+ if (instance) {
187
+ instance.addTrigger(event);
188
+ }
189
+ };
190
+
191
+ this.on = function(componentOn) {
192
+ var otherArgs = util.toArray(arguments, 1);
193
+ var instance = registry.findInstanceInfo(this);
194
+ var boundCallback;
195
+
196
+ if (instance) {
197
+ boundCallback = componentOn.apply(null, otherArgs);
198
+ if(boundCallback) {
199
+ otherArgs[otherArgs.length-1] = boundCallback;
200
+ }
201
+ var event = parseEventArgs(this, otherArgs);
202
+ instance.addBind(event);
203
+ }
204
+ };
205
+
206
+ this.off = function(el, type, callback) {
207
+ var event = parseEventArgs(this, arguments),
208
+ instance = registry.findInstanceInfo(this);
209
+
210
+ if (instance) {
211
+ instance.removeBind(event);
212
+ }
213
+ };
214
+
215
+ this.teardown = function() {
216
+ registry.removeInstance(this);
217
+ };
218
+
219
+ this.withRegistration = function() {
220
+ this.before('initialize', function() {
221
+ registry.addInstance(this);
222
+ });
223
+
224
+ this.after('trigger', registry.trigger);
225
+ this.around('on', registry.on);
226
+ this.after('off', registry.off);
227
+ this.after('teardown', {obj:registry, fnName:'teardown'});
228
+ };
229
+
230
+ }
231
+
232
+ return new Registry;
233
+ }
234
+ );
@@ -0,0 +1,228 @@
1
+ // ==========================================
2
+ // Copyright 2013 Twitter, Inc
3
+ // Licensed under The MIT License
4
+ // http://opensource.org/licenses/MIT
5
+ // ==========================================
6
+
7
+ "use strict";
8
+
9
+ define(
10
+
11
+ [],
12
+
13
+ function () {
14
+
15
+ var arry = [];
16
+ var DEFAULT_INTERVAL = 100;
17
+
18
+ var utils = {
19
+
20
+ isDomObj: function(obj) {
21
+ return !!(obj.nodeType || (obj === window));
22
+ },
23
+
24
+ toArray: function(obj, from) {
25
+ return arry.slice.call(obj, from);
26
+ },
27
+
28
+ // returns new object representing multiple objects merged together
29
+ // optional final argument is boolean which specifies if merge is recursive
30
+ // original objects are unmodified
31
+ //
32
+ // usage:
33
+ // var base = {a:2, b:6};
34
+ // var extra = {b:3, c:4};
35
+ // merge(base, extra); //{a:2, b:3, c:4}
36
+ // base; //{a:2, b:6}
37
+ //
38
+ // var base = {a:2, b:6};
39
+ // var extra = {b:3, c:4};
40
+ // var extraExtra = {a:4, d:9};
41
+ // merge(base, extra, extraExtra); //{a:4, b:3, c:4. d: 9}
42
+ // base; //{a:2, b:6}
43
+ //
44
+ // var base = {a:2, b:{bb:4, cc:5}};
45
+ // var extra = {a:4, b:{cc:7, dd:1}};
46
+ // merge(base, extra, true); //{a:4, b:{bb:4, cc:7, dd:1}}
47
+ // base; //{a:2, b:6}
48
+
49
+ merge: function(/*obj1, obj2,....deepCopy*/) {
50
+ var args = this.toArray(arguments);
51
+
52
+ //start with empty object so a copy is created
53
+ args.unshift({});
54
+
55
+ if (args[args.length - 1] === true) {
56
+ //jquery extend requires deep copy as first arg
57
+ args.pop();
58
+ args.unshift(true);
59
+ }
60
+
61
+ return $.extend.apply(undefined, args);
62
+ },
63
+
64
+ // updates base in place by copying properties of extra to it
65
+ // optionally clobber protected
66
+ // usage:
67
+ // var base = {a:2, b:6};
68
+ // var extra = {c:4};
69
+ // push(base, extra); //{a:2, b:6, c:4}
70
+ // base; //{a:2, b:6, c:4}
71
+ //
72
+ // var base = {a:2, b:6};
73
+ // var extra = {b: 4 c:4};
74
+ // push(base, extra, true); //Error ("utils.push attempted to overwrite 'b' while running in protected mode")
75
+ // base; //{a:2, b:6}
76
+ //
77
+ // objects with the same key will merge recursively when protect is false
78
+ // eg:
79
+ // var base = {a:16, b:{bb:4, cc:10}};
80
+ // var extra = {b:{cc:25, dd:19}, c:5};
81
+ // push(base, extra); //{a:16, {bb:4, cc:25, dd:19}, c:5}
82
+ //
83
+ push: function(base, extra, protect) {
84
+ if (base) {
85
+ Object.keys(extra || {}).forEach(function(key) {
86
+ if (base[key] && protect) {
87
+ throw Error("utils.push attempted to overwrite '" + key + "' while running in protected mode");
88
+ }
89
+
90
+ if (typeof base[key] == "object" && typeof extra[key] == "object") {
91
+ //recurse
92
+ this.push(base[key], extra[key]);
93
+ } else {
94
+ //no protect, so extra wins
95
+ base[key] = extra[key];
96
+ }
97
+ }, this);
98
+ }
99
+
100
+ return base;
101
+ },
102
+
103
+ isEnumerable: function(obj, property) {
104
+ return Object.keys(obj).indexOf(property) > -1;
105
+ },
106
+
107
+ //build a function from other function(s)
108
+ //util.compose(a,b,c) -> a(b(c()));
109
+ //implementation lifted from underscore.js (c) 2009-2012 Jeremy Ashkenas
110
+ compose: function() {
111
+ var funcs = arguments;
112
+
113
+ return function() {
114
+ var args = arguments;
115
+
116
+ for (var i = funcs.length-1; i >= 0; i--) {
117
+ args = [funcs[i].apply(this, args)];
118
+ }
119
+
120
+ return args[0];
121
+ };
122
+ },
123
+
124
+ // Can only unique arrays of homogeneous primitives, e.g. an array of only strings, an array of only booleans, or an array of only numerics
125
+ uniqueArray: function(array) {
126
+ var u = {}, a = [];
127
+
128
+ for (var i = 0, l = array.length; i < l; ++i) {
129
+ if (u.hasOwnProperty(array[i])) {
130
+ continue;
131
+ }
132
+
133
+ a.push(array[i]);
134
+ u[array[i]] = 1;
135
+ }
136
+
137
+ return a;
138
+ },
139
+
140
+ debounce: function(func, wait, immediate) {
141
+ if (typeof wait != 'number') {
142
+ wait = DEFAULT_INTERVAL;
143
+ }
144
+
145
+ var timeout, result;
146
+
147
+ return function() {
148
+ var context = this, args = arguments;
149
+ var later = function() {
150
+ timeout = null;
151
+ if (!immediate) {
152
+ result = func.apply(context, args);
153
+ }
154
+ };
155
+ var callNow = immediate && !timeout;
156
+
157
+ clearTimeout(timeout);
158
+ timeout = setTimeout(later, wait);
159
+
160
+ if (callNow) {
161
+ result = func.apply(context, args);
162
+ }
163
+
164
+ return result;
165
+ };
166
+ },
167
+
168
+ throttle: function(func, wait) {
169
+ if (typeof wait != 'number') {
170
+ wait = DEFAULT_INTERVAL;
171
+ }
172
+
173
+ var context, args, timeout, throttling, more, result;
174
+ var whenDone = this.debounce(function(){
175
+ more = throttling = false;
176
+ }, wait);
177
+
178
+ return function() {
179
+ context = this; args = arguments;
180
+ var later = function() {
181
+ timeout = null;
182
+ if (more) {
183
+ result = func.apply(context, args);
184
+ }
185
+ whenDone();
186
+ };
187
+
188
+ if (!timeout) {
189
+ timeout = setTimeout(later, wait);
190
+ }
191
+
192
+ if (throttling) {
193
+ more = true;
194
+ } else {
195
+ throttling = true;
196
+ result = func.apply(context, args);
197
+ }
198
+
199
+ whenDone();
200
+ return result;
201
+ };
202
+ },
203
+
204
+ countThen: function(num, base) {
205
+ return function() {
206
+ if (!--num) { return base.apply(this, arguments); }
207
+ };
208
+ },
209
+
210
+ delegate: function(rules) {
211
+ return function(e, data) {
212
+ var target = $(e.target), parent;
213
+
214
+ Object.keys(rules).forEach(function(selector) {
215
+ if ((parent = target.closest(selector)).length) {
216
+ data = data || {};
217
+ data.el = parent[0];
218
+ return rules[selector].apply(this, [e, data]);
219
+ }
220
+ }, this);
221
+ };
222
+ }
223
+
224
+ };
225
+
226
+ return utils;
227
+ }
228
+ );
@@ -0,0 +1 @@
1
+ //= require_directory twitter/flight
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: twitter-flight-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Yousef Ourabi
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: railties
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '3.1'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '3.1'
30
+ - !ruby/object:Gem::Dependency
31
+ name: actionpack
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '3.1'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '3.1'
46
+ - !ruby/object:Gem::Dependency
47
+ name: jquery-rails
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rails
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '3.1'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '3.1'
78
+ description: twitter-flight-rails flight framework for Rails asset pipeline
79
+ email:
80
+ - yourabi@gmail.com
81
+ executables: []
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - .gitignore
86
+ - .travis.yml
87
+ - Gemfile
88
+ - LICENSE.txt
89
+ - README.md
90
+ - Rakefile
91
+ - lib/twitter-flight-rails.rb
92
+ - lib/twitter-flight-rails/version.rb
93
+ - twitter-flight-rails.gemspec
94
+ - vendor/assets/javascripts/twitter/flight.js
95
+ - vendor/assets/javascripts/twitter/flight/advice.js
96
+ - vendor/assets/javascripts/twitter/flight/component.js
97
+ - vendor/assets/javascripts/twitter/flight/compose.js
98
+ - vendor/assets/javascripts/twitter/flight/index.js
99
+ - vendor/assets/javascripts/twitter/flight/logger.js
100
+ - vendor/assets/javascripts/twitter/flight/registry.js
101
+ - vendor/assets/javascripts/twitter/flight/utils.js
102
+ homepage: https://github.com/yourabi/twitter-flight-rails
103
+ licenses: []
104
+ post_install_message:
105
+ rdoc_options: []
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ! '>='
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ! '>='
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubyforge_project:
122
+ rubygems_version: 1.8.23
123
+ signing_key:
124
+ specification_version: 3
125
+ summary: twitter-flight-rails packages the flight framework into an asset gem
126
+ test_files: []