flight-rails 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in flight-rails.gemspec
4
+ gemspec
5
+
6
+ gem 'jquery-rails'
7
+ gem 'requirejs-rails'
8
+ gem 'es5-shim-rails', github: 'msievers/es5-shim-rails'
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Viktor Tomilin
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,36 @@
1
+ # Flight for Rails 3.1 Asset Pipeline
2
+
3
+ A lightweight, component-based JavaScript framework from Twitter.
4
+
5
+ Official repo: https://github.com/twitter/bower
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'flight-rails'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install flight-rails
20
+
21
+ ## Usage
22
+
23
+ Remove all Sprockets directives such as //= require jquery from application.js and elsewhere. Instead establish JavaScript dependencies using AMD-style define() and require() calls.
24
+
25
+ ## Dependencies
26
+
27
+ $ requirejs-rails
28
+ $ es5-shim-rails
29
+
30
+ ## Contributing
31
+
32
+ 1. Fork it
33
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
34
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
35
+ 4. Push to the branch (`git push origin my-new-feature`)
36
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'flight-rails/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "flight-rails"
8
+ gem.version = Flight::Rails::VERSION
9
+ gem.authors = ["Viktor Tomilin"]
10
+ gem.email = ["caballerosolar@gmail.com"]
11
+ gem.description = 'Flight: an event driven component framework'
12
+ gem.summary = 'Flight: an event driven component framework'
13
+ gem.homepage = "http://twitter.github.com/flight/"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+ end
@@ -0,0 +1,8 @@
1
+ require "rails"
2
+ require "flight-rails/version"
3
+
4
+ module Flight
5
+ module Rails
6
+ require "flight-rails/engine"
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module Flight
2
+ class Engine < ::Rails::Engine
3
+ initializer 'flight-rails-setup', group: :all
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Flight
2
+ module Rails
3
+ VERSION = "0.0.1"
4
+ end
5
+ 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,257 @@
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
+ $element = (args.length == 2) ? $(args.shift()) : this.$node;
99
+ type = args[0];
100
+
101
+ if (typeof callback == 'undefined') {
102
+ throw new Error("Unable to bind to '" + type + "' because the given callback is undefined");
103
+ }
104
+
105
+ $element.on(type, callback);
106
+
107
+ // get jquery's guid from our bound fn, so unbinding will work
108
+ originalCb.guid = callback.guid;
109
+
110
+ return callback;
111
+ };
112
+
113
+ this.off = function() {
114
+ var $element, type, callback;
115
+ var args = utils.toArray(arguments);
116
+
117
+ if (typeof args[args.length - 1] == "function") {
118
+ callback = args.pop();
119
+ }
120
+
121
+ $element = (args.length == 2) ? $(args.shift()) : this.$node;
122
+ type = args[0];
123
+
124
+ return $element.off(type, callback);
125
+ };
126
+
127
+ this.resolveDelegateRules = function(ruleInfo) {
128
+ var rules = {};
129
+
130
+ Object.keys(ruleInfo).forEach(
131
+ function(r) {
132
+ if (!this.attr.hasOwnProperty(r)) {
133
+ throw new Error('Component "' + this.describe + '" wants to listen on "' + r + '" but no such attribute was defined.');
134
+ }
135
+ rules[this.attr[r]] = ruleInfo[r];
136
+ },
137
+ this
138
+ );
139
+
140
+ return rules;
141
+ };
142
+
143
+ this.defaultAttrs = function(defaults) {
144
+ utils.push(this.defaults, defaults, true) || (this.defaults = defaults);
145
+ };
146
+
147
+ this.select = function(attributeKey) {
148
+ return this.$node.find(this.attr[attributeKey]);
149
+ };
150
+
151
+ this.initialize = $.noop;
152
+ this.teardown = teardown;
153
+ }
154
+
155
+ function attachTo(selector/*, options args */) {
156
+ if (!selector) {
157
+ throw new Error("Component needs to be attachTo'd a jQuery object, native node or selector string");
158
+ }
159
+
160
+ var options = utils.merge.apply(utils, utils.toArray(arguments, 1));
161
+
162
+ $(selector).each(function(i, node) {
163
+ new this(node, options);
164
+ }.bind(this));
165
+ }
166
+
167
+ // define the constructor for a custom component type
168
+ // takes an unlimited number of mixin functions as arguments
169
+ // typical api call with 3 mixins: define(timeline, withTweetCapability, withScrollCapability);
170
+ function define(/*mixins*/) {
171
+ var mixins = utils.toArray(arguments);
172
+
173
+ Component.toString = function() {
174
+ var prettyPrintMixins = mixins.map(function(mixin) {
175
+ if ($.browser.msie) {
176
+ var m = mixin.toString().match(/function (.*?)\s?\(/);
177
+ return (m && m[1]) ? m[1] : "";
178
+ } else {
179
+ return mixin.name;
180
+ }
181
+ }).join(', ').replace(/\s\,/g,'');//weed out no-named mixins
182
+
183
+ return prettyPrintMixins;
184
+ };
185
+
186
+ Component.describe = Component.toString();
187
+
188
+ //'options' is optional hash to be merged with 'defaults' in the component definition
189
+ function Component(node, options) {
190
+ var fnCache = {}, uuid = 0;
191
+
192
+ if (!node) {
193
+ throw new Error("Component needs a node");
194
+ }
195
+
196
+ if (node.jquery) {
197
+ this.node = node[0];
198
+ this.$node = node;
199
+ } else {
200
+ this.node = node;
201
+ this.$node = $(node);
202
+ }
203
+
204
+ this.describe = this.constructor.describe;
205
+
206
+ this.bind = function(func) {
207
+ var bound;
208
+
209
+ if (func.uuid && (bound = fnCache[func.uuid])) {
210
+ return bound;
211
+ }
212
+
213
+ var bindArgs = utils.toArray(arguments, 1);
214
+ bindArgs.unshift(this); //prepend context
215
+
216
+ bound = func.bind.apply(func, bindArgs);
217
+ bound.target = func;
218
+ func.uuid = uuid++;
219
+ fnCache[func.uuid] = bound;
220
+
221
+ return bound;
222
+ };
223
+
224
+ //merge defaults with supplied options
225
+ this.attr = utils.merge(this.defaults, options);
226
+ this.defaults && Object.keys(this.defaults).forEach(function(key) {
227
+ if (this.defaults[key] === null && this.attr[key] === null) {
228
+ throw new Error('Required attribute "' + key + '" not specified in attachTo for component "' + this.describe + '".');
229
+ }
230
+ }, this);
231
+
232
+ this.initialize.call(this, options || {});
233
+
234
+ this.trigger('componentInitialized');
235
+ }
236
+
237
+ Component.attachTo = attachTo;
238
+ Component.teardownAll = teardownAll;
239
+
240
+ // prepend common mixins to supplied list, then mixin all flavors
241
+ mixins.unshift(withBaseComponent, advice.withAdvice, registry.withRegistration);
242
+
243
+ compose.mixin(Component.prototype, mixins);
244
+
245
+ return Component;
246
+ }
247
+
248
+ define.teardownAll = function() {
249
+ registry.components.slice().forEach(function(c) {
250
+ c.component.teardownAll();
251
+ });
252
+ registry.reset();
253
+ };
254
+
255
+ return define;
256
+ }
257
+ );
@@ -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,7 @@
1
+ //= require advice
2
+ //= require component
3
+ //= require compose
4
+ //= require index
5
+ //= require logger
6
+ //= require registry
7
+ //= require utils
@@ -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
+ );
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flight-rails
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Viktor Tomilin
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-02 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: ! 'Flight: an event driven component framework'
15
+ email:
16
+ - caballerosolar@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - Gemfile
23
+ - LICENSE.txt
24
+ - README.md
25
+ - Rakefile
26
+ - flight-rails.gemspec
27
+ - lib/flight-rails.rb
28
+ - lib/flight-rails/engine.rb
29
+ - lib/flight-rails/version.rb
30
+ - vendor/assets/javascript/advice.js
31
+ - vendor/assets/javascript/component.js
32
+ - vendor/assets/javascript/compose.js
33
+ - vendor/assets/javascript/flight.js
34
+ - vendor/assets/javascript/index.js
35
+ - vendor/assets/javascript/logger.js
36
+ - vendor/assets/javascript/registry.js
37
+ - vendor/assets/javascript/utils.js
38
+ homepage: http://twitter.github.com/flight/
39
+ licenses: []
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ! '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubyforge_project:
58
+ rubygems_version: 1.8.24
59
+ signing_key:
60
+ specification_version: 3
61
+ summary: ! 'Flight: an event driven component framework'
62
+ test_files: []