13 changed files with 0 additions and 1630 deletions
@ -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 @@ |
|||||||
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 @@ |
|||||||
# 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 @@ |
|||||||
{ |
|
||||||
"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 @@ |
|||||||
// ==========================================
|
|
||||||
// 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 @@ |
|||||||
// ==========================================
|
|
||||||
// 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 @@ |
|||||||
// ==========================================
|
|
||||||
// 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 @@ |
|||||||
// ==========================================
|
|
||||||
// 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 @@ |
|||||||
// ==========================================
|
|
||||||
// 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 @@ |
|||||||
// ==========================================
|
|
||||||
// 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 @@ |
|||||||
// ==========================================
|
|
||||||
// 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 @@ |
|||||||
// ==========================================
|
|
||||||
// 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 @@ |
|||||||
// ==========================================
|
|
||||||
// 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