diff options
Diffstat (limited to 'third_party/webkit/PerformanceTests/Speedometer/resources/flightjs-example-app/components/flight/lib/component.js')
-rw-r--r-- | third_party/webkit/PerformanceTests/Speedometer/resources/flightjs-example-app/components/flight/lib/component.js | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/flightjs-example-app/components/flight/lib/component.js b/third_party/webkit/PerformanceTests/Speedometer/resources/flightjs-example-app/components/flight/lib/component.js new file mode 100644 index 0000000000..d9a5077ac9 --- /dev/null +++ b/third_party/webkit/PerformanceTests/Speedometer/resources/flightjs-example-app/components/flight/lib/component.js @@ -0,0 +1,308 @@ +// ========================================== +// Copyright 2013 Twitter, Inc +// Licensed under The MIT License +// http://opensource.org/licenses/MIT +// ========================================== + +"use strict"; + +define( + + [ + './advice', + './utils', + './compose', + './registry' + ], + + function(advice, utils, compose, registry) { + + var functionNameRegEx = /function (.*?)\s?\(/; + 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 teardown() { + teardownInstance(registry.findInstanceInfo(this)); + } + + //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]; + 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(" ") + ); + } + } + + //common mixin allocates basic functionality - used by all component prototypes + //callback context is bound to component + function withBaseComponent() { + + // 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 (window.DEBUG && window.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 { + 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; + + // if the original callback is already branded by jQuery's guid, copy it to the context-bound version + if (originalCb.guid) { + callback.guid = originalCb.guid; + } + + $element.on(type, callback); + + // get jquery's guid from our bound fn, so unbinding will work + originalCb.guid = callback.guid; + + 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]; + } + + 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]] = 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 = $.noop; + this.teardown = teardown; + } + + 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); + + $(selector).each(function(i, node) { + var rawNode = node.jQuery ? node[0] : node; + var componentInfo = registry.findComponentInfo(this) + if (componentInfo && componentInfo.isAttachedTo(rawNode)) { + //already attached + return; + } + + new this(node, options); + }.bind(this)); + } + + // 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]; + + Component.toString = function() { + var prettyPrintMixins = mixins.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 != "withBaseComponent") ? mixin.name : ""; + } + }).filter(Boolean).join(', '); + return prettyPrintMixins; + }; + + if (window.DEBUG && window.DEBUG.enabled) { + Component.describe = Component.toString(); + } + + //'options' is optional hash to be merged with 'defaults' in the component definition + function Component(node, options) { + options = options || {}; + 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); + } + + this.toString = Component.toString; + if (window.DEBUG && window.DEBUG.enabled) { + this.describe = this.toString(); + } + + //merge defaults with supplied options + //put options in attr.__proto__ to avoid merge overhead + var attr = Object.create(options); + for (var key in this.defaults) { + if (!options.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); + + this.initialize.call(this, options); + } + + Component.attachTo = attachTo; + Component.teardownAll = teardownAll; + + // prepend common mixins to supplied list, then mixin all flavors + mixins.unshift(withBaseComponent, 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; + } +); |