13 changed files with 0 additions and 1630 deletions
@ -1,33 +0,0 @@
@@ -1,33 +0,0 @@
|
||||
{ |
||||
"name": "flight", |
||||
"description": "Clientside component infrastructure", |
||||
"main": "lib/index.js", |
||||
"version": "1.1.4", |
||||
"ignore": [ |
||||
"doc", |
||||
"tools", |
||||
"test", |
||||
".gitignore", |
||||
".travis.yml", |
||||
"CHANGELOG.md", |
||||
"CONTRIBUTING.md", |
||||
"Makefile", |
||||
"karma.conf.js", |
||||
"package.json" |
||||
], |
||||
"dependencies": { |
||||
"es5-shim": "2.0.0", |
||||
"jquery": ">=1.8.0" |
||||
}, |
||||
"homepage": "https://github.com/flightjs/flight", |
||||
"_release": "1.1.4", |
||||
"_resolution": { |
||||
"type": "version", |
||||
"tag": "v1.1.4", |
||||
"commit": "5da831ba9026330b692ecf5668cad8832f1f19cd" |
||||
}, |
||||
"_source": "git://github.com/flightjs/flight.git", |
||||
"_target": "~1.1.4", |
||||
"_originalSource": "flight", |
||||
"_direct": true |
||||
} |
||||
@ -1,19 +0,0 @@
@@ -1,19 +0,0 @@
|
||||
Copyright (c) 2013-2014 Twitter, Inc and others |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in |
||||
all copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||
THE SOFTWARE. |
||||
@ -1,242 +0,0 @@
@@ -1,242 +0,0 @@
|
||||
# Flight |
||||
|
||||
[](http://travis-ci.org/flightjs/flight) |
||||
|
||||
[Flight](http://flightjs.github.io/) is a lightweight, component-based, |
||||
event-driven JavaScript framework that maps behavior to DOM nodes. It was |
||||
created at Twitter, and is used by the [twitter.com](https://twitter.com/) and |
||||
[TweetDeck](https://web.tweetdeck.com/) web applications. |
||||
|
||||
* [Website](http://flightjs.github.io/) |
||||
* [API documentation](doc/README.md) |
||||
* [Flight example app](http://flightjs.github.io/example-app/) ([Source](https://github.com/flightjs/example-app)) |
||||
* [Flight's Google Group](https://groups.google.com/forum/?fromgroups#!forum/twitter-flight) |
||||
* [Flight on Twitter](https://twitter.com/flight) |
||||
|
||||
|
||||
## Why Flight? |
||||
|
||||
Flight is only ~5K minified and gzipped. It's built upon jQuery, and has |
||||
first-class support for Asynchronous Module Definition (AMD) and [Bower](http://bower.io/). |
||||
|
||||
Flight components are highly portable and easily testable. This is because a |
||||
Flight component (and its API) is entirely decoupled from other components. |
||||
Flight components communicate only by triggering and subscribing to events. |
||||
|
||||
Flight also includes a simple and safe |
||||
[mixin](https://javascriptweblog.wordpress.com/2011/05/31/a-fresh-look-at-javascript-mixins/) |
||||
infrastructure, allowing components to be easily extended with minimal |
||||
boilerplate. |
||||
|
||||
|
||||
## Development tools |
||||
|
||||
Flight has supporting projects that provide everything you need to setup, |
||||
write, and test your application. |
||||
|
||||
* [Flight generator](https://github.com/flightjs/generator-flight/) |
||||
Recommended. One-step to setup everything you need to work with Flight. |
||||
|
||||
* [Flight package generator](https://github.com/flightjs/generator-flight-package/) |
||||
Recommended. One-step to setup everything you need to write and test a |
||||
standalone Flight component. |
||||
|
||||
* [Jasmine Flight](https://github.com/flightjs/jasmine-flight/) |
||||
Extensions for the Jasmine test framework. |
||||
|
||||
* [Mocha Flight](https://github.com/flightjs/mocha-flight/) |
||||
Extensions for the Mocha test framework. |
||||
|
||||
|
||||
## Finding and writing standalone components |
||||
|
||||
You can browse all the [Flight components](http://flight-components.jit.su) |
||||
available at this time. They can also be found by searching the Bower registry: |
||||
|
||||
``` |
||||
bower search flight |
||||
``` |
||||
|
||||
The easiest way to write a standalone Flight component is to use the [Flight |
||||
package generator](https://github.com/flightjs/generator-flight-package/): |
||||
|
||||
``` |
||||
yo flight-package foo |
||||
``` |
||||
|
||||
|
||||
## Installation |
||||
|
||||
If you prefer not to use the Flight generators, it's highly recommended that |
||||
you install Flight as an AMD package (including all the correct dependencies). |
||||
This is best done with [Bower](http://bower.io/), a package manager for the web. |
||||
|
||||
``` |
||||
npm install -g bower |
||||
bower install --save flight |
||||
``` |
||||
|
||||
You will have to reference Flight's installed dependencies – |
||||
[ES5-shim](https://github.com/kriskowal/es5-shim) and |
||||
[jQuery](http://jquery.com) – and use an AMD module loader like |
||||
[Require.js](http://requirejs.org/) or |
||||
[Loadrunner](https://github.com/danwrong/loadrunner). |
||||
|
||||
```html |
||||
<script src="bower_components/es5-shim/es5-shim.js"></script> |
||||
<script src="bower_components/es5-shim/es5-sham.js"></script> |
||||
<script src="bower_components/jquery/dist/jquery.js"></script> |
||||
<script data-main="main.js" src="bower_components/requirejs/require.js"></script> |
||||
... |
||||
``` |
||||
|
||||
## Standalone version |
||||
|
||||
Alternatively, you can manually install the [standalone |
||||
version](http://flightjs.github.io/release/latest/flight.js) of Flight, also |
||||
available on [cdnjs](http://cdnjs.com/). It exposes all of its modules as |
||||
properties of a global variable, `flight`: |
||||
|
||||
```html |
||||
... |
||||
<script src="flight.js"></script> |
||||
<script> |
||||
var MyComponent = flight.component(function() { |
||||
//... |
||||
}); |
||||
</script> |
||||
``` |
||||
|
||||
N.B. You will also need to manually install the correct versions of Flight's |
||||
dependencies: ES5 Shim and jQuery. |
||||
|
||||
## Browser Support |
||||
|
||||
Chrome, Firefox, Safari, Opera, IE 7+. |
||||
|
||||
## Quick Overview |
||||
|
||||
Here's a brief introduction to Flight's key concepts and syntax. Read the [API |
||||
documentation](doc) for a comprehensive overview. |
||||
|
||||
### Example |
||||
|
||||
A simple example of how to write and use a Flight component. |
||||
|
||||
```js |
||||
define(function (require) { |
||||
var defineComponent = require('flight/lib/component'); |
||||
|
||||
// define the component |
||||
return defineComponent(inbox); |
||||
|
||||
function inbox() { |
||||
// define custom functions here |
||||
this.doSomething = function() { |
||||
//... |
||||
} |
||||
|
||||
this.doSomethingElse = function() { |
||||
//... |
||||
} |
||||
|
||||
// now initialize the component |
||||
this.after('initialize', function() { |
||||
this.on('click', this.doSomething); |
||||
this.on('mouseover', this.doSomethingElse); |
||||
}); |
||||
} |
||||
}); |
||||
``` |
||||
|
||||
```js |
||||
/* attach an inbox component to a node with id 'inbox' */ |
||||
|
||||
define(function (require) { |
||||
var Inbox = require('inbox'); |
||||
|
||||
Inbox.attachTo('#inbox', { |
||||
'nextPageSelector': '#nextPage', |
||||
'previousPageSelector': '#previousPage', |
||||
}); |
||||
}); |
||||
``` |
||||
|
||||
### Components ([API](doc/component_api.md)) |
||||
|
||||
- A Component is nothing more than a constructor with properties mixed into its prototype. |
||||
- Every Component comes with a set of basic functionality such as event handling and component registration. |
||||
(see [Base API](doc/base_api.md)) |
||||
- Additionally, each Component definition mixes in a set of custom properties which describe its behavior. |
||||
- When a component is attached to a DOM node, a new instance of that component is created. Each component |
||||
instance references the DOM node via its `node` property. |
||||
- Component instances cannot be referenced directly; they communicate with other components via events. |
||||
|
||||
### Interacting with the DOM |
||||
|
||||
Once attached, component instances have direct access to their node object via the `node` property. (There's |
||||
also a jQuery version of the node available via the `$node` property.) |
||||
|
||||
### Events in Flight |
||||
|
||||
Events are how Flight components interact. The Component prototype supplies methods for triggering events as |
||||
well as for subscribing to and unsubscribing from events. These Component event methods are actually just convenient |
||||
wrappers around regular event methods on DOM nodes. |
||||
|
||||
### Mixins ([API](doc/mixin_api.md)) |
||||
|
||||
- In Flight, a mixin is a function which assigns properties to a target object (represented by the `this` |
||||
keyword). |
||||
- A typical mixin defines a set of functionality that will be useful to more than one component. |
||||
- One mixin can be applied to any number of [Component](#components) definitions. |
||||
- One Component definition can have any number of mixins applied to it. |
||||
- Each Component defines a [*core*](#core_mixin) mixin within its own module. |
||||
- A mixin can itself have mixins applied to it. |
||||
|
||||
### Advice ([API](doc/advice_api.md)) |
||||
|
||||
In Flight, advice is a mixin (`'lib/advice.js'`) that defines `before`, `after` and `around` methods. |
||||
|
||||
These can be used to modify existing functions by adding custom code. All Components have advice mixed in to |
||||
their prototype so that mixins can augment existing functions without requiring knowledge |
||||
of the original implementation. Moreover, since Component's are seeded with an empty `initialize` method, |
||||
Component definitions will typically use `after` to define custom `initialize` behavior. |
||||
|
||||
### Debugging ([API](doc/debug_api.md)) |
||||
|
||||
Flight ships with a debug module which can help you trace the sequence of event triggering and binding. By default |
||||
console logging is turned off, but you can you can log `trigger`, `on` and `off` events by means of the following console |
||||
commands. |
||||
|
||||
## Authors |
||||
|
||||
+ [@angus-c](http://github.com/angus-c) |
||||
+ [@danwrong](http://github.com/danwrong) |
||||
+ [@kpk](http://github.com/kennethkufluk) |
||||
|
||||
Thanks for assistance and contributions: |
||||
[@sayrer](https://github.com/sayrer), |
||||
[@shinypb](https://github.com/shinypb), |
||||
[@kloots](https://github.com/kloots), |
||||
[@marcelduran](https://github.com/marcelduran), |
||||
[@tbrd](https://github.com/tbrd), |
||||
[@necolas](https://github.com/necolas), |
||||
[@fat](https://github.com/fat), |
||||
[@mkuklis](https://github.com/mkuklis), |
||||
[@jrburke](https://github.com/jrburke), |
||||
[@garann](https://github.com/garann), |
||||
[@WebReflection](https://github.com/WebReflection), |
||||
[@coldhead](https://github.com/coldhead), |
||||
[@paulirish](https://github.com/paulirish), |
||||
[@nimbupani](https://github.com/nimbupani), |
||||
[@mootcycle](https://github.com/mootcycle). |
||||
|
||||
Special thanks to the rest of the Twitter web team for their abundant |
||||
contributions and feedback. |
||||
|
||||
|
||||
## License |
||||
|
||||
Copyright 2013 Twitter, Inc and other contributors. |
||||
|
||||
Licensed under the MIT License |
||||
@ -1,22 +0,0 @@
@@ -1,22 +0,0 @@
|
||||
{ |
||||
"name": "flight", |
||||
"description": "Clientside component infrastructure", |
||||
"main": "lib/index.js", |
||||
"version": "1.1.4", |
||||
"ignore": [ |
||||
"doc", |
||||
"tools", |
||||
"test", |
||||
".gitignore", |
||||
".travis.yml", |
||||
"CHANGELOG.md", |
||||
"CONTRIBUTING.md", |
||||
"Makefile", |
||||
"karma.conf.js", |
||||
"package.json" |
||||
], |
||||
"dependencies": { |
||||
"es5-shim": "2.0.0", |
||||
"jquery": ">=1.8.0" |
||||
} |
||||
} |
||||
@ -1,69 +0,0 @@
@@ -1,69 +0,0 @@
|
||||
// ==========================================
|
||||
// Copyright 2013 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
|
||||
define( |
||||
|
||||
[ |
||||
'./compose' |
||||
], |
||||
|
||||
function(compose) { |
||||
'use strict'; |
||||
|
||||
var advice = { |
||||
|
||||
around: function(base, wrapped) { |
||||
return function composedAround() { |
||||
// unpacking arguments by hand benchmarked faster
|
||||
var i = 0, l = arguments.length, args = new Array(l + 1); |
||||
args[0] = base.bind(this); |
||||
for (; i < l; i++) args[i + 1] = arguments[i]; |
||||
|
||||
return wrapped.apply(this, args); |
||||
}; |
||||
}, |
||||
|
||||
before: function(base, before) { |
||||
var beforeFn = (typeof before == 'function') ? before : before.obj[before.fnName]; |
||||
return function composedBefore() { |
||||
beforeFn.apply(this, arguments); |
||||
return base.apply(this, arguments); |
||||
}; |
||||
}, |
||||
|
||||
after: function(base, after) { |
||||
var afterFn = (typeof after == 'function') ? after : after.obj[after.fnName]; |
||||
return function composedAfter() { |
||||
var res = (base.unbound || base).apply(this, arguments); |
||||
afterFn.apply(this, arguments); |
||||
return res; |
||||
}; |
||||
}, |
||||
|
||||
// a mixin that allows other mixins to augment existing functions by adding additional
|
||||
// code before, after or around.
|
||||
withAdvice: function() { |
||||
['before', 'after', 'around'].forEach(function(m) { |
||||
this[m] = function(method, fn) { |
||||
|
||||
compose.unlockProperty(this, method, function() { |
||||
if (typeof this[method] == 'function') { |
||||
this[method] = advice[m](this[method], fn); |
||||
} else { |
||||
this[method] = fn; |
||||
} |
||||
|
||||
return this[method]; |
||||
}); |
||||
|
||||
}; |
||||
}, this); |
||||
} |
||||
}; |
||||
|
||||
return advice; |
||||
} |
||||
); |
||||
@ -1,237 +0,0 @@
@@ -1,237 +0,0 @@
|
||||
// ==========================================
|
||||
// Copyright 2013 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
|
||||
define( |
||||
|
||||
[ |
||||
'./utils', |
||||
'./registry', |
||||
'./debug' |
||||
], |
||||
|
||||
function(utils, registry, debug) { |
||||
'use strict'; |
||||
|
||||
// common mixin allocates basic functionality - used by all component prototypes
|
||||
// callback context is bound to component
|
||||
var componentId = 0; |
||||
|
||||
function teardownInstance(instanceInfo){ |
||||
instanceInfo.events.slice().forEach(function(event) { |
||||
var args = [event.type]; |
||||
|
||||
event.element && args.unshift(event.element); |
||||
(typeof event.callback == 'function') && args.push(event.callback); |
||||
|
||||
this.off.apply(this, args); |
||||
}, instanceInfo.instance); |
||||
} |
||||
|
||||
function checkSerializable(type, data) { |
||||
try { |
||||
window.postMessage(data, '*'); |
||||
} catch(e) { |
||||
console.log('unserializable data for event',type,':',data); |
||||
throw new Error( |
||||
['The event', type, 'on component', this.toString(), 'was triggered with non-serializable data'].join(' ') |
||||
); |
||||
} |
||||
} |
||||
|
||||
function proxyEventTo(targetEvent) { |
||||
return function(e, data) { |
||||
$(e.target).trigger(targetEvent, data); |
||||
}; |
||||
} |
||||
|
||||
function withBase() { |
||||
|
||||
// delegate trigger, bind and unbind to an element
|
||||
// if $element not supplied, use component's node
|
||||
// other arguments are passed on
|
||||
// event can be either a string specifying the type
|
||||
// of the event, or a hash specifying both the type
|
||||
// and a default function to be called.
|
||||
this.trigger = function() { |
||||
var $element, type, data, event, defaultFn; |
||||
var lastIndex = arguments.length - 1, lastArg = arguments[lastIndex]; |
||||
|
||||
if (typeof lastArg != 'string' && !(lastArg && lastArg.defaultBehavior)) { |
||||
lastIndex--; |
||||
data = lastArg; |
||||
} |
||||
|
||||
if (lastIndex == 1) { |
||||
$element = $(arguments[0]); |
||||
event = arguments[1]; |
||||
} else { |
||||
$element = this.$node; |
||||
event = arguments[0]; |
||||
} |
||||
|
||||
if (event.defaultBehavior) { |
||||
defaultFn = event.defaultBehavior; |
||||
event = $.Event(event.type); |
||||
} |
||||
|
||||
type = event.type || event; |
||||
|
||||
if (debug.enabled && window.postMessage) { |
||||
checkSerializable.call(this, type, data); |
||||
} |
||||
|
||||
if (typeof this.attr.eventData === 'object') { |
||||
data = $.extend(true, {}, this.attr.eventData, data); |
||||
} |
||||
|
||||
$element.trigger((event || type), data); |
||||
|
||||
if (defaultFn && !event.isDefaultPrevented()) { |
||||
(this[defaultFn] || defaultFn).call(this); |
||||
} |
||||
|
||||
return $element; |
||||
}; |
||||
|
||||
this.on = function() { |
||||
var $element, type, callback, originalCb; |
||||
var lastIndex = arguments.length - 1, origin = arguments[lastIndex]; |
||||
|
||||
if (typeof origin == 'object') { |
||||
//delegate callback
|
||||
originalCb = utils.delegate( |
||||
this.resolveDelegateRules(origin) |
||||
); |
||||
} else if (typeof origin == 'string') { |
||||
originalCb = proxyEventTo(origin); |
||||
} else { |
||||
originalCb = origin; |
||||
} |
||||
|
||||
if (lastIndex == 2) { |
||||
$element = $(arguments[0]); |
||||
type = arguments[1]; |
||||
} else { |
||||
$element = this.$node; |
||||
type = arguments[0]; |
||||
} |
||||
|
||||
if (typeof originalCb != 'function' && typeof originalCb != 'object') { |
||||
throw new Error('Unable to bind to "' + type + '" because the given callback is not a function or an object'); |
||||
} |
||||
|
||||
callback = originalCb.bind(this); |
||||
callback.target = originalCb; |
||||
callback.context = this; |
||||
|
||||
$element.on(type, callback); |
||||
|
||||
// store every bound version of the callback
|
||||
originalCb.bound || (originalCb.bound = []); |
||||
originalCb.bound.push(callback); |
||||
|
||||
return callback; |
||||
}; |
||||
|
||||
this.off = function() { |
||||
var $element, type, callback; |
||||
var lastIndex = arguments.length - 1; |
||||
|
||||
if (typeof arguments[lastIndex] == 'function') { |
||||
callback = arguments[lastIndex]; |
||||
lastIndex -= 1; |
||||
} |
||||
|
||||
if (lastIndex == 1) { |
||||
$element = $(arguments[0]); |
||||
type = arguments[1]; |
||||
} else { |
||||
$element = this.$node; |
||||
type = arguments[0]; |
||||
} |
||||
|
||||
if (callback) { |
||||
//this callback may be the original function or a bound version
|
||||
var boundFunctions = callback.target ? callback.target.bound : callback.bound || []; |
||||
//set callback to version bound against this instance
|
||||
boundFunctions && boundFunctions.some(function(fn, i, arr) { |
||||
if (fn.context && (this.identity == fn.context.identity)) { |
||||
arr.splice(i, 1); |
||||
callback = fn; |
||||
return true; |
||||
} |
||||
}, this); |
||||
} |
||||
|
||||
return $element.off(type, callback); |
||||
}; |
||||
|
||||
this.resolveDelegateRules = function(ruleInfo) { |
||||
var rules = {}; |
||||
|
||||
Object.keys(ruleInfo).forEach(function(r) { |
||||
if (!(r in this.attr)) { |
||||
throw new Error('Component "' + this.toString() + '" wants to listen on "' + r + '" but no such attribute was defined.'); |
||||
} |
||||
rules[this.attr[r]] = (typeof ruleInfo[r] == 'string') ? proxyEventTo(ruleInfo[r]) : ruleInfo[r]; |
||||
}, this); |
||||
|
||||
return rules; |
||||
}; |
||||
|
||||
this.defaultAttrs = function(defaults) { |
||||
utils.push(this.defaults, defaults, true) || (this.defaults = defaults); |
||||
}; |
||||
|
||||
this.select = function(attributeKey) { |
||||
return this.$node.find(this.attr[attributeKey]); |
||||
}; |
||||
|
||||
this.initialize = function(node, attrs) { |
||||
attrs || (attrs = {}); |
||||
//only assign identity if there isn't one (initialize can be called multiple times)
|
||||
this.identity || (this.identity = componentId++); |
||||
|
||||
if (!node) { |
||||
throw new Error('Component needs a node'); |
||||
} |
||||
|
||||
if (node.jquery) { |
||||
this.node = node[0]; |
||||
this.$node = node; |
||||
} else { |
||||
this.node = node; |
||||
this.$node = $(node); |
||||
} |
||||
|
||||
// merge defaults with supplied options
|
||||
// put options in attr.__proto__ to avoid merge overhead
|
||||
var attr = Object.create(attrs); |
||||
for (var key in this.defaults) { |
||||
if (!attrs.hasOwnProperty(key)) { |
||||
attr[key] = this.defaults[key]; |
||||
} |
||||
} |
||||
|
||||
this.attr = attr; |
||||
|
||||
Object.keys(this.defaults || {}).forEach(function(key) { |
||||
if (this.defaults[key] === null && this.attr[key] === null) { |
||||
throw new Error('Required attribute "' + key + '" not specified in attachTo for component "' + this.toString() + '".'); |
||||
} |
||||
}, this); |
||||
|
||||
return this; |
||||
}; |
||||
|
||||
this.teardown = function() { |
||||
teardownInstance(registry.findInstanceInfo(this)); |
||||
}; |
||||
} |
||||
|
||||
return withBase; |
||||
} |
||||
); |
||||
@ -1,136 +0,0 @@
@@ -1,136 +0,0 @@
|
||||
// ==========================================
|
||||
// Copyright 2013 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
|
||||
define( |
||||
|
||||
[ |
||||
'./advice', |
||||
'./utils', |
||||
'./compose', |
||||
'./base', |
||||
'./registry', |
||||
'./logger', |
||||
'./debug' |
||||
], |
||||
|
||||
function(advice, utils, compose, withBase, registry, withLogging, debug) { |
||||
'use strict'; |
||||
|
||||
var functionNameRegEx = /function (.*?)\s?\(/; |
||||
|
||||
// teardown for all instances of this constructor
|
||||
function teardownAll() { |
||||
var componentInfo = registry.findComponentInfo(this); |
||||
|
||||
componentInfo && Object.keys(componentInfo.instances).forEach(function(k) { |
||||
var info = componentInfo.instances[k]; |
||||
// It's possible that a previous teardown caused another component to teardown,
|
||||
// so we can't assume that the instances object is as it was.
|
||||
if (info && info.instance) { |
||||
info.instance.teardown(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
function checkSerializable(type, data) { |
||||
try { |
||||
window.postMessage(data, '*'); |
||||
} catch(e) { |
||||
console.log('unserializable data for event',type,':',data); |
||||
throw new Error( |
||||
['The event', type, 'on component', this.toString(), 'was triggered with non-serializable data'].join(' ') |
||||
); |
||||
} |
||||
} |
||||
|
||||
function attachTo(selector/*, options args */) { |
||||
// unpacking arguments by hand benchmarked faster
|
||||
var l = arguments.length; |
||||
var args = new Array(l - 1); |
||||
for (var i = 1; i < l; i++) args[i - 1] = arguments[i]; |
||||
|
||||
if (!selector) { |
||||
throw new Error('Component needs to be attachTo\'d a jQuery object, native node or selector string'); |
||||
} |
||||
|
||||
var options = utils.merge.apply(utils, args); |
||||
var componentInfo = registry.findComponentInfo(this); |
||||
|
||||
$(selector).each(function(i, node) { |
||||
if (componentInfo && componentInfo.isAttachedTo(node)) { |
||||
// already attached
|
||||
return; |
||||
} |
||||
|
||||
(new this).initialize(node, options); |
||||
}.bind(this)); |
||||
} |
||||
|
||||
function prettyPrintMixins() { |
||||
//could be called from constructor or constructor.prototype
|
||||
var mixedIn = this.mixedIn || this.prototype.mixedIn || []; |
||||
return mixedIn.map(function(mixin) { |
||||
if (mixin.name == null) { |
||||
// function name property not supported by this browser, use regex
|
||||
var m = mixin.toString().match(functionNameRegEx); |
||||
return (m && m[1]) ? m[1] : ''; |
||||
} else { |
||||
return (mixin.name != 'withBase') ? mixin.name : ''; |
||||
} |
||||
}).filter(Boolean).join(', '); |
||||
}; |
||||
|
||||
|
||||
// define the constructor for a custom component type
|
||||
// takes an unlimited number of mixin functions as arguments
|
||||
// typical api call with 3 mixins: define(timeline, withTweetCapability, withScrollCapability);
|
||||
function define(/*mixins*/) { |
||||
// unpacking arguments by hand benchmarked faster
|
||||
var l = arguments.length; |
||||
var mixins = new Array(l); |
||||
for (var i = 0; i < l; i++) mixins[i] = arguments[i]; |
||||
|
||||
var Component = function() {}; |
||||
|
||||
Component.toString = Component.prototype.toString = prettyPrintMixins; |
||||
if (debug.enabled) { |
||||
Component.describe = Component.prototype.describe = Component.toString(); |
||||
} |
||||
|
||||
// 'options' is optional hash to be merged with 'defaults' in the component definition
|
||||
Component.attachTo = attachTo; |
||||
// enables extension of existing "base" Components
|
||||
Component.mixin = function() { |
||||
var newComponent = define(); //TODO: fix pretty print
|
||||
var newPrototype = Object.create(Component.prototype); |
||||
newPrototype.mixedIn = [].concat(Component.prototype.mixedIn); |
||||
compose.mixin(newPrototype, arguments); |
||||
newComponent.prototype = newPrototype; |
||||
newComponent.prototype.constructor = newComponent; |
||||
return newComponent; |
||||
}; |
||||
Component.teardownAll = teardownAll; |
||||
|
||||
// prepend common mixins to supplied list, then mixin all flavors
|
||||
if (debug.enabled) { |
||||
mixins.unshift(withLogging); |
||||
} |
||||
mixins.unshift(withBase, advice.withAdvice, registry.withRegistration); |
||||
compose.mixin(Component.prototype, mixins); |
||||
|
||||
return Component; |
||||
} |
||||
|
||||
define.teardownAll = function() { |
||||
registry.components.slice().forEach(function(c) { |
||||
c.component.teardownAll(); |
||||
}); |
||||
registry.reset(); |
||||
}; |
||||
|
||||
return define; |
||||
} |
||||
); |
||||
@ -1,85 +0,0 @@
@@ -1,85 +0,0 @@
|
||||
// ==========================================
|
||||
// Copyright 2013 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
|
||||
define( |
||||
|
||||
[ |
||||
'./utils', |
||||
'./debug' |
||||
], |
||||
|
||||
function(utils, debug) { |
||||
'use strict'; |
||||
|
||||
//enumerables are shims - getOwnPropertyDescriptor shim doesn't work
|
||||
var canWriteProtect = debug.enabled && !utils.isEnumerable(Object, 'getOwnPropertyDescriptor'); |
||||
//whitelist of unlockable property names
|
||||
var dontLock = ['mixedIn']; |
||||
|
||||
if (canWriteProtect) { |
||||
//IE8 getOwnPropertyDescriptor is built-in but throws exeption on non DOM objects
|
||||
try { |
||||
Object.getOwnPropertyDescriptor(Object, 'keys'); |
||||
} catch(e) { |
||||
canWriteProtect = false; |
||||
} |
||||
} |
||||
|
||||
function setPropertyWritability(obj, isWritable) { |
||||
if (!canWriteProtect) { |
||||
return; |
||||
} |
||||
|
||||
var props = Object.create(null); |
||||
|
||||
Object.keys(obj).forEach( |
||||
function (key) { |
||||
if (dontLock.indexOf(key) < 0) { |
||||
var desc = Object.getOwnPropertyDescriptor(obj, key); |
||||
desc.writable = isWritable; |
||||
props[key] = desc; |
||||
} |
||||
} |
||||
); |
||||
|
||||
Object.defineProperties(obj, props); |
||||
} |
||||
|
||||
function unlockProperty(obj, prop, op) { |
||||
var writable; |
||||
|
||||
if (!canWriteProtect || !obj.hasOwnProperty(prop)) { |
||||
op.call(obj); |
||||
return; |
||||
} |
||||
|
||||
writable = Object.getOwnPropertyDescriptor(obj, prop).writable; |
||||
Object.defineProperty(obj, prop, { writable: true }); |
||||
op.call(obj); |
||||
Object.defineProperty(obj, prop, { writable: writable }); |
||||
} |
||||
|
||||
function mixin(base, mixins) { |
||||
base.mixedIn = base.hasOwnProperty('mixedIn') ? base.mixedIn : []; |
||||
|
||||
for (var i=0; i<mixins.length; i++) { |
||||
if (base.mixedIn.indexOf(mixins[i]) == -1) { |
||||
setPropertyWritability(base, false); |
||||
mixins[i].call(base); |
||||
base.mixedIn.push(mixins[i]); |
||||
} |
||||
} |
||||
|
||||
setPropertyWritability(base, true); |
||||
} |
||||
|
||||
return { |
||||
mixin: mixin, |
||||
unlockProperty: unlockProperty |
||||
}; |
||||
|
||||
} |
||||
); |
||||
@ -1,165 +0,0 @@
@@ -1,165 +0,0 @@
|
||||
// ==========================================
|
||||
// Copyright 2013 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
|
||||
define( |
||||
|
||||
[], |
||||
|
||||
function() { |
||||
'use strict'; |
||||
|
||||
// ==========================================
|
||||
// Search object model
|
||||
// ==========================================
|
||||
|
||||
function traverse(util, searchTerm, options) { |
||||
options = options || {}; |
||||
var obj = options.obj || window; |
||||
var path = options.path || ((obj==window) ? 'window' : ''); |
||||
var props = Object.keys(obj); |
||||
props.forEach(function(prop) { |
||||
if ((tests[util] || util)(searchTerm, obj, prop)){ |
||||
console.log([path, '.', prop].join(''), '->', ['(', typeof obj[prop], ')'].join(''), obj[prop]); |
||||
} |
||||
if (Object.prototype.toString.call(obj[prop]) == '[object Object]' && (obj[prop] != obj) && path.split('.').indexOf(prop) == -1) { |
||||
traverse(util, searchTerm, {obj: obj[prop], path: [path,prop].join('.')}); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
function search(util, expected, searchTerm, options) { |
||||
if (!expected || typeof searchTerm == expected) { |
||||
traverse(util, searchTerm, options); |
||||
} else { |
||||
console.error([searchTerm, 'must be', expected].join(' ')); |
||||
} |
||||
} |
||||
|
||||
var tests = { |
||||
'name': function(searchTerm, obj, prop) {return searchTerm == prop;}, |
||||
'nameContains': function(searchTerm, obj, prop) {return prop.indexOf(searchTerm) > -1;}, |
||||
'type': function(searchTerm, obj, prop) {return obj[prop] instanceof searchTerm;}, |
||||
'value': function(searchTerm, obj, prop) {return obj[prop] === searchTerm;}, |
||||
'valueCoerced': function(searchTerm, obj, prop) {return obj[prop] == searchTerm;} |
||||
}; |
||||
|
||||
function byName(searchTerm, options) {search('name', 'string', searchTerm, options);} |
||||
function byNameContains(searchTerm, options) {search('nameContains', 'string', searchTerm, options);} |
||||
function byType(searchTerm, options) {search('type', 'function', searchTerm, options);} |
||||
function byValue(searchTerm, options) {search('value', null, searchTerm, options);} |
||||
function byValueCoerced(searchTerm, options) {search('valueCoerced', null, searchTerm, options);} |
||||
function custom(fn, options) {traverse(fn, null, options);} |
||||
|
||||
// ==========================================
|
||||
// Event logging
|
||||
// ==========================================
|
||||
|
||||
var ALL = 'all'; //no filter
|
||||
|
||||
//log nothing by default
|
||||
var logFilter = { |
||||
eventNames: [], |
||||
actions: [] |
||||
} |
||||
|
||||
function filterEventLogsByAction(/*actions*/) { |
||||
var actions = [].slice.call(arguments); |
||||
|
||||
logFilter.eventNames.length || (logFilter.eventNames = ALL); |
||||
logFilter.actions = actions.length ? actions : ALL; |
||||
saveLogFilter(); |
||||
} |
||||
|
||||
function filterEventLogsByName(/*eventNames*/) { |
||||
var eventNames = [].slice.call(arguments); |
||||
|
||||
logFilter.actions.length || (logFilter.actions = ALL); |
||||
logFilter.eventNames = eventNames.length ? eventNames : ALL; |
||||
saveLogFilter(); |
||||
} |
||||
|
||||
function hideAllEventLogs() { |
||||
logFilter.actions = []; |
||||
logFilter.eventNames = []; |
||||
saveLogFilter(); |
||||
} |
||||
|
||||
function showAllEventLogs() { |
||||
logFilter.actions = ALL; |
||||
logFilter.eventNames = ALL; |
||||
saveLogFilter(); |
||||
} |
||||
|
||||
function saveLogFilter() { |
||||
try { |
||||
if (window.localStorage) { |
||||
localStorage.setItem('logFilter_eventNames', logFilter.eventNames); |
||||
localStorage.setItem('logFilter_actions', logFilter.actions); |
||||
} |
||||
} catch (ignored) {}; |
||||
} |
||||
|
||||
function retrieveLogFilter() { |
||||
var eventNames, actions; |
||||
try { |
||||
eventNames = (window.localStorage && localStorage.getItem('logFilter_eventNames')); |
||||
actions = (window.localStorage && localStorage.getItem('logFilter_actions')); |
||||
} catch(ignored) { |
||||
return; |
||||
} |
||||
eventNames && (logFilter.eventNames = eventNames); |
||||
actions && (logFilter.actions = actions); |
||||
|
||||
// reconstitute arrays in place
|
||||
Object.keys(logFilter).forEach(function(k) { |
||||
var thisProp = logFilter[k]; |
||||
if (typeof thisProp == 'string' && thisProp !== ALL) { |
||||
logFilter[k] = thisProp ? thisProp.split(',') : []; |
||||
} |
||||
}); |
||||
} |
||||
|
||||
return { |
||||
|
||||
enable: function(enable) { |
||||
this.enabled = !!enable; |
||||
|
||||
if (enable && window.console) { |
||||
console.info('Booting in DEBUG mode'); |
||||
console.info('You can configure event logging with DEBUG.events.logAll()/logNone()/logByName()/logByAction()'); |
||||
} |
||||
|
||||
retrieveLogFilter(); |
||||
|
||||
window.DEBUG = this; |
||||
}, |
||||
|
||||
find: { |
||||
byName: byName, |
||||
byNameContains: byNameContains, |
||||
byType: byType, |
||||
byValue: byValue, |
||||
byValueCoerced: byValueCoerced, |
||||
custom: custom |
||||
}, |
||||
|
||||
events: { |
||||
logFilter: logFilter, |
||||
|
||||
// Accepts any number of action args
|
||||
// e.g. DEBUG.events.logByAction("on", "off")
|
||||
logByAction: filterEventLogsByAction, |
||||
|
||||
// Accepts any number of event name args (inc. regex or wildcards)
|
||||
// e.g. DEBUG.events.logByName(/ui.*/, "*Thread*");
|
||||
logByName: filterEventLogsByName, |
||||
|
||||
logAll: showAllEventLogs, |
||||
logNone: hideAllEventLogs |
||||
} |
||||
}; |
||||
} |
||||
); |
||||
@ -1,31 +0,0 @@
@@ -1,31 +0,0 @@
|
||||
// ==========================================
|
||||
// Copyright 2013 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
|
||||
define( |
||||
|
||||
[ |
||||
'./advice', |
||||
'./component', |
||||
'./compose', |
||||
'./logger', |
||||
'./registry', |
||||
'./utils' |
||||
], |
||||
|
||||
function(advice, component, compose, logger, registry, utils) { |
||||
'use strict'; |
||||
|
||||
return { |
||||
advice: advice, |
||||
component: component, |
||||
compose: compose, |
||||
logger: logger, |
||||
registry: registry, |
||||
utils: utils |
||||
}; |
||||
|
||||
} |
||||
); |
||||
@ -1,100 +0,0 @@
@@ -1,100 +0,0 @@
|
||||
// ==========================================
|
||||
// Copyright 2013 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
|
||||
define( |
||||
|
||||
[ |
||||
'./utils' |
||||
], |
||||
|
||||
function(utils) { |
||||
'use strict'; |
||||
|
||||
var actionSymbols = { |
||||
on: '<-', |
||||
trigger: '->', |
||||
off: 'x ' |
||||
}; |
||||
|
||||
function elemToString(elem) { |
||||
var tagStr = elem.tagName ? elem.tagName.toLowerCase() : elem.toString(); |
||||
var classStr = elem.className ? '.' + (elem.className) : ''; |
||||
var result = tagStr + classStr; |
||||
return elem.tagName ? ['\'', '\''].join(result) : result; |
||||
} |
||||
|
||||
function log(action, component, eventArgs) { |
||||
if (!window.DEBUG || !window.DEBUG.enabled) return; |
||||
var name, eventType, elem, fn, payload, logFilter, toRegExp, actionLoggable, nameLoggable, info; |
||||
|
||||
if (typeof eventArgs[eventArgs.length-1] == 'function') { |
||||
fn = eventArgs.pop(); |
||||
fn = fn.unbound || fn; // use unbound version if any (better info)
|
||||
} |
||||
|
||||
if (eventArgs.length == 1) { |
||||
elem = component.$node[0]; |
||||
eventType = eventArgs[0]; |
||||
} else if ((eventArgs.length == 2) && typeof eventArgs[1] == 'object' && !eventArgs[1].type) { |
||||
//2 args, first arg is not elem
|
||||
elem = component.$node[0]; |
||||
eventType = eventArgs[0]; |
||||
if (action == "trigger") { |
||||
payload = eventArgs[1]; |
||||
} |
||||
} else { |
||||
//2+ args, first arg is elem
|
||||
elem = eventArgs[0]; |
||||
eventType = eventArgs[1]; |
||||
if (action == "trigger") { |
||||
payload = eventArgs[2]; |
||||
} |
||||
} |
||||
|
||||
name = typeof eventType == 'object' ? eventType.type : eventType; |
||||
|
||||
logFilter = DEBUG.events.logFilter; |
||||
|
||||
// no regex for you, actions...
|
||||
actionLoggable = logFilter.actions == 'all' || (logFilter.actions.indexOf(action) > -1); |
||||
// event name filter allow wildcards or regex...
|
||||
toRegExp = function(expr) { |
||||
return expr.test ? expr : new RegExp('^' + expr.replace(/\*/g, '.*') + '$'); |
||||
}; |
||||
nameLoggable = |
||||
logFilter.eventNames == 'all' || |
||||
logFilter.eventNames.some(function(e) {return toRegExp(e).test(name);}); |
||||
|
||||
if (actionLoggable && nameLoggable) { |
||||
info = [actionSymbols[action], action, '[' + name + ']']; |
||||
payload && info.push(payload); |
||||
info.push(elemToString(elem)); |
||||
info.push(component.constructor.describe.split(' ').slice(0,3).join(' ')); |
||||
console.groupCollapsed && action == 'trigger' && console.groupCollapsed(action, name); |
||||
console.info.apply(console, info); |
||||
} |
||||
} |
||||
|
||||
function withLogging() { |
||||
this.before('trigger', function() { |
||||
log('trigger', this, utils.toArray(arguments)); |
||||
}); |
||||
if (console.groupCollapsed) { |
||||
this.after('trigger', function() { |
||||
console.groupEnd(); |
||||
}); |
||||
} |
||||
this.before('on', function() { |
||||
log('on', this, utils.toArray(arguments)); |
||||
}); |
||||
this.before('off', function() { |
||||
log('off', this, utils.toArray(arguments)); |
||||
}); |
||||
} |
||||
|
||||
return withLogging; |
||||
} |
||||
); |
||||
@ -1,228 +0,0 @@
@@ -1,228 +0,0 @@
|
||||
// ==========================================
|
||||
// Copyright 2013 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
|
||||
define( |
||||
|
||||
[], |
||||
|
||||
function() { |
||||
'use strict'; |
||||
|
||||
function parseEventArgs(instance, args) { |
||||
var element, type, callback; |
||||
var end = args.length; |
||||
|
||||
if (typeof args[end - 1] === 'function') { |
||||
end -= 1; |
||||
callback = args[end]; |
||||
} |
||||
|
||||
if (typeof args[end - 1] === 'object') { |
||||
end -= 1; |
||||
} |
||||
|
||||
if (end == 2) { |
||||
element = args[0]; |
||||
type = args[1]; |
||||
} else { |
||||
element = instance.node; |
||||
type = args[0]; |
||||
} |
||||
|
||||
return { |
||||
element: element, |
||||
type: type, |
||||
callback: callback |
||||
}; |
||||
} |
||||
|
||||
function matchEvent(a, b) { |
||||
return ( |
||||
(a.element == b.element) && |
||||
(a.type == b.type) && |
||||
(b.callback == null || (a.callback == b.callback)) |
||||
); |
||||
} |
||||
|
||||
function Registry() { |
||||
|
||||
var registry = this; |
||||
|
||||
(this.reset = function() { |
||||
this.components = []; |
||||
this.allInstances = {}; |
||||
this.events = []; |
||||
}).call(this); |
||||
|
||||
function ComponentInfo(component) { |
||||
this.component = component; |
||||
this.attachedTo = []; |
||||
this.instances = {}; |
||||
|
||||
this.addInstance = function(instance) { |
||||
var instanceInfo = new InstanceInfo(instance); |
||||
this.instances[instance.identity] = instanceInfo; |
||||
this.attachedTo.push(instance.node); |
||||
|
||||
return instanceInfo; |
||||
}; |
||||
|
||||
this.removeInstance = function(instance) { |
||||
delete this.instances[instance.identity]; |
||||
var indexOfNode = this.attachedTo.indexOf(instance.node); |
||||
(indexOfNode > -1) && this.attachedTo.splice(indexOfNode, 1); |
||||
|
||||
if (!Object.keys(this.instances).length) { |
||||
//if I hold no more instances remove me from registry
|
||||
registry.removeComponentInfo(this); |
||||
} |
||||
}; |
||||
|
||||
this.isAttachedTo = function(node) { |
||||
return this.attachedTo.indexOf(node) > -1; |
||||
}; |
||||
} |
||||
|
||||
function InstanceInfo(instance) { |
||||
this.instance = instance; |
||||
this.events = []; |
||||
|
||||
this.addBind = function(event) { |
||||
this.events.push(event); |
||||
registry.events.push(event); |
||||
}; |
||||
|
||||
this.removeBind = function(event) { |
||||
for (var i = 0, e; e = this.events[i]; i++) { |
||||
if (matchEvent(e, event)) { |
||||
this.events.splice(i, 1); |
||||
} |
||||
} |
||||
}; |
||||
} |
||||
|
||||
this.addInstance = function(instance) { |
||||
var component = this.findComponentInfo(instance); |
||||
|
||||
if (!component) { |
||||
component = new ComponentInfo(instance.constructor); |
||||
this.components.push(component); |
||||
} |
||||
|
||||
var inst = component.addInstance(instance); |
||||
|
||||
this.allInstances[instance.identity] = inst; |
||||
|
||||
return component; |
||||
}; |
||||
|
||||
this.removeInstance = function(instance) { |
||||
var index, instInfo = this.findInstanceInfo(instance); |
||||
|
||||
//remove from component info
|
||||
var componentInfo = this.findComponentInfo(instance); |
||||
componentInfo && componentInfo.removeInstance(instance); |
||||
|
||||
//remove from registry
|
||||
delete this.allInstances[instance.identity]; |
||||
}; |
||||
|
||||
this.removeComponentInfo = function(componentInfo) { |
||||
var index = this.components.indexOf(componentInfo); |
||||
(index > -1) && this.components.splice(index, 1); |
||||
}; |
||||
|
||||
this.findComponentInfo = function(which) { |
||||
var component = which.attachTo ? which : which.constructor; |
||||
|
||||
for (var i = 0, c; c = this.components[i]; i++) { |
||||
if (c.component === component) { |
||||
return c; |
||||
} |
||||
} |
||||
|
||||
return null; |
||||
}; |
||||
|
||||
this.findInstanceInfo = function(instance) { |
||||
return this.allInstances[instance.identity] || null; |
||||
}; |
||||
|
||||
this.getBoundEventNames = function(instance) { |
||||
return this.findInstanceInfo(instance).events.map(function(ev) { |
||||
return ev.type; |
||||
}); |
||||
}; |
||||
|
||||
this.findInstanceInfoByNode = function(node) { |
||||
var result = []; |
||||
Object.keys(this.allInstances).forEach(function(k) { |
||||
var thisInstanceInfo = this.allInstances[k]; |
||||
if (thisInstanceInfo.instance.node === node) { |
||||
result.push(thisInstanceInfo); |
||||
} |
||||
}, this); |
||||
return result; |
||||
}; |
||||
|
||||
this.on = function(componentOn) { |
||||
var instance = registry.findInstanceInfo(this), boundCallback; |
||||
|
||||
// unpacking arguments by hand benchmarked faster
|
||||
var l = arguments.length, i = 1; |
||||
var otherArgs = new Array(l - 1); |
||||
for (; i < l; i++) otherArgs[i - 1] = arguments[i]; |
||||
|
||||
if (instance) { |
||||
boundCallback = componentOn.apply(null, otherArgs); |
||||
if (boundCallback) { |
||||
otherArgs[otherArgs.length-1] = boundCallback; |
||||
} |
||||
var event = parseEventArgs(this, otherArgs); |
||||
instance.addBind(event); |
||||
} |
||||
}; |
||||
|
||||
this.off = function(/*el, type, callback*/) { |
||||
var event = parseEventArgs(this, arguments), |
||||
instance = registry.findInstanceInfo(this); |
||||
|
||||
if (instance) { |
||||
instance.removeBind(event); |
||||
} |
||||
|
||||
//remove from global event registry
|
||||
for (var i = 0, e; e = registry.events[i]; i++) { |
||||
if (matchEvent(e, event)) { |
||||
registry.events.splice(i, 1); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
// debug tools may want to add advice to trigger
|
||||
registry.trigger = function() {}; |
||||
|
||||
this.teardown = function() { |
||||
registry.removeInstance(this); |
||||
}; |
||||
|
||||
this.withRegistration = function() { |
||||
this.after('initialize', function() { |
||||
registry.addInstance(this); |
||||
}); |
||||
|
||||
this.around('on', registry.on); |
||||
this.after('off', registry.off); |
||||
//debug tools may want to add advice to trigger
|
||||
window.DEBUG && DEBUG.enabled && this.after('trigger', registry.trigger); |
||||
this.after('teardown', {obj: registry, fnName: 'teardown'}); |
||||
}; |
||||
|
||||
} |
||||
|
||||
return new Registry; |
||||
} |
||||
); |
||||
@ -1,263 +0,0 @@
@@ -1,263 +0,0 @@
|
||||
// ==========================================
|
||||
// Copyright 2013 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
|
||||
define( |
||||
|
||||
[], |
||||
|
||||
function() { |
||||
'use strict'; |
||||
|
||||
var arry = []; |
||||
var DEFAULT_INTERVAL = 100; |
||||
|
||||
var utils = { |
||||
|
||||
isDomObj: function(obj) { |
||||
return !!(obj.nodeType || (obj === window)); |
||||
}, |
||||
|
||||
toArray: function(obj, from) { |
||||
return arry.slice.call(obj, from); |
||||
}, |
||||
|
||||
// returns new object representing multiple objects merged together
|
||||
// optional final argument is boolean which specifies if merge is recursive
|
||||
// original objects are unmodified
|
||||
//
|
||||
// usage:
|
||||
// var base = {a:2, b:6};
|
||||
// var extra = {b:3, c:4};
|
||||
// merge(base, extra); //{a:2, b:3, c:4}
|
||||
// base; //{a:2, b:6}
|
||||
//
|
||||
// var base = {a:2, b:6};
|
||||
// var extra = {b:3, c:4};
|
||||
// var extraExtra = {a:4, d:9};
|
||||
// merge(base, extra, extraExtra); //{a:4, b:3, c:4. d: 9}
|
||||
// base; //{a:2, b:6}
|
||||
//
|
||||
// var base = {a:2, b:{bb:4, cc:5}};
|
||||
// var extra = {a:4, b:{cc:7, dd:1}};
|
||||
// merge(base, extra, true); //{a:4, b:{bb:4, cc:7, dd:1}}
|
||||
// base; //{a:2, b:6}
|
||||
|
||||
merge: function(/*obj1, obj2,....deepCopy*/) { |
||||
// unpacking arguments by hand benchmarked faster
|
||||
var l = arguments.length, |
||||
i = 0, |
||||
args = new Array(l + 1); |
||||
for (; i < l; i++) args[i + 1] = arguments[i]; |
||||
|
||||
if (l === 0) { |
||||
return {}; |
||||
} |
||||
|
||||
//start with empty object so a copy is created
|
||||
args[0] = {}; |
||||
|
||||
if (args[args.length - 1] === true) { |
||||
//jquery extend requires deep copy as first arg
|
||||
args.pop(); |
||||
args.unshift(true); |
||||
} |
||||
|
||||
return $.extend.apply(undefined, args); |
||||
}, |
||||
|
||||
// updates base in place by copying properties of extra to it
|
||||
// optionally clobber protected
|
||||
// usage:
|
||||
// var base = {a:2, b:6};
|
||||
// var extra = {c:4};
|
||||
// push(base, extra); //{a:2, b:6, c:4}
|
||||
// base; //{a:2, b:6, c:4}
|
||||
//
|
||||
// var base = {a:2, b:6};
|
||||
// var extra = {b: 4 c:4};
|
||||
// push(base, extra, true); //Error ("utils.push attempted to overwrite 'b' while running in protected mode")
|
||||
// base; //{a:2, b:6}
|
||||
//
|
||||
// objects with the same key will merge recursively when protect is false
|
||||
// eg:
|
||||
// var base = {a:16, b:{bb:4, cc:10}};
|
||||
// var extra = {b:{cc:25, dd:19}, c:5};
|
||||
// push(base, extra); //{a:16, {bb:4, cc:25, dd:19}, c:5}
|
||||
//
|
||||
push: function(base, extra, protect) { |
||||
if (base) { |
||||
Object.keys(extra || {}).forEach(function(key) { |
||||
if (base[key] && protect) { |
||||
throw new Error('utils.push attempted to overwrite "' + key + '" while running in protected mode'); |
||||
} |
||||
|
||||
if (typeof base[key] == 'object' && typeof extra[key] == 'object') { |
||||
// recurse
|
||||
this.push(base[key], extra[key]); |
||||
} else { |
||||
// no protect, so extra wins
|
||||
base[key] = extra[key]; |
||||
} |
||||
}, this); |
||||
} |
||||
|
||||
return base; |
||||
}, |
||||
|
||||
isEnumerable: function(obj, property) { |
||||
return Object.keys(obj).indexOf(property) > -1; |
||||
}, |
||||
|
||||
// build a function from other function(s)
|
||||
// utils.compose(a,b,c) -> a(b(c()));
|
||||
// implementation lifted from underscore.js (c) 2009-2012 Jeremy Ashkenas
|
||||
compose: function() { |
||||
var funcs = arguments; |
||||
|
||||
return function() { |
||||
var args = arguments; |
||||
|
||||
for (var i = funcs.length-1; i >= 0; i--) { |
||||
args = [funcs[i].apply(this, args)]; |
||||
} |
||||
|
||||
return args[0]; |
||||
}; |
||||
}, |
||||
|
||||
// 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
|
||||
uniqueArray: function(array) { |
||||
var u = {}, a = []; |
||||
|
||||
for (var i = 0, l = array.length; i < l; ++i) { |
||||
if (u.hasOwnProperty(array[i])) { |
||||
continue; |
||||
} |
||||
|
||||
a.push(array[i]); |
||||
u[array[i]] = 1; |
||||
} |
||||
|
||||
return a; |
||||
}, |
||||
|
||||
debounce: function(func, wait, immediate) { |
||||
if (typeof wait != 'number') { |
||||
wait = DEFAULT_INTERVAL; |
||||
} |
||||
|
||||
var timeout, result; |
||||
|
||||
return function() { |
||||
var context = this, args = arguments; |
||||
var later = function() { |
||||
timeout = null; |
||||
if (!immediate) { |
||||
result = func.apply(context, args); |
||||
} |
||||
}; |
||||
var callNow = immediate && !timeout; |
||||
|
||||
clearTimeout(timeout); |
||||
timeout = setTimeout(later, wait); |
||||
|
||||
if (callNow) { |
||||
result = func.apply(context, args); |
||||
} |
||||
|
||||
return result; |
||||
}; |
||||
}, |
||||
|
||||
throttle: function(func, wait) { |
||||
if (typeof wait != 'number') { |
||||
wait = DEFAULT_INTERVAL; |
||||
} |
||||
|
||||
var context, args, timeout, throttling, more, result; |
||||
var whenDone = this.debounce(function(){ |
||||
more = throttling = false; |
||||
}, wait); |
||||
|
||||
return function() { |
||||
context = this; args = arguments; |
||||
var later = function() { |
||||
timeout = null; |
||||
if (more) { |
||||
result = func.apply(context, args); |
||||
} |
||||
whenDone(); |
||||
}; |
||||
|
||||
if (!timeout) { |
||||
timeout = setTimeout(later, wait); |
||||
} |
||||
|
||||
if (throttling) { |
||||
more = true; |
||||
} else { |
||||
throttling = true; |
||||
result = func.apply(context, args); |
||||
} |
||||
|
||||
whenDone(); |
||||
return result; |
||||
}; |
||||
}, |
||||
|
||||
countThen: function(num, base) { |
||||
return function() { |
||||
if (!--num) { return base.apply(this, arguments); } |
||||
}; |
||||
}, |
||||
|
||||
delegate: function(rules) { |
||||
return function(e, data) { |
||||
var target = $(e.target), parent; |
||||
|
||||
Object.keys(rules).forEach(function(selector) { |
||||
if (!e.isPropagationStopped() && (parent = target.closest(selector)).length) { |
||||
data = data || {}; |
||||
data.el = parent[0]; |
||||
return rules[selector].apply(this, [e, data]); |
||||
} |
||||
}, this); |
||||
}; |
||||
}, |
||||
|
||||
// ensures that a function will only be called once.
|
||||
// usage:
|
||||
// will only create the application once
|
||||
// var initialize = utils.once(createApplication)
|
||||
// initialize();
|
||||
// initialize();
|
||||
//
|
||||
// will only delete a record once
|
||||
// var myHanlder = function () {
|
||||
// $.ajax({type: 'DELETE', url: 'someurl.com', data: {id: 1}});
|
||||
// };
|
||||
// this.on('click', utils.once(myHandler));
|
||||
//
|
||||
once: function(func) { |
||||
var ran, result; |
||||
|
||||
return function() { |
||||
if (ran) { |
||||
return result; |
||||
} |
||||
|
||||
ran = true; |
||||
result = func.apply(this, arguments); |
||||
|
||||
return result; |
||||
}; |
||||
} |
||||
|
||||
}; |
||||
|
||||
return utils; |
||||
} |
||||
); |
||||
Loading…
Reference in new issue