From c59648f3c491e5e5aba4e2bd62ee0178d3365b58 Mon Sep 17 00:00:00 2001 From: rajyavardhanp Date: Wed, 29 Apr 2015 13:18:33 +0530 Subject: [PATCH 001/195] ENYO-931: removed unnecessary trailing semi-colon DCO-1.1-Signed-Off-By: Rajyavardhan P --- lib/Control/Control.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Control/Control.js b/lib/Control/Control.js index 4acd0d52d..5580eb8fc 100644 --- a/lib/Control/Control.js +++ b/lib/Control/Control.js @@ -1663,7 +1663,7 @@ Control.concat = function (ctor, props, instance) { if (props.style) { if (instance) { - str = (proto.style ? (proto.style + ';') : '') + (props.style + ';'); + str = (proto.style ? proto.style : '') + props.style; proto.style = Control.normalizeCssStyleString(str); } else { str = proto.kindStyle ? proto.kindStyle : ''; From b6a745a4f2a66d75b32687bf6e4af993e2ed2774 Mon Sep 17 00:00:00 2001 From: Ryan Duffy Date: Thu, 2 Jul 2015 16:45:44 -0500 Subject: [PATCH 002/195] add rtl-compatible margin and padding mixins Issue: ENYO-2009 Enyo-DCO-1.1-Signed-off-by: Ryan Duffy (ryan.duffy@lge.com) --- css/mixins.less | 21 +++++++++++++++++++++ package.json | 3 ++- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 css/mixins.less diff --git a/css/mixins.less b/css/mixins.less new file mode 100644 index 000000000..37afdb723 --- /dev/null +++ b/css/mixins.less @@ -0,0 +1,21 @@ +// Applies RTL-compatible start and end margins to a selector +.margin-start-end (@start, @end) { + margin-left: @start; + margin-right: @end; + + .enyo-locale-right-to-left & { + margin-left: @end; + margin-right: @start; + } +} + +// Applies RTL-compatible start and end padding to a selector +.padding-start-end (@start, @end) { + padding-left: @start; + padding-right: @end; + + .enyo-locale-right-to-left & { + padding-left: @end; + padding-right: @start; + } +} \ No newline at end of file diff --git a/package.json b/package.json index 810267ce5..95c9ddb93 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "bugs": "http://jira.enyojs.com/", "main": "index.js", "styles": [ - "css/enyo.css" + "css/enyo.css", + "css/mixins.less" ], "keywords": [ "framework", From 114ee58c34f6f043320d9d8ca347b95bbe6ab933 Mon Sep 17 00:00:00 2001 From: Ryan Duffy Date: Mon, 13 Jul 2015 13:50:53 -0500 Subject: [PATCH 003/195] add History submodule Moved from moonstone to add support to the core framework with a refined API. Issue: ENYO-2083 Enyo-DCO-1.1-Signed-off-by: Ryan Duffy (ryan.duffy@lge.com) --- lib/History.js | 313 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 313 insertions(+) create mode 100644 lib/History.js diff --git a/lib/History.js b/lib/History.js new file mode 100644 index 000000000..705104741 --- /dev/null +++ b/lib/History.js @@ -0,0 +1,313 @@ +var + dispatcher = require('enyo/dispatcher'), + kind = require('enyo/kind'), + utils = require('enyo/utils'), + Component = require('enyo/Component'), + Signals = require('enyo/Signals'); + +var + // App history, ordered from oldest to newest. + _history = [], + + // History actions that were queued because a back operation was in progress. These will be + // dequeued when the next popstate event is handled + _queue = [], + + // Indicates the number of steps 'back' in history requested + _popQueueCount = 0, + + // `true` if a push action has been enqueued + _pushQueued = false, + + // Track if we're in the midst of handling a pop + _processing = false, + + // `true` if the platform support the HTML5 History API + _supports = !!global.history.pushState; + +var EnyoHistory = module.exports = kind.singleton( + /** @lends module:enyo/History~History.prototype */ { + + /** + * @private + */ + kind: Component, + + /** + * When enabled, enyo/History will handle onpopstate events and notify controls when their + * history entry is popped. + * + * @type {Boolean} + * @default true + * @public + */ + enabled: true, + + /** + * @private + */ + components: [ + {kind: Signals, onkeyup: 'handleKeyUp'} + ], + + /** + * @private + */ + enabledChanged: function () { + // reset private members + _history = []; + _queue = []; + _popQueueCount = 0; + _pushQueued = false; + _processing = false; + this.stopJob('history.go'); + }, + + // Public methods + + /** + * Adds a new history entry + * + * @param {module:enyo/History~HistoryEntry} entry Object describing the history entry. Must + * contain a `handler` property to invoke the callback. If it also has a `context` property, + * the `handler` will be called with that context. The entry object will be included as the + * first argument to the `handler`. + * + * @public + */ + push: function (entry) { + if (this.enabled) { + if (_popQueueCount) { + this.enqueuePush(entry); + } else { + this.pushEntry(entry); + } + } + }, + + /** + * Asynchronously removes `count` entries from the history invoking the callback for each if it + * exists. + * + * @param {Number} count Number of entries to remove + * + * @oublic + */ + pop: function (count) { + if (!this.enabled) return; + this.enqueuePop('pop', count); + }, + + /** + * Asynchronously removes `count` entries from the history without invoking the callbacks for + * each. + * + * @param {Number} count Number of entries to remove + * + * @oublic + */ + drop: function (count) { + if (!this.enabled) return; + this.enqueuePop('drop', count); + }, + + /** + * Returns the latest history entry without removing it. + * + * @return {module:enyo/History~HistoryEntry} + * @public + */ + peek: function () { + return _history[_history.length - 1]; + }, + + /** + * Returns `true` when enyo/History is currently handling a popstate event and invoking the + * callbacks for any popped entries. + * + * @return {Boolean} [description] + */ + isProcessing: function () { + return _processing; + }, + + // Private methods + + /** + * Handles flushing the history action queus and processing each entry. When the queues are + * empty and a popstate event occurs, this pops the next entry and processes it. + * + * @param {Object} state Value of the state member of the PopStateEvent + * + * @private + */ + processState: function (state) { + _processing = true; + if (_queue.length) { + this.processQueue(); + } else { + this.processPopEntry(_history.pop()); + } + _processing = false; + }, + + /** + * Processes any queued actions + * + * @private + */ + processQueue: function () { + var i, l = _queue.length, next, + i2, entries; + + this.silencePushEntries(); + + for (i = 0; i < l; i++) { + next = _queue.shift(); + + if (next.type === 'push') { + this.pushEntry(next.entry, next.silenced); + } else { + _popQueueCount -= next.count; + entries = _history.splice(_history.length - next.count, next.count); + // if a 'pop' was requested + if (next.type == 'pop') { + // iterate the requested number of history entries + for (i2 = entries.length - 1; i2 >= 0; --i2) { + // and call each handler if it exists + this.processPopEntry(entries[i2]); + } + } + // otherwise we just drop the entries and do nothing + } + } + _popQueueCount = 0; + _pushQueued = false; + }, + + /** + * Marks any queued push entries as silenced that would be popped by a subsequent queued pop or + * drop entry. + * + * @private + */ + silencePushEntries: function () { + var i, next, + silence = 0; + + for (i = _queue.length - 1; i >= 0; --i) { + next = _queue[i]; + if (next.type == 'push') { + if (silence) { + silence -= 1; + next.silenced = true; + } + } else { + silence += next.count; + } + } + }, + + /** + * Invokes the callback for a pop entry + * + * @param {module:enyo/History~HistoryEntry} entry + * + * @private + */ + processPopEntry: function (entry) { + if (entry.handler) { + utils.call(entry.context, entry.handler, entry); + } + }, + + /** + * Adds an pop or drop entry to the history queue + * + * @param {String} type History action type ('pop' or 'drop') + * @param {Number} [count] Number of actions to invoke. Defaults to 1. + * + * @private + */ + enqueuePop: function (type, count) { + count = count || 1; + _queue.push({type: type, count: count}); + // if we've only queued pop/drop events, we need to increment the number of entries to go + // back. once a push is queued, the history must be managed in processState. + if (!_pushQueued) { + _popQueueCount += count; + } + if (_queue.length === 1) { + // defer the actual 'back' action so pop() or drop() can be called multiple times in the + // same frame. Otherwise, only the first go() would be observed. + this.startJob('history.go', function () { + // If the platform supports pushState and the effective change is positive + if (_supports && _popQueueCount > 0) { + // go back that many entries + global.history.go(-_popQueueCount); + } else { + // otherwise we'll start the processing + this.handlePop({ + state: this.peek() + }); + } + }); + } + }, + + /** + * Adds a push entry to the history queue + * + * @param {module:enyo/History~HistoryEntry} entry + * + * @private + */ + enqueuePush: function (entry) { + _pushQueued = true; + _queue.push({type: 'push', entry: entry}); + }, + + /** + * Adds an new entry to the _history and pushes the new state to global.history (if supported) + * + * @param {module:enyo/History~HistoryEntry} entry + * @param {Boolean} silenced Prevents pushing the state onto history when `true` + * + * @private + */ + pushEntry: function (entry, silenced) { + var id; + _history.push(entry); + id = entry.context && entry.context.id || 'anonymous'; + if (_supports && !silenced) { + global.history.pushState({id: id}, '', ''); + } + }, + + /** + * onpopstate handler + * + * @private + */ + handlePop: function (event) { + if (this.enabled && _history.length) { + this.processState(event.state); + } + }, + + /** + * onkeyup handler + * + * @private + */ + handleKeyUp: function (sender, event) { + var current = this.peek(); + if (event.keySymbol == 'back' && current && current.getShowing()) { + this.pop(); + } + return true; + } + +}); + +dispatcher.listen(global, 'popstate', EnyoHistory.handlePop.bind(EnyoHistory)); \ No newline at end of file From 37420ac1ce6bee714b2822270351b99eb4c4524d Mon Sep 17 00:00:00 2001 From: Ryan Duffy Date: Thu, 16 Jul 2015 14:49:02 -0500 Subject: [PATCH 004/195] update Router to use enyo/History Enyo-DCO-1.1-Signed-off-by: Ryan Duffy (ryan.duffy@lge.com) --- lib/History.js | 72 +++++++++++++++++++++++++++++++++++++++++------- lib/Router.js | 75 ++++++++++++++++++-------------------------------- 2 files changed, 89 insertions(+), 58 deletions(-) diff --git a/lib/History.js b/lib/History.js index 705104741..56c740c02 100644 --- a/lib/History.js +++ b/lib/History.js @@ -1,3 +1,33 @@ +/** +* [EnyoHistory description] +* +* @module enyo/History +*/ + +/** +* Passed to {@link module:enyo/History#push} to add a new history entry and passed as the first +* argument to {@link module:enyo/History~HistoryEntry#handler} when the entry is popped. Additional +* properties beyond those defined here may be included to provide additional data to the handler. +* +* @example +* var +* EnyoHistory = require('enyo/History'); +* +* EnyoHistory.push({ +* handler: function (entry) { +* console.log('Entry added at ', entry.time, 'and popped at', Date.now()); +* }, +* time: Date.now() +* }); +* +* @typedef {Object} module:enyo/History~HistoryEntry +* @property {Function|String} handler - Function called when this history entry is popped. May +* either be a Function or the name of a function on `context`. +* @property {Object} [context] - Context on which `handler` is bound +* @property {String} [location] - When {@link module:enyo/History#isSupported} is `true`, updates +* the displayed URL. Must be within the same origin. +*/ + var dispatcher = require('enyo/dispatcher'), kind = require('enyo/kind'), @@ -68,10 +98,7 @@ var EnyoHistory = module.exports = kind.singleton( /** * Adds a new history entry * - * @param {module:enyo/History~HistoryEntry} entry Object describing the history entry. Must - * contain a `handler` property to invoke the callback. If it also has a `context` property, - * the `handler` will be called with that context. The entry object will be included as the - * first argument to the `handler`. + * @param {module:enyo/History~HistoryEntry} entry Object describing the history entry * * @public */ @@ -104,7 +131,7 @@ var EnyoHistory = module.exports = kind.singleton( * * @param {Number} count Number of entries to remove * - * @oublic + * @public */ drop: function (count) { if (!this.enabled) return; @@ -121,16 +148,41 @@ var EnyoHistory = module.exports = kind.singleton( return _history[_history.length - 1]; }, + /** + * Asynchronously drops all history entries without calling their respective handlers. When the + * entries are popped, the internal history will be empty and the browser history will be + * reset to the state when this module started tracking the history. + * + * @public + */ + clear: function () { + var len = _history.length; + if (len) { + this.drop(len); + } + }, + /** * Returns `true` when enyo/History is currently handling a popstate event and invoking the * callbacks for any popped entries. * - * @return {Boolean} [description] + * @return {Boolean} + * @public */ isProcessing: function () { return _processing; }, + /** + * Returns `true` when the HTML5 history API is supported by the platform + * + * @return {Boolean} + * @public + */ + isSupported: function () { + return _supports; + }, + // Private methods /** @@ -217,7 +269,7 @@ var EnyoHistory = module.exports = kind.singleton( */ processPopEntry: function (entry) { if (entry.handler) { - utils.call(entry.context, entry.handler, entry); + utils.call(entry.context, entry.handler, [entry]); } }, @@ -276,11 +328,11 @@ var EnyoHistory = module.exports = kind.singleton( * @private */ pushEntry: function (entry, silenced) { - var id; + var id = entry.context && entry.context.id || 'anonymous', + location = entry.location || ''; _history.push(entry); - id = entry.context && entry.context.id || 'anonymous'; if (_supports && !silenced) { - global.history.pushState({id: id}, '', ''); + global.history.pushState({id: id}, '', location); } }, diff --git a/lib/Router.js b/lib/Router.js index b427671c9..3b4bd0a9f 100644 --- a/lib/Router.js +++ b/lib/Router.js @@ -12,7 +12,8 @@ var dispatcher = require('./dispatcher'); var - Controller = require('./Controller'); + Controller = require('./Controller'), + EnyoHistory = require('./History'); /** * Any instance of a router will be referenced here for the global hash change handler. @@ -227,11 +228,6 @@ module.exports = kind( */ _current: '', - /* - * @private - */ - _history: null, - // ........................... // COMPUTED PROPERTIES @@ -304,7 +300,12 @@ module.exports = kind( var current = this.get('location'); if (change) { if (current !== loc) { - global.location.hash = loc; + if (EnyoHistory.isSupported()) { + this.addHistory(loc); + this._hashChanged(loc); + } else { + global.location.hash = loc; + } } else { this._hashChanged(loc); } @@ -350,48 +351,22 @@ module.exports = kind( */ back: function () { if (this.useHistory) { - if (this._history.length >= 2) { - // we shift the current location off the stack - this._history.shift(); - // we shift the requested location off the stack - // but reapply it - this.location(this._history.shift()); - } + EnyoHistory.pop(); } }, /** - * Arbitrarily adds history. The optional second parameter may be set to a - * boolean `true` to add the location at the lowest (first) position in the - * stack, or to an integer indicating the exact position for the location in - * the stack. If the index is out of bounds, the location will be added at - * the lowest position (the same as if boolean `true` is passed as the second - * parameter). Returns callee for chaining. + * Arbitrarily adds history at the lowest (first) position in the stack. Returns callee for + * chaining. * * @param {String} location - The location string to add to the history. - * @param {(Number|Boolean)} [idx] - Position in the history stack where the - * new location should be added. Pass `true` for the first/oldest position, - * or a number indicating the index where the location should be added. If no - * value (or `undefined`) is specified, the location will be added at the - * last/most-recent position in the history stack. * @returns {this} The callee for chaining. * @public */ - addHistory: function (location, idx) { + addHistory: function (location) { if (this.useHistory) { - switch (typeof idx) { - case 'undefined': - this._history.unshift(location); - break; - case 'number': - if (idx >= 0 && idx < this._history.length) { - this._history.splice(idx, 0, location); - } - break; - case 'boolean': - this._history.push(location); - break; - } + if (location[0] !== '#') location = '#' + location; + EnyoHistory.push({context: this, handler: '_handlePopHistory', location: location}); } return this; }, @@ -403,7 +378,7 @@ module.exports = kind( * @public */ clearHistory: function () { - this._history = []; + EnyoHistory.clear(); return this; }, @@ -446,7 +421,6 @@ module.exports = kind( this._staticRoutes = {}; this._dynamicRoutes = []; this.routes = this.routes || []; - this._history = this._history || []; sup.apply(this, arguments); }; }), @@ -508,6 +482,16 @@ module.exports = kind( } }, + /** + * @private + */ + _handlePopHistory: function (entry) { + if (this.listening) { + var location = prepare(entry.location); + this.handle(location); + } + }, + /* * @private */ @@ -614,13 +598,8 @@ module.exports = kind( } }, - /* - * @private - */ - _currentChanged: function () { - if (this.useHistory) { - this._history.unshift(this.get('location')); - } + _currentChanged: function (was, is) { + this.log(was, is); } }); From 538fcad3e9c9a6b97247909405e054297d7f355d Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Tue, 21 Jul 2015 10:25:06 -0700 Subject: [PATCH 005/195] ENYO-2126: Always attempt to update the source, regardless of generated state. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- lib/Image/Image.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Image/Image.js b/lib/Image/Image.js index fdff569fb..f281a9c7e 100644 --- a/lib/Image/Image.js +++ b/lib/Image/Image.js @@ -247,8 +247,8 @@ module.exports = kind( if (this.sizing) { this.addClass(this.sizing); } + this.updateSource(); if (this.generated) { - this.updateSource(); this.render(); } }, From 531bfdab34a9f7623b6c7925ac05a85442f8c122 Mon Sep 17 00:00:00 2001 From: Gray Date: Wed, 22 Jul 2015 17:00:37 -0700 Subject: [PATCH 006/195] Fix selection support when using native JS data objects Enyo-DCO-1.1-Signed-Off-By: Gray Norton (gray.norton@lge.com) --- lib/DataRepeater.js | 12 +++++++++--- lib/VerticalDelegate.js | 15 ++++++++++----- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/DataRepeater.js b/lib/DataRepeater.js index d11551d83..434a0985a 100644 --- a/lib/DataRepeater.js +++ b/lib/DataRepeater.js @@ -586,7 +586,8 @@ var DataRepeater = module.exports = kind( var c = this.getChildForIndex(idx), s = this._selection, i = utils.indexOf(model, s), - dd = this.get('data'); + dd = this.get('data'), + p = this.selectionProperty; if (select) { if(i == -1) { @@ -608,8 +609,13 @@ var DataRepeater = module.exports = kind( if (c) { c.set('selected', select); } - if (this.selectionProperty && model) { - (s=this.selectionProperty) && model.set(s, select); + if (p && model) { + if (typeof model.set === 'function') { + model.set(p, select); + } + else { + model[p] = select; + } } this.notifyObservers('selected'); }, diff --git a/lib/VerticalDelegate.js b/lib/VerticalDelegate.js index d10cdf36c..089329261 100755 --- a/lib/VerticalDelegate.js +++ b/lib/VerticalDelegate.js @@ -213,11 +213,16 @@ module.exports = { * @private */ checkSelected: function (list, view) { - var s = list.selectionProperty; - if (s && view.model.get(s) && !list.isSelected(view.model)) { - list.select(view.index); - // don't have to check opposite case (model is false and isSelected is true) - // because that shouldn't happen + var p = list.selectionProperty, + m = view.model, + shouldBeSelected; + if (p) { + shouldBeSelected = (typeof m.get === 'function') ? m.get(p) : m[p]; + if (shouldBeSelected && !list.isSelected(m)) { + list.select(view.index); + // don't have to check opposite case (model is false and isSelected is true) + // because that shouldn't happen + } } }, From df5baf728d1026e9fa13c2ed6d1af957a6caa494 Mon Sep 17 00:00:00 2001 From: Gray Date: Fri, 24 Jul 2015 16:10:37 -0700 Subject: [PATCH 007/195] ENYO-2177: Invalidate attributes to ensure changes... ...will be preserved when re-rendering a Control. We were already doing this in the special case of the `classes` attribute, but not in the more general case, so post-render changes made via `setAttribute()` were being lost if the control were subsequently re-rendered. Enyo-DCO-1.1-Signed-Off-By: Gray Norton (gray.norton@lge.com) --- lib/Control.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Control.js b/lib/Control.js index 8803996fd..e7fcffd5f 100644 --- a/lib/Control.js +++ b/lib/Control.js @@ -453,7 +453,9 @@ var Control = module.exports = kind( if (value == null || value === false || value === '') { node.removeAttribute(name); } else node.setAttribute(name, value); - } else delegate.invalidate(this, 'attributes'); + } + + delegate.invalidate(this, 'attributes'); } return this; From bb467753e0b5cf67262369004543f392b937e0f4 Mon Sep 17 00:00:00 2001 From: Gray Date: Fri, 31 Jul 2015 20:21:17 -0700 Subject: [PATCH 008/195] ENYO-2227: RepeaterChildSupport mixin shouldn't stomp modelChanged The RepeaterChildSupport mixin implements a modelChanged method to update passive bindings, but this method unintentionally overrides any implementation of that method that may exist in the kind that applies the mixin. We need to have the mixin's implementation call the method inherited from the target kind. Enyo-DCO-1.1-Signed-Off-By: Gray Norton (gray.norton@lge.com) --- lib/RepeaterChildSupport.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/RepeaterChildSupport.js b/lib/RepeaterChildSupport.js index 8fa0a7e9f..46d665ba4 100644 --- a/lib/RepeaterChildSupport.js +++ b/lib/RepeaterChildSupport.js @@ -75,9 +75,12 @@ var RepeaterChildSupport = { * @method * @private */ - modelChanged: function () { - this.syncBindings(); - }, + modelChanged: kind.inherit(function (sup) { + return function () { + this.syncBindings(); + sup.apply(this, arguments); + }; + }), /* * @method From 4669e7fb06ccdb1fcceb4713ffeadb9d15274109 Mon Sep 17 00:00:00 2001 From: Anish_Ramesan Date: Mon, 3 Aug 2015 15:49:00 +0530 Subject: [PATCH 009/195] Initial commit - Added new "animation" module. - Added animation to UI components. Enyo-DCO-1.1-Signed-off-by: Anish Ramesan anish.ramesan@lge.com --- lib/UiComponent.js | 9 +- lib/animation/AnimationSupport.js | 134 ++++++++++++ lib/animation/Core.js | 164 +++++++++++++++ lib/animation/Frame.js | 324 ++++++++++++++++++++++++++++++ lib/animation/KeyFrame.js | 135 +++++++++++++ lib/animation/Tween.js | 103 ++++++++++ lib/animation/package.json | 3 + 7 files changed, 871 insertions(+), 1 deletion(-) create mode 100644 lib/animation/AnimationSupport.js create mode 100644 lib/animation/Core.js create mode 100644 lib/animation/Frame.js create mode 100644 lib/animation/KeyFrame.js create mode 100644 lib/animation/Tween.js create mode 100644 lib/animation/package.json diff --git a/lib/UiComponent.js b/lib/UiComponent.js index 1a393b47c..ce447d393 100644 --- a/lib/UiComponent.js +++ b/lib/UiComponent.js @@ -8,7 +8,8 @@ require('enyo'); var kind = require('./kind'), utils = require('./utils'), - master = require('./master'); + master = require('./master'), + AnimationSupport = require('./animation/AnimationSupport'); var Component = require('./Component'); @@ -128,6 +129,12 @@ var UiComponent = module.exports = kind( onresize: 'handleResize' }, + /** + * Adding animation support for controls + * @private + */ + mixins: [AnimationSupport], + /** * When set, provides a [control]{@link module:enyo/Control~Control} reference used to indicate where a * newly-created [component]{@link module:enyo/Component~Component} should be added in the diff --git a/lib/animation/AnimationSupport.js b/lib/animation/AnimationSupport.js new file mode 100644 index 000000000..9431945a7 --- /dev/null +++ b/lib/animation/AnimationSupport.js @@ -0,0 +1,134 @@ + +require('enyo'); + +var + kind = require('../kind'), + activator = require('./KeyFrame'), + frame = require('./Frame'), + utils = require('../utils'); + +var extend = kind.statics.extend; + +kind.concatenated.push('animation'); + +var AnimationSupport = { + + /** + * @private + */ + //name: 'AnimationSupport', + animating: false, + + /** + * @private + */ + ready: function() { + return this.generated && this.animating; + }, + + /** + * @private + */ + setInitial: function (initial) { + this._start = initial; + }, + + /** + * @private + */ + getDom: function() { + return this.hasNode && this.hasNode(); + }, + + /** + * @private + */ + getProperty: function() { + return this._prop || (this._prop = this.animate); + }, + + /** + * @private + */ + setProperty: function (newProp) { + this._prop = newProp; + }, + + /** + * @private + */ + getDuration: function() { + return this._duration || (this._duration = this.duration); + }, + + /** + * @private + */ + setDuration: function (newDuration) { + this._duration = newDuration; + }, + + /** + * @private + */ + completed: function() { + return this.onAnimated && this.onAnimated(this); + }, + + /** + * @public + */ + start: function () { + this._duration = parseInt(this.getDuration(), 10); + this._startTime = utils.perfNow(); + this._lastTime = this._startTime + this._duration; + this.animating = true; + }, + + /** + * @public + */ + stop: function () { + this.animating = false; + }, + + /** + * @private + */ + rendered: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + var dom = this.hasNode(), + prop = this.getProperty(); + if(dom && prop) { + var init = frame.getCompoutedProperty(dom, prop, this._start); + utils.mixin(this, init); + frame.accelerate(dom, this.matrix); + } + }; + }) +}; + +module.exports = AnimationSupport; + +/** + Hijacking original behaviour as in other Enyo supports. +*/ +var sup = kind.concatHandler; + +/** +* @private +*/ +kind.concatHandler = function (ctor, props, instance) { + sup.call(this, ctor, props, instance); + if (props.animate || props.keyFrame) { + var proto = ctor.prototype || ctor; + extend(AnimationSupport, proto); + if (props.keyFrame && typeof props.keyFrame != 'function') { + activator.animate(proto, props); + } + if (props.animate && typeof props.animate != 'function') { + activator.trigger(proto); + } + } +}; \ No newline at end of file diff --git a/lib/animation/Core.js b/lib/animation/Core.js new file mode 100644 index 000000000..7c09fc85c --- /dev/null +++ b/lib/animation/Core.js @@ -0,0 +1,164 @@ +/** +* Core module is responsible for handling all animations in the framework. +* The responsibilities of this module is to; +* 1. Trigger vendor specific rAF. +* 2. Knowing all elements which have requested for animation. +* 3. Requesting Animator to generate frames for each characters. +* 4. Idenfying initial state of an element. +* 5. Sperating multiple CSS properties values. +* +* As of now this module is exposed as an interface for application +* to directly trigger an animation. However, this will be later made private +* and will be accessible only by other interfaces exposed by framework. +* +* @public +*/ +require('enyo'); + +/** +* This module returns the Loop singleton +* @module enyo/AnimationCore +*/ +var + kind = require('../kind'), + animation = require('../animation'), + utils = require('../utils'); + +var + tween = require('./Tween'); + +var + CoreObject = require('../CoreObject'); + +module.exports = kind.singleton({ + /** @lends module:enyo/AnimationCore */ + + /** + * @private + */ + name: 'enyo.Core', + /** + * @private + */ + kind: CoreObject, + + /** + * @private + */ + chracs: [], + + /** + * @private + */ + req: 0, + + /** + * @private + */ + running: false, + + /** + * Animation Core base API to start core functionalities for animation. + * The purpose of this method is to check if the animation is already started or not + * otherwise wake up core to handle animation for a character. + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * @parameter charc- Animation character + * + * @public + */ + trigger: function (charc) { + if (!charc.animating) { + this.chracs.push(charc); + } + if (!this.running) { + this.running = true; + this.start(); + } + }, + + /** + * Animator public API to check if animation is happening on a particular + * document element. + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * @parameter charc- Animation character + * + * @public + */ + exists: function (eventTarget) { + for (var i = 0; i < this.chracs.length; i++) { + if (this.chracs[i].getDom() === eventTarget) { // Already Animating + return this.chracs[i]; + } + } + }, + + /** + * Animator public API to remove animation happening on a particular + * document element. + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * @parameter charc- Animation character + * + * @public + */ + remove: function (curr) { + this.chracs.splice(this.chracs.indexOf(curr), 1); + }, + + /** + * @private + */ + start: function () { + this.req = animation.requestAnimationFrame(this.bindSafely(this.loop)); + }, + + /** + * @private + */ + cancel: function () { + animation.cancelRequestAnimationFrame(this.req); + }, + + /** + * @private + */ + loop: function () { + var i, curr, + len = this.chracs.length, + ts = utils.perfNow(); + + if (len <= 0) { + this.cancel(); + this.running = false; + return; + } + + for (i = 0; i < len; i++) { + curr = this.chracs[i]; + if (curr && curr.ready()) { + tween.update(curr, ts); + if (ts >= curr._lastTime) { + this.remove(curr); + curr.completed(curr); + } + } + } + this.start(); + }, + + /** + * @private + */ + dummy: function () { + animation.requestAnimationFrame(function() {}); + } +}); + diff --git a/lib/animation/Frame.js b/lib/animation/Frame.js new file mode 100644 index 000000000..93809f496 --- /dev/null +++ b/lib/animation/Frame.js @@ -0,0 +1,324 @@ +require('enyo'); + +var + Dom = require('../dom'); +/** +* Frame is a module responsible for providing animation features required for a frame. +* This module exposes bunch of animation API's like trasnform, matrix calculation, +* fetching inital DOM properties and also applying style updates to DOM. +* +* @public +*/ +var frame = module.exports = { + /** + * @public + */ + translate: function (x, y, z) { + return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y ? y : 0, z ? z : 0, 1]; + }, + + /** + * @public + */ + translateX: function (x) { + return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x ? x : 0, 0, 0, 1]; + }, + + /** + * @public + */ + translateY: function (y) { + return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, y ? y : 0, 0, 1]; + }, + + /** + * @public + */ + translateZ: function (z) { + return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, z ? z : 0, 1]; + }, + + /** + * @public + */ + scale: function (x, y, z) { + return [x, 0, 0, 0, 0, y ? y : 1, 0, 0, 0, 0, z ? z : 1, 0, 0, 0, 0, 1]; + }, + + /** + * @public + */ + skew: function (a, b) { + a = a ? Math.tan(a * Math.PI / 180): 0; + b = b ? Math.tan(b * Math.PI / 180): 0; + return [1, b, 0, 0, a, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + }, + + /** + * @public + */ + rotateX: function (a) { + var cosa, sina; + a = a * Math.PI / 180; + cosa = Math.cos(a); + sina = Math.sin(a); + return [1, 0, 0, 0, 0, cosa, -sina, 0, 0, sina, cosa, 0, 0, 0, 0, 1]; + }, + + /** + * @public + */ + rotateY: function (b) { + var cosb, sinb; + b = b * Math.PI / 180; + cosb = Math.cos(b); + sinb = Math.sin(b); + return [cosb, 0, sinb, 0, 0, 1, 0, 0, -sinb, 0, cosb, 0, 0, 0, 0, 1]; + }, + + /** + * @public + */ + rotateZ: function (g) { + var cosg, sing; + g = g * Math.PI / 180; + cosg = Math.cos(g); + sing = Math.sin(g); + return [cosg, -sing, 0, 0, sing, cosg, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + }, + + /** + * @public + */ + rotate: function (a, b, g) { + a = a * Math.PI / 180; + b = b * Math.PI / 180; + g = g * Math.PI / 180; + var ca = Math.cos(a); + var sa = Math.sin(a); + var cb = Math.cos(b); + var sb = Math.sin(b); + var cg = Math.cos(g); + var sg = Math.sin(g); + var m = [ + cb * cg, + ca * sg + sa * sb * cg, + sa * sg - ca * sb * cg, + 0, + -cb * sg, + ca * cg - sa * sb * sg, + sa * cg + ca * sb * sg, + 0, + sb, + -sa * cb, + ca * cb, + 0, + 0, 0, 0, 1 + ]; + return m; + }, + + /** + * @public + */ + multiply: function(m1, m2) { + return [ + m1[0] * m2[0] + m1[4] * m2[1] + m1[8] * m2[2], + m1[1] * m2[0] + m1[5] * m2[1] + m1[9] * m2[2], + m1[2] * m2[0] + m1[6] * m2[1] + m1[10] * m2[2], + 0, + m1[0] * m2[4] + m1[4] * m2[5] + m1[8] * m2[6], + m1[1] * m2[4] + m1[5] * m2[5] + m1[9] * m2[6], + m1[2] * m2[4] + m1[6] * m2[5] + m1[10] * m2[6], + 0, + m1[0] * m2[8] + m1[4] * m2[9] + m1[8] * m2[10], + m1[1] * m2[8] + m1[5] * m2[9] + m1[9] * m2[10], + m1[2] * m2[8] + m1[6] * m2[9] + m1[10] * m2[10], + 0, + m1[0] * m2[12] + m1[4] * m2[13] + m1[8] * m2[14] + m1[12], + m1[1] * m2[12] + m1[5] * m2[13] + m1[9] * m2[14] + m1[13], + m1[2] * m2[12] + m1[6] * m2[13] + m1[10] * m2[14] + m1[14], + 1 + ]; + }, + + /** + * @public + */ + formatMatrix: function(m) { + var matrix = 'matrix3d('; + for (var i = 0; i < 15; i++) { + matrix += (m[i] < 0.000001 && m[i] > -0.000001) ? '0,' : m[i] + ','; + } + matrix += m[15] + ')'; + return matrix; + }, + + /** + * @public + */ + isTransform: function(transform) { + return this.TRANSFORM[transform]; + }, + + /** + * @public + */ + parseMatrix: function (v) { + var m = this.IDENTIY; + v = v.replace(/^\w*\(/, '').replace(')', ''); + v = this.parseValue(v); + if (v.length <= 6) { + m[0] = v[0]; + m[1] = v[1]; + m[4] = v[2]; + m[5] = v[3]; + m[12] = v[4]; + m[13] = v[5]; + } else { + m = v; + } + return m; + }, + + /** + * @public + */ + parseValue: function (val) { + return val.toString().split(",").map(function(v) { + return parseFloat(v, 10); + }); + }, + + /** + * @public + */ + getMatrix: function (style) { + var m = style.getPropertyValue('transform') || + style.getPropertyValue('-moz-transform') || + style.getPropertyValue('-webkit-transform') || + style.getPropertyValue('-ms-transform') || + style.getPropertyValue('-o-transform'); + if (m === undefined || m === null || m == "none") { + return ""; + } + return this.parseMatrix(m); + }, + + /** + * @public + */ + getStyleValue: function (style, key) { + var v = style.getPropertyValue(key); + if (v === undefined || v === null || v == "auto" || isNaN(v)) { + return 0; + } + if (frame.COLOR[key]) { + return v.replace(/^\w*\(/, '').replace(')', ''); + } + v = parseFloat(v, 10); + return v; + }, + + /** + * @public + */ + accelerate: function (ele, m) { + m = m ? m : this.IDENTIY; + this.setTransformProperty(ele, m); + }, + + /** + * @public + */ + setProperty: function (element, elProperty, elValue) { + if (this.COLOR[elProperty]) { + elValue = elValue.map(function(v) { + return parseInt(v, 10); + }); + element.style[elProperty] = "rgb("+ elValue + ")"; + } else if (elProperty == "opacity") { + var opacity = elValue[0].toFixed(6); + opacity = (opacity <= 0) ? '0.000001' : opacity; + element.style.opacity = opacity; + } else { + element.style[elProperty] = elValue[0] + "px"; + } + }, + + /** + * @public + */ + setTransformProperty: function (element, matrix) { + var mat = this.formatMatrix(matrix); + element.style.transform = mat; + element.style.webkitTransform = mat; + element.style.MozTransform = mat; + element.style.msTransform = mat; + element.style.OTransform = mat; + }, + + /** + * @public + */ + getCompoutedProperty: function (node, props, inital) { + var end = {}, + start = {}, + matrix = {}, + key, val, + style = Dom.getComputedStyle(node); + + //initilize start and end values + for (key in this.TRANSFORM) { + if (props[key]) { + start[key] = inital ? inital[key] : undefined; + end[key] = undefined; + } + } + + for (key in props) { + val = start[key]; + if (!val) { + if (this.isTransform(key)) { + matrix = this.getMatrix(style); + val = ""; + } else { + val = this.getStyleValue(style, key); + } + } + end[key] = this.parseValue(props[key]); + start[key] = this.parseValue(val); + } + + return {_start: start, _end: end, _matrix: matrix}; + } +}; + +/** +* @private +*/ +frame.TRANSFORM = { + "translate": 1, + "translateX": 1, + "translateY": 1, + "translateZ": 1, + "rotateX": 1, + "rotateY": 1, + "rotateZ": 1, + "rotate": 1, + "skew": 1, + "scale": 1 +}; + +/** +* @private +*/ +frame.COLOR = { + "color" : 1, + "background-color": 1 +}; + +/** +* @private +*/ +frame.IDENTIY = [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]; \ No newline at end of file diff --git a/lib/animation/KeyFrame.js b/lib/animation/KeyFrame.js new file mode 100644 index 000000000..478dbff48 --- /dev/null +++ b/lib/animation/KeyFrame.js @@ -0,0 +1,135 @@ + +require('enyo'); + +/** +* This module returns the Loop singleton +* @module enyo/Core +*/ +var + kind = require('../kind'), + animation = require('./Core'), + frame = require('./Frame'), + utils = require('../utils'); + +var + CoreObject = require('../CoreObject'); + +var AnimationKeyFrame = module.exports = kind.singleton({ + /** @lends module:enyo/AnimatioKeyFrame */ + + /** + * @private + */ + name: 'enyo.KeyFrame', + /** + * @private + */ + kind: CoreObject, + + /** + * KeyFrame base API to perform animation on any document element + * repersented as a Character. The purpose of this method is to add a new + * character to Animation Core based on animation properties passed as + * parameter to this function and also to manage the frames allocated to + * each of individual poses. + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * @parameter domObject- Document element on which animation will be performed. + * duration- Duration for the animation. + * keyframe- Key frame Animation propeties represented as CSS objects. + * like: {0: {"rotateX": "0"}, 50: {"rotateX": "90"}, 100: {"rotateX": "180"}} + * complete- Call back triggered when an animation is completed. + * @public + */ + animate: function (charc, proto) { + var prop; + //if (animation.exists(charc.getDom())) return; + + //charc = new animation.Character(domObject, null, null, this.reframe); + var keyframeCallback = proto.completed; + charc.keyframeCallback = keyframeCallback; + charc.initialTime = utils.perfNow(); + charc.totalDuration = proto.duration; + charc.keyProps = []; + charc.keyTime = []; + var keyframe = proto.keyFrame; + + for (prop in keyframe) { + charc.keyTime.push(prop); + charc.keyProps.push(keyframe[prop]); + } + charc.completed = this.bindSafely(this.reframe); + + this.keyFraming(charc); + }, + + /** + * KeyFrame's public API to reverse an animation. + * The purpose of this method is to find the animating character based on + * the DOM provided and reversing a keyframe animation by interchanging its intial + * state with final state and final state with current state + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * @parameter dom- Document element on which animation will be reversed. + * + * @public + */ + reverse: function (dom) { + var charc = animation.exists(dom), + finalState, duration; + if (charc) { + finalState = charc._start; + duration = utils.perfNow() - charc.initialTime; + animation.remove(charc); + + charc.setProperty(finalState); + charc.setInitial(charc.currentState); + charc.setDuration(duration); + charc.totalDuration = duration; + charc.keyProps = []; + charc.keyTime = []; + charc.animating = false; + animation.trigger(charc); + } + }, + + trigger: function (charc) { + animation.trigger(charc); + } +}); + +/** +* @private +*/ +AnimationKeyFrame.keyFraming = function (charc, callback) { + var index = charc.currentIndex || 0; + var time = charc.currentIndex ? + charc.totalDuration * ((charc.keyTime[index] - charc.keyTime[index -1])/100) : "0"; + + charc.setProperty(charc.keyProps[index]); + charc.setInitial(index > 0 ? charc.keyProps[index -1] : {}); + charc.setDuration(time); + charc.animating = false; + charc.currentIndex = index; + animation.trigger(charc); +}; + +/** +* @private +*/ +AnimationKeyFrame.reframe = function (charc) { + charc.currentIndex++; + if (charc.currentIndex < charc.keyTime.length) { + this.keyFraming(charc); + var init = frame.getCompoutedProperty(charc.getDom(), charc.getProperty(), charc._start); + utils.mixin(charc, init); + charc.start(); + } else { + //Tigerring callback function at end of animation + charc.keyframeCallback && charc.keyframeCallback(this); + } +}; diff --git a/lib/animation/Tween.js b/lib/animation/Tween.js new file mode 100644 index 000000000..33d9f2963 --- /dev/null +++ b/lib/animation/Tween.js @@ -0,0 +1,103 @@ +require('enyo'); + +var + frame = require('./Frame'); +/** +* Animator is a module responsible for animation happening on a Character(described below). +* The responsibilities of this module is to; +* 1. Create new characters for animation. +* 2. Add new characters who are willing to get animated. +* 3. Interpolating current state of character. +* 4. Update DOM based on current state, using matrix for tranform and styles for others. +* +* As of now this module is exposed as an interface for application +* to directly trigger an animation. However, this will be later made private +* and will be accessible only by other interfaces exposed by framework. +* +* @public +*/ +module.exports = { + /** + * Animator public API which notifies Animator to change current state of + * a character. This method is normally trigger by the Animation Core to + * update the animating characters state based on the current timestamp. + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * @parameter chrac- Animating character + * ts- DOMHighResTimeStamp + * + * @public + */ + update: function (charc, ts) { + var t, + dur = charc._duration, + since = ts - charc._startTime; + + if (since < 0) return; + if (since < dur) { + t = since / dur; + this.step(charc, t); + } else { + this.step(charc); + } + }, + + /** + * @private + */ + step: function(charc, t) { + var key, i, + matrix, + start, end, + states = [], + dom = charc.getDom(), + prop = charc.getProperty(), + fMatrix = frame.IDENTIY, + iMatrix = charc._matrix || frame.IDENTIY; + + charc.currentState = {}; + for (key in prop) { + start = charc._start[key]; + end = charc._end[key]; + for (i = 0; i < end.length; i++) { + states[i] = t ? this.interpolate(start[i], end[i], this.ease(t)): end[i]; + } + if (frame.isTransform(key)) { + matrix = frame[key](states[0], states[1], states[2]); + fMatrix = frame.multiply(matrix, iMatrix); + iMatrix = fMatrix; + } else { + frame.setProperty(dom, key, states); + } + charc.currentState[key] = states; + } + + if (fMatrix) this.accelerate(dom, fMatrix); + }, + + /** + * @private + */ + ease: function (t) { + return t; + }, + + /** + * @private + */ + interpolate: function(a, b, t) { + a = parseFloat(a || 0, 10); + b = parseFloat(b || 0, 10); + return ((1 - t) * a) + (t * b); + }, + + /** + * @private + */ + accelerate: function (ele, m) { + m = m ? m : frame.IDENTIY; + frame.setTransformProperty(ele, m); + } +}; \ No newline at end of file diff --git a/lib/animation/package.json b/lib/animation/package.json new file mode 100644 index 000000000..8454c0c27 --- /dev/null +++ b/lib/animation/package.json @@ -0,0 +1,3 @@ +{ + "main": "AnimationSupport.js" +} From f69d7e19a526c2baf95e5221ae4cd880e6e7e0d4 Mon Sep 17 00:00:00 2001 From: Anish_Ramesan Date: Mon, 3 Aug 2015 19:39:55 +0530 Subject: [PATCH 010/195] Updated path Enyo-DCO-1.1-Signed-off-by: Anish Ramesan anish.ramesan@lge.com --- lib/{animation => AnimationSupport}/AnimationSupport.js | 0 lib/{animation => AnimationSupport}/Core.js | 0 lib/{animation => AnimationSupport}/Frame.js | 0 lib/{animation => AnimationSupport}/KeyFrame.js | 0 lib/{animation => AnimationSupport}/Tween.js | 0 lib/{animation => AnimationSupport}/package.json | 0 lib/UiComponent.js | 2 +- 7 files changed, 1 insertion(+), 1 deletion(-) rename lib/{animation => AnimationSupport}/AnimationSupport.js (100%) rename lib/{animation => AnimationSupport}/Core.js (100%) rename lib/{animation => AnimationSupport}/Frame.js (100%) rename lib/{animation => AnimationSupport}/KeyFrame.js (100%) rename lib/{animation => AnimationSupport}/Tween.js (100%) rename lib/{animation => AnimationSupport}/package.json (100%) diff --git a/lib/animation/AnimationSupport.js b/lib/AnimationSupport/AnimationSupport.js similarity index 100% rename from lib/animation/AnimationSupport.js rename to lib/AnimationSupport/AnimationSupport.js diff --git a/lib/animation/Core.js b/lib/AnimationSupport/Core.js similarity index 100% rename from lib/animation/Core.js rename to lib/AnimationSupport/Core.js diff --git a/lib/animation/Frame.js b/lib/AnimationSupport/Frame.js similarity index 100% rename from lib/animation/Frame.js rename to lib/AnimationSupport/Frame.js diff --git a/lib/animation/KeyFrame.js b/lib/AnimationSupport/KeyFrame.js similarity index 100% rename from lib/animation/KeyFrame.js rename to lib/AnimationSupport/KeyFrame.js diff --git a/lib/animation/Tween.js b/lib/AnimationSupport/Tween.js similarity index 100% rename from lib/animation/Tween.js rename to lib/AnimationSupport/Tween.js diff --git a/lib/animation/package.json b/lib/AnimationSupport/package.json similarity index 100% rename from lib/animation/package.json rename to lib/AnimationSupport/package.json diff --git a/lib/UiComponent.js b/lib/UiComponent.js index ce447d393..433f3706e 100644 --- a/lib/UiComponent.js +++ b/lib/UiComponent.js @@ -9,7 +9,7 @@ var kind = require('./kind'), utils = require('./utils'), master = require('./master'), - AnimationSupport = require('./animation/AnimationSupport'); + AnimationSupport = require('./AnimationSupport/AnimationSupport'); var Component = require('./Component'); From d47265a32c7991832bbb3feb538f2d58e9284ed2 Mon Sep 17 00:00:00 2001 From: Anish_Ramesan Date: Mon, 3 Aug 2015 22:29:36 +0530 Subject: [PATCH 011/195] Updated doc Enyo-DCO-1.1-Signed-off-by: Anish Ramesan anish.ramesan@lge.com --- lib/AnimationSupport/AnimationSupport.js | 57 ++++++++++++++++-------- lib/AnimationSupport/Core.js | 26 ++++------- lib/AnimationSupport/Frame.js | 6 ++- lib/AnimationSupport/KeyFrame.js | 41 +++++++---------- lib/AnimationSupport/Tween.js | 16 +++---- 5 files changed, 73 insertions(+), 73 deletions(-) diff --git a/lib/AnimationSupport/AnimationSupport.js b/lib/AnimationSupport/AnimationSupport.js index 9431945a7..5830d0ac4 100644 --- a/lib/AnimationSupport/AnimationSupport.js +++ b/lib/AnimationSupport/AnimationSupport.js @@ -20,62 +20,87 @@ var AnimationSupport = { animating: false, /** - * @private + * Check if the character is suitable for animation + * @public */ ready: function() { return this.generated && this.animating; }, /** - * @private + * Sets current animation state for this character + * @public */ setInitial: function (initial) { this._start = initial; }, /** - * @private + * Gets current state of animation for this character + * @parameter accelerate- Turns on/off hardware acceleration + * @public + */ + getInitial: function (accelerate) { + var dom = this.hasNode(), + prop = this.getAnimation(), + init = frame.getCompoutedProperty(dom, prop, this._start); + + utils.mixin(this, init); + if (accelerate) frame.accelerate(dom, this.matrix); + + }, + + /** + * Gets HTML representation of this character + * @public */ getDom: function() { return this.hasNode && this.hasNode(); }, /** - * @private + * Gets animations applied to this chracter. + * @public */ - getProperty: function() { + getAnimation: function() { return this._prop || (this._prop = this.animate); }, /** - * @private + * Sets new animation for this character. + * @public */ - setProperty: function (newProp) { + setAnimation: function (newProp) { this._prop = newProp; }, /** - * @private + * Gets how long animation is active on this character + * @public */ getDuration: function() { return this._duration || (this._duration = this.duration); }, /** - * @private + * Sets how long animation should be active on this character + * @public */ setDuration: function (newDuration) { this._duration = newDuration; }, /** - * @private + * Idnetify when the character has done animating. + * This triggers "onAnimated" event on this character + * @public */ completed: function() { return this.onAnimated && this.onAnimated(this); }, /** + * Trigger animation for this character. * @public */ start: function () { @@ -83,12 +108,14 @@ var AnimationSupport = { this._startTime = utils.perfNow(); this._lastTime = this._startTime + this._duration; this.animating = true; + this.getInitial(false); }, /** + * Halt existing animation of this character * @public */ - stop: function () { + pause: function () { this.animating = false; }, @@ -98,13 +125,7 @@ var AnimationSupport = { rendered: kind.inherit(function (sup) { return function () { sup.apply(this, arguments); - var dom = this.hasNode(), - prop = this.getProperty(); - if(dom && prop) { - var init = frame.getCompoutedProperty(dom, prop, this._start); - utils.mixin(this, init); - frame.accelerate(dom, this.matrix); - } + this.getInitial(true); }; }) }; diff --git a/lib/AnimationSupport/Core.js b/lib/AnimationSupport/Core.js index 7c09fc85c..4ec9837c7 100644 --- a/lib/AnimationSupport/Core.js +++ b/lib/AnimationSupport/Core.js @@ -1,15 +1,9 @@ /** -* Core module is responsible for handling all animations in the framework. +* Core module is responsible for handling all animations happening in Enyo. * The responsibilities of this module is to; -* 1. Trigger vendor specific rAF. -* 2. Knowing all elements which have requested for animation. -* 3. Requesting Animator to generate frames for each characters. -* 4. Idenfying initial state of an element. -* 5. Sperating multiple CSS properties values. -* -* As of now this module is exposed as an interface for application -* to directly trigger an animation. However, this will be later made private -* and will be accessible only by other interfaces exposed by framework. +* - Trigger vendor specific rAF. +* - Knowing all elements which have requested for animation. +* - Tween animation frames for each characters. * * @public */ @@ -17,21 +11,19 @@ require('enyo'); /** * This module returns the Loop singleton -* @module enyo/AnimationCore +* @module enyo/Core */ var kind = require('../kind'), animation = require('../animation'), - utils = require('../utils'); - -var + utils = require('../utils'), tween = require('./Tween'); var CoreObject = require('../CoreObject'); module.exports = kind.singleton({ - /** @lends module:enyo/AnimationCore */ + /** @lends module:enyo/Core */ /** * @private @@ -58,7 +50,7 @@ module.exports = kind.singleton({ running: false, /** - * Animation Core base API to start core functionalities for animation. + * Core base API to start animation functionalities. * The purpose of this method is to check if the animation is already started or not * otherwise wake up core to handle animation for a character. * @@ -80,7 +72,7 @@ module.exports = kind.singleton({ }, /** - * Animator public API to check if animation is happening on a particular + * Core public API to check if core is handling animation for particular * document element. * * As of now this method is provided as an interface for application diff --git a/lib/AnimationSupport/Frame.js b/lib/AnimationSupport/Frame.js index 93809f496..f000f8c5d 100644 --- a/lib/AnimationSupport/Frame.js +++ b/lib/AnimationSupport/Frame.js @@ -4,7 +4,7 @@ var Dom = require('../dom'); /** * Frame is a module responsible for providing animation features required for a frame. -* This module exposes bunch of animation API's like trasnform, matrix calculation, +* This module exposes bunch of animation API's like transform, matrix calculation, * fetching inital DOM properties and also applying style updates to DOM. * * @public @@ -262,9 +262,11 @@ var frame = module.exports = { * @public */ getCompoutedProperty: function (node, props, inital) { + if(!node && !props) return; + var end = {}, start = {}, - matrix = {}, + matrix = "", key, val, style = Dom.getComputedStyle(node); diff --git a/lib/AnimationSupport/KeyFrame.js b/lib/AnimationSupport/KeyFrame.js index 478dbff48..8f8884b09 100644 --- a/lib/AnimationSupport/KeyFrame.js +++ b/lib/AnimationSupport/KeyFrame.js @@ -1,21 +1,18 @@ require('enyo'); -/** -* This module returns the Loop singleton -* @module enyo/Core -*/ var kind = require('../kind'), animation = require('./Core'), - frame = require('./Frame'), - utils = require('../utils'); - -var + utils = require('../utils'), CoreObject = require('../CoreObject'); -var AnimationKeyFrame = module.exports = kind.singleton({ - /** @lends module:enyo/AnimatioKeyFrame */ +/** +* This module returns the Loop singleton +* @module enyo/KeyFrame +*/ +var keyFrame = module.exports = kind.singleton({ + /** @lends module:enyo/KeyFrame */ /** * @private @@ -36,25 +33,21 @@ var AnimationKeyFrame = module.exports = kind.singleton({ * As of now this method is provided as an interface for application * to directly trigger an animation. However, this will be later made private * and will be accessible only by the interfaces exposed by framework. - * @parameter domObject- Document element on which animation will be performed. - * duration- Duration for the animation. + * @parameter charc- Character responsible for animation. * keyframe- Key frame Animation propeties represented as CSS objects. * like: {0: {"rotateX": "0"}, 50: {"rotateX": "90"}, 100: {"rotateX": "180"}} - * complete- Call back triggered when an animation is completed. * @public */ animate: function (charc, proto) { - var prop; - //if (animation.exists(charc.getDom())) return; + var prop, + cb = proto.completed, + keyframe = proto.keyFrame; - //charc = new animation.Character(domObject, null, null, this.reframe); - var keyframeCallback = proto.completed; - charc.keyframeCallback = keyframeCallback; + charc.keyframeCallback = cb; charc.initialTime = utils.perfNow(); charc.totalDuration = proto.duration; charc.keyProps = []; charc.keyTime = []; - var keyframe = proto.keyFrame; for (prop in keyframe) { charc.keyTime.push(prop); @@ -86,7 +79,7 @@ var AnimationKeyFrame = module.exports = kind.singleton({ duration = utils.perfNow() - charc.initialTime; animation.remove(charc); - charc.setProperty(finalState); + charc.setAnimation(finalState); charc.setInitial(charc.currentState); charc.setDuration(duration); charc.totalDuration = duration; @@ -105,12 +98,12 @@ var AnimationKeyFrame = module.exports = kind.singleton({ /** * @private */ -AnimationKeyFrame.keyFraming = function (charc, callback) { +keyFrame.keyFraming = function (charc, callback) { var index = charc.currentIndex || 0; var time = charc.currentIndex ? charc.totalDuration * ((charc.keyTime[index] - charc.keyTime[index -1])/100) : "0"; - charc.setProperty(charc.keyProps[index]); + charc.setAnimation(charc.keyProps[index]); charc.setInitial(index > 0 ? charc.keyProps[index -1] : {}); charc.setDuration(time); charc.animating = false; @@ -121,12 +114,10 @@ AnimationKeyFrame.keyFraming = function (charc, callback) { /** * @private */ -AnimationKeyFrame.reframe = function (charc) { +keyFrame.reframe = function (charc) { charc.currentIndex++; if (charc.currentIndex < charc.keyTime.length) { this.keyFraming(charc); - var init = frame.getCompoutedProperty(charc.getDom(), charc.getProperty(), charc._start); - utils.mixin(charc, init); charc.start(); } else { //Tigerring callback function at end of animation diff --git a/lib/AnimationSupport/Tween.js b/lib/AnimationSupport/Tween.js index 33d9f2963..f58716dd8 100644 --- a/lib/AnimationSupport/Tween.js +++ b/lib/AnimationSupport/Tween.js @@ -3,22 +3,16 @@ require('enyo'); var frame = require('./Frame'); /** -* Animator is a module responsible for animation happening on a Character(described below). +* Tween is a module responsible for creating intermediate frames for an animation. * The responsibilities of this module is to; -* 1. Create new characters for animation. -* 2. Add new characters who are willing to get animated. -* 3. Interpolating current state of character. -* 4. Update DOM based on current state, using matrix for tranform and styles for others. -* -* As of now this module is exposed as an interface for application -* to directly trigger an animation. However, this will be later made private -* and will be accessible only by other interfaces exposed by framework. +* - Interpolating current state of character. +* - Update DOM based on current state, using matrix for tranform and styles for others. * * @public */ module.exports = { /** - * Animator public API which notifies Animator to change current state of + * Tweens public API which notifies to change current state of * a character. This method is normally trigger by the Animation Core to * update the animating characters state based on the current timestamp. * @@ -53,7 +47,7 @@ module.exports = { start, end, states = [], dom = charc.getDom(), - prop = charc.getProperty(), + prop = charc.getAnimation(), fMatrix = frame.IDENTIY, iMatrix = charc._matrix || frame.IDENTIY; From 9ec962c1f2d7cf4216649891b0f005f7bdd31f57 Mon Sep 17 00:00:00 2001 From: Blake Stephens Date: Wed, 15 Jul 2015 10:43:37 -0700 Subject: [PATCH 012/195] ENYO-2062: Corrected fullscreen pseudo selector to match W3 spec Also updated IE's fullscreen pseudo-selector. More details: https://developer.mozilla.org/en-US/docs/Web/CSS/:fullscreen Enyo-DCO-1.1-Signed-off-by: Blake Stephens --- css/mixins.less | 15 ++++++++++++--- lib/fullscreen/fullscreen.css | 33 --------------------------------- lib/fullscreen/fullscreen.less | 19 +++++++++++++++++++ lib/fullscreen/package.json | 2 +- 4 files changed, 32 insertions(+), 37 deletions(-) delete mode 100644 lib/fullscreen/fullscreen.css create mode 100644 lib/fullscreen/fullscreen.less diff --git a/css/mixins.less b/css/mixins.less index 37afdb723..09c979e56 100644 --- a/css/mixins.less +++ b/css/mixins.less @@ -2,7 +2,7 @@ .margin-start-end (@start, @end) { margin-left: @start; margin-right: @end; - + .enyo-locale-right-to-left & { margin-left: @end; margin-right: @start; @@ -13,9 +13,18 @@ .padding-start-end (@start, @end) { padding-left: @start; padding-right: @end; - + .enyo-locale-right-to-left & { padding-left: @end; padding-right: @start; } -} \ No newline at end of file +} + +// Provide a set of rules to assign to each vendor-prefixed pseudo selector +.vendor-fullscreen(@rule) { + &:-webkit-full-screen { @rule(); } + &:-moz-full-screen { @rule(); } + &:-ms-fullscreen { @rule(); } + &:-o-full-screen { @rule(); } + &:fullscreen { @rule(); } +} diff --git a/lib/fullscreen/fullscreen.css b/lib/fullscreen/fullscreen.css deleted file mode 100644 index d3674364e..000000000 --- a/lib/fullscreen/fullscreen.css +++ /dev/null @@ -1,33 +0,0 @@ - -/* Fullscreen CSS */ -:-webkit-full-screen { - width: 100% !important; - height: 100% !important; -} -:-moz-full-screen { - width: 100% !important; - height: 100% !important; -} -:-ms-full-screen { - width: 100% !important; - height: 100% !important; -} -:-o-full-screen { - width: 100% !important; - height: 100% !important; -} -:full-screen { - width: 100% !important; - height: 100% !important; -} -/* Fallback Fullscreen CSS */ -body .enyo-fullscreen { - position: absolute !important; - left: 0 !important; - top: 0 !important; - right: 0 !important; - bottom: 0 !important; - width: 100% !important; - height: 100% !important; - box-sizing: border-box !important; -} \ No newline at end of file diff --git a/lib/fullscreen/fullscreen.less b/lib/fullscreen/fullscreen.less new file mode 100644 index 000000000..865f4470b --- /dev/null +++ b/lib/fullscreen/fullscreen.less @@ -0,0 +1,19 @@ +/* Fullscreen */ + +.enyo-fullsize() { + width: 100% !important; + height: 100% !important; +} + +.vendor-fullscreen({ .enyo-fullsize; }); + +/* Fallback Fullscreen */ +body .enyo-fullscreen { + position: absolute !important; + left: 0 !important; + top: 0 !important; + right: 0 !important; + bottom: 0 !important; + .enyo-fullsize; + box-sizing: border-box !important; +} \ No newline at end of file diff --git a/lib/fullscreen/package.json b/lib/fullscreen/package.json index edc423f99..29aabd187 100644 --- a/lib/fullscreen/package.json +++ b/lib/fullscreen/package.json @@ -1,6 +1,6 @@ { "main": "fullscreen.js", "styles": [ - "fullscreen.css" + "fullscreen.less" ] } From f2caa1d0f0cdb7bff20b5911594eef75b0bea0d0 Mon Sep 17 00:00:00 2001 From: Ryan Duffy Date: Mon, 3 Aug 2015 16:29:08 -0500 Subject: [PATCH 013/195] prevent setting scrollTop/scrollLeft on Scrollables Issue: ENYO-2223 Enyo-DCO-1.1-Signed-off-by: Ryan Duffy (ryan.duffy@lge.com) --- lib/Scrollable/Scrollable.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Scrollable/Scrollable.js b/lib/Scrollable/Scrollable.js index dc7555165..15bee5b83 100644 --- a/lib/Scrollable/Scrollable.js +++ b/lib/Scrollable/Scrollable.js @@ -222,6 +222,7 @@ module.exports = { defProps = this.defaultProps; this.defaultProps = {}; + this.accessibilityPreventScroll = true; this.createComponents(smc, {isChrome: true, owner: this}); if (this.scrollControls) { From fe6a58ffb53c9c2bf340a26e08c30f5443846979 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Wed, 1 Jul 2015 23:17:24 -0700 Subject: [PATCH 014/195] ENYO-1981: Add enyo/LightPanel kind. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- lib/LightPanels/LightPanel.js | 60 ++++++++++++++++++++++++++++++++++ lib/LightPanels/LightPanels.js | 10 ++++++ 2 files changed, 70 insertions(+) create mode 100644 lib/LightPanels/LightPanel.js diff --git a/lib/LightPanels/LightPanel.js b/lib/LightPanels/LightPanel.js new file mode 100644 index 000000000..ea7b401af --- /dev/null +++ b/lib/LightPanels/LightPanel.js @@ -0,0 +1,60 @@ +require('enyo'); + +var + kind = require('../kind'); + +var + Control = require('../Control'); + +/** +* A light-weight panels implementation that has basic support for side-to-side transitions +* between child components. +* +* @class LightPanel +* @extends module:enyo/Control~Control +* @ui +* @public +*/ +module.exports = kind( + /** @lends module:enyo/LightPanel~LightPanel.prototype */ { + + /** + * @private + */ + name: 'enyo.LightPanel', + + /** + * @private + */ + kind: Control, + + /** + * @private + * @lends module:enyo/LightPanels~LightPanels.prototype + */ + published: { + + /** + * Is `true` if the panel is currently active; `false` otherwise. + * + * @type {Boolean} + * @default false + * @public + */ + active: false + }, + + /** + * This overridable method is called before a transition. + * + * @public + */ + preTransition: function () {}, + + /** + * This overridable method is called after a transition. + * + * @public + */ + postTransition: function () {} +}); \ No newline at end of file diff --git a/lib/LightPanels/LightPanels.js b/lib/LightPanels/LightPanels.js index 3c4cd9295..6b376e6a1 100644 --- a/lib/LightPanels/LightPanels.js +++ b/lib/LightPanels/LightPanels.js @@ -16,6 +16,9 @@ var ViewPreloadSupport = require('../ViewPreloadSupport'), TaskManagerSupport = require('../TaskManagerSupport'); +var + LightPanel = require('./LightPanel'); + /** * The configurable options used by {@link module:enyo/LightPanels~LightPanels} when pushing panels. * @@ -60,6 +63,11 @@ module.exports = kind( */ classes: 'enyo-light-panels', + /** + * @private + */ + defaultKind: LightPanel, + /** * @private * @lends module:enyo/LightPanels~LightPanels.prototype @@ -711,3 +719,5 @@ module.exports = kind( } }); + +module.exports.Panel = LightPanel; \ No newline at end of file From 0428a85d00ee1faa9b511989aca14eea8ede8b62 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Thu, 2 Jul 2015 00:05:35 -0700 Subject: [PATCH 015/195] ENYO-1981: Refactorization, clean-up, and prevention of pushing while transitioning. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- lib/LightPanels/LightPanel.js | 19 +- lib/LightPanels/LightPanels.js | 307 ++++++++++++++++++--------------- 2 files changed, 172 insertions(+), 154 deletions(-) diff --git a/lib/LightPanels/LightPanel.js b/lib/LightPanels/LightPanel.js index ea7b401af..e98b501a4 100644 --- a/lib/LightPanels/LightPanel.js +++ b/lib/LightPanels/LightPanel.js @@ -29,20 +29,13 @@ module.exports = kind( kind: Control, /** - * @private - * @lends module:enyo/LightPanels~LightPanels.prototype + * Is `true` if the panel is currently active; `false` otherwise. + * + * @type {Boolean} + * @default false + * @public */ - published: { - - /** - * Is `true` if the panel is currently active; `false` otherwise. - * - * @type {Boolean} - * @default false - * @public - */ - active: false - }, + active: false, /** * This overridable method is called before a transition. diff --git a/lib/LightPanels/LightPanels.js b/lib/LightPanels/LightPanels.js index 6b376e6a1..c52f444cb 100644 --- a/lib/LightPanels/LightPanels.js +++ b/lib/LightPanels/LightPanels.js @@ -8,8 +8,7 @@ require('enyo'); var kind = require('../kind'), dom = require('../dom'), - utils = require('../utils'), - asyncMethod = utils.asyncMethod; + asyncMethod = require('../utils').asyncMethod; var Control = require('../Control'), @@ -19,6 +18,26 @@ var var LightPanel = require('./LightPanel'); +/** +* @enum {Number} +* @memberof module:moonstone/LightPanels~LightPanels +* @public +*/ +var Direction = { + FORWARDS: 1, + BACKWARDS: -1 +}; + +/** +* @enum {Number} +* @memberof module:moonstone/LightPanels~LightPanels +* @public +*/ +var Orientation = { + HORIZONTAL: 'X', + VERTICAL: 'Y' +}; + /** * The configurable options used by {@link module:enyo/LightPanels~LightPanels} when pushing panels. * @@ -69,97 +88,88 @@ module.exports = kind( defaultKind: LightPanel, /** - * @private - * @lends module:enyo/LightPanels~LightPanels.prototype - */ - published: { - - /** - * The index of the active panel. Changing this value will trigger a non-animated transition - * to the new index. - * - * @type {Number} - * @default -1 - * @public - */ - index: -1, - - /** - * Indicates whether the panels animate when transitioning. This applies to the - * [next]{module:enyo/LightPanels#next}, [previous]{module:enyo/LightPanels#previous}, - * [pushPanel]{module:enyo/LightPanels#pushPanel}, and - * [pushPanels]{module:enyo/LightPanels#pushPanels} methods. - * - * @type {Boolean} - * @default true - * @public - */ - animate: true, - - /** - * Indicates whether panels "wrap around" when moving past the end. - * - * @type {Boolean} - * @default false - * @public - */ - wrap: false, - - /** - * When `true`, panels are automatically popped when the user moves back. - * - * @type {Boolean} - * @default true - * @public - */ - popOnBack: true, - - /** - * When `true`, panels are automatically popped when the user moves forward (they remain - * components of the parent to maintain the proper hierarchy). - * - * @type {Boolean} - * @default true - * @public - */ - popOnForward: true, - - /** - * The amount of time, in milliseconds, to run the transition animation between panels. - * - * @type {Number} - * @default 250 - * @public - */ - duration: 250, - - /** - * The timing function to be applied to the transition animation between panels. - * - * @type {String} - * @default 'ease-out' - * @public - */ - timingFunction: 'ease-out', - - /** - * The orientation of the panels. Possible values are 'vertical' and 'horizontal'. - * - * @type {String} - * @default 'horizontal' - * @public - */ - orientation: 'horizontal', - - /** - * The direction of the panel movement. Possible values are 'forwards' and 'backwards'. - * - * @type {String} - * @default 'forwards' - * @public - */ - direction: 'forwards' - }, + * The index of the active panel. + * + * @type {Number} + * @default -1 + * @public + */ + index: -1, + + /** + * Indicates whether the panels animate when transitioning. + * + * @type {Boolean} + * @default true + * @public + */ + animate: true, + + /** + * Indicates whether panels "wrap around" when moving past the end. + * + * @type {Boolean} + * @default false + * @public + */ + wrap: false, + + /** + * When `true`, previous panels are automatically popped when moving backwards. + * + * @type {Boolean} + * @default true + * @public + */ + popOnBack: true, + + /** + * When `true`, previous panels are automatically popped when moving forwards. + * + * @type {Boolean} + * @default false + * @public + */ + popOnForward: false, + + /** + * The amount of time, in milliseconds, to run the transition animation between panels. + * + * @type {Number} + * @default 250 + * @public + */ + duration: 250, + + /** + * The timing function to be applied to the transition animation between panels. Please refer + * to https://developer.mozilla.org/en-US/docs/Web/CSS/transition-timing-function. + * + * @type {String} + * @default 'ease-out' + * @public + */ + timingFunction: 'ease-out', + + /** + * The orientation of the panels. Possible values from + * {@link module:moonstone/LightPanels~Orientation}. + * + * @type {String} + * @default Orientation.HORIZONTAL + * @public + */ + orientation: Orientation.HORIZONTAL, + + /** + * The direction of the panel movement. Possible values from + * {@link module:moonstone/LightPanels~Direction}. + * + * @type {String} + * @default Direction.FORWARDS + * @public + */ + direction: Direction.FORWARDS, /** * @private @@ -175,10 +185,6 @@ module.exports = kind( create: kind.inherit(function (sup) { return function () { sup.apply(this, arguments); - - this.addClass(this.orientation); - this.addClass(this.direction); - this.orientationChanged(); this.directionChanged(); this.indexChanged(); @@ -196,17 +202,28 @@ module.exports = kind( /** * @private */ - orientationChanged: function () { - this._axis = this.orientation == 'horizontal' ? 'X' : 'Y'; + directionChanged: function (was) { + var key, value; + for (key in Direction) { + value = Direction[key]; + if (value == was) this.removeClass(key.toLowerCase()); + if (value == this.direction) this.addClass(key.toLowerCase()); + } }, /** * @private */ - directionChanged: function () { - this._direction = this.direction == 'forwards' ? 1 : this.direction == 'backwards' ? -1 : 0; + orientationChanged: function (was) { + var key, value; + for (key in Orientation) { + value = Orientation[key]; + if (value == was) this.removeClass(key.toLowerCase()); + if (value == this.orientation) this.addClass(key.toLowerCase()); + } }, + /** * @private */ @@ -214,13 +231,6 @@ module.exports = kind( this.setupTransitions(was); }, - /** - * @private - */ - cacheViewsChanged: function () { - this.popOnForward = this.cacheViews; - }, - /* @@ -315,6 +325,8 @@ module.exports = kind( * @public */ pushPanel: function (info, moreInfo, opts) { + if (this.transitioning) return; + if (opts && opts.purge) { this.purge(); } @@ -353,6 +365,8 @@ module.exports = kind( * @public */ pushPanels: function (info, moreInfo, opts) { + if (this.transitioning) return true; + if (opts && opts.purge) { this.purge(); } @@ -391,6 +405,8 @@ module.exports = kind( * depending on the direction. * * @param {Number} index - Index at which to start destroying panels. + * @param {Number} direction - The direction in which we want to destroy panels. A negative + * number signifies popping backwards, otherwise we pop forwards. * @public */ popPanels: function (index, direction) { @@ -456,7 +472,7 @@ module.exports = kind( */ resetView: function (view) { // reset position - dom.transformValue(view, 'translate' + this._axis, 100 * this._direction + '%'); + dom.transformValue(view, 'translate' + this.orientation, 100 * this.direction + '%'); }, @@ -532,30 +548,22 @@ module.exports = kind( * @private */ transitionFinished: function (sender, ev) { - if (ev.originator === this._currentPanel) { - if ((this._indexDirection < 0 && this.popOnBack && this.index < this.getPanels().length - 1) || - (this._indexDirection > 0 && this.popOnForward && this.index > 0)) { + var panel = ev.originator; + + if (panel === this._currentPanel) { + if ((this._indexDirection < 0 && (this.popOnBack || this.cacheViews) && this.index < this.getPanels().length - 1) || + (this._indexDirection > 0 && (this.popOnForward || this.cacheViews) && this.index > 0)) { this.popPanels(this.index, this._indexDirection); } - if (this._currentPanel.postTransition) { - asyncMethod(this, function () { - this._currentPanel.postTransition(); - }); - } - if (this._garbagePanels && this._garbagePanels.length) { - this.finalizePurge(); - } + if (this.popQueue && this.popQueue.length) this.finalizePurge(); + this.transitioning = false; + } - if (this._currentPanel) { - this._currentPanel.removeClass('transitioning'); - this._currentPanel.activated && this._currentPanel.activated(); - } + panel.removeClass('transitioning'); - if (this._previousPanel) { - this._previousPanel.removeClass('transitioning'); - this._previousPanel.deactivated && this._previousPanel.deactivated(); - } - } + asyncMethod(function () { + if (panel.postTransition) panel.postTransition(); + }); }, @@ -597,15 +605,26 @@ module.exports = kind( setupTransitions: function (previousIndex, animate) { var panels = this.getPanels(), nextPanel = panels[this.index], + currPanel = this._currentPanel, trans, wTrans; this._indexDirection = this.index - previousIndex; if (nextPanel) { + this.transitioning = true; + + if (currPanel) { + currPanel.set('active', false); + if (currPanel.preTransition) currPanel.preTransition(); + } + if (!nextPanel.generated) { nextPanel.render(); } + nextPanel.set('active', true); + if (nextPanel.preTransition) nextPanel.preTransition(); + // only animate transition if there is more than one panel and/or we're animating if (animate) { trans = 'transform ' + this.duration + 'ms ' + this.timingFunction; @@ -613,10 +632,11 @@ module.exports = kind( nextPanel.applyStyle('-webkit-transition', wTrans); nextPanel.applyStyle('transition', trans); nextPanel.addClass('transitioning'); - if (this._currentPanel) { - this._currentPanel.applyStyle('-webkit-transition', wTrans); - this._currentPanel.applyStyle('transition', trans); - this._currentPanel.addClass('transitioning'); + + if (currPanel) { + currPanel.applyStyle('-webkit-transition', wTrans); + currPanel.applyStyle('transition', trans); + currPanel.addClass('transitioning'); } setTimeout(this.bindSafely(function () { @@ -636,16 +656,19 @@ module.exports = kind( * @private */ applyTransitions: function (nextPanel, direct) { + var previousPanel = this._currentPanel; + // apply the transition for the next panel - dom.transformValue(nextPanel, 'translate' + this._axis, '0%'); + dom.transformValue(nextPanel, 'translate' + this.orientation, '0%'); if (this._currentPanel) { // apply the transition for the current panel this.shiftPanel(this._currentPanel, this._indexDirection); } - this._previousPanel = this._currentPanel; this._currentPanel = nextPanel; + if (!this.shouldAnimate() || direct) { // ensure that `transitionFinished is called, regardless of animation - this.transitionFinished(this._currentPanel, {originator: this._currentPanel}); + this.transitionFinished(nextPanel, {originator: nextPanel}); + if (previousPanel) this.transitionFinished(previousPanel, {originator: previousPanel}); } }, @@ -658,8 +681,8 @@ module.exports = kind( * @private */ shiftPanel: function (panel, indexDirection) { - var value = (indexDirection > 0 ? -100 : 100) * this._direction + '%'; - dom.transformValue(panel, 'translate' + this._axis, value); + var value = (indexDirection > 0 ? -100 : 100) * this.direction + '%'; + dom.transformValue(panel, 'translate' + this.orientation, value); }, /** @@ -669,7 +692,7 @@ module.exports = kind( */ purge: function () { var panels = this.getPanels(); - this._garbagePanels = panels.slice(); + this.popQueue = panels.slice(); panels.length = 0; this.index = -1; }, @@ -680,7 +703,7 @@ module.exports = kind( * @private */ finalizePurge: function () { - var panels = this._garbagePanels, + var panels = this.popQueue, panel; while (panels.length) { panel = panels.pop(); @@ -720,4 +743,6 @@ module.exports = kind( }); -module.exports.Panel = LightPanel; \ No newline at end of file +module.exports.Panel = LightPanel; +module.exports.Direction = Direction; +module.exports.Orientation = Orientation; \ No newline at end of file From 3881793d1368d7632287b90e78dbd78956865398 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Mon, 3 Aug 2015 14:08:49 -0700 Subject: [PATCH 016/195] ENYO-1981: Utilize states. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- lib/LightPanels/LightPanel.js | 41 +++++++++++++--- lib/LightPanels/LightPanels.js | 90 ++++++++++++++++++++++------------ 2 files changed, 94 insertions(+), 37 deletions(-) diff --git a/lib/LightPanels/LightPanel.js b/lib/LightPanels/LightPanel.js index e98b501a4..de2fa4119 100644 --- a/lib/LightPanels/LightPanel.js +++ b/lib/LightPanels/LightPanel.js @@ -6,6 +6,18 @@ var var Control = require('../Control'); +/** +* @enum {Number} +* @memberof module:enyo/LightPanel~LightPanel +* @public +*/ +var States = { + ACTIVE: 1, + ACTIVATING: 2, + DEACTIVATING: 3, + INACTIVE: 4 +}; + /** * A light-weight panels implementation that has basic support for side-to-side transitions * between child components. @@ -29,13 +41,20 @@ module.exports = kind( kind: Control, /** - * Is `true` if the panel is currently active; `false` otherwise. + * @private + */ + handlers: { + ontransitionend: 'transitionEnd' + }, + + /** + * The current [state]{@link module:enyo/LightPanel~LightPanel#States}. * - * @type {Boolean} - * @default false + * @type {module:enyo/LightPanel~LightPanel#States} + * @default null * @public */ - active: false, + state: States.INACTIVE, /** * This overridable method is called before a transition. @@ -49,5 +68,15 @@ module.exports = kind( * * @public */ - postTransition: function () {} -}); \ No newline at end of file + postTransition: function () {}, + + /** + * @private + */ + transitionEnd: function (sender, ev) { + if (ev.originator === this) this.set('state', this.state == States.ACTIVATING ? States.ACTIVE : States.INACTIVE); + } + +}); + +module.exports.States = States; \ No newline at end of file diff --git a/lib/LightPanels/LightPanels.js b/lib/LightPanels/LightPanels.js index c52f444cb..384989b6c 100644 --- a/lib/LightPanels/LightPanels.js +++ b/lib/LightPanels/LightPanels.js @@ -16,11 +16,12 @@ var TaskManagerSupport = require('../TaskManagerSupport'); var - LightPanel = require('./LightPanel'); + LightPanel = require('./LightPanel'), + States = LightPanel.States; /** * @enum {Number} -* @memberof module:moonstone/LightPanels~LightPanels +* @memberof module:enyo/LightPanels~LightPanels * @public */ var Direction = { @@ -30,7 +31,7 @@ var Direction = { /** * @enum {Number} -* @memberof module:moonstone/LightPanels~LightPanels +* @memberof module:enyo/LightPanels~LightPanels * @public */ var Orientation = { @@ -171,13 +172,6 @@ module.exports = kind( */ direction: Direction.FORWARDS, - /** - * @private - */ - handlers: { - ontransitionend: 'transitionFinished' - }, - /** * @method * @private @@ -185,6 +179,7 @@ module.exports = kind( create: kind.inherit(function (sup) { return function () { sup.apply(this, arguments); + this._handleStateChange = this.bindSafely('handleStateChange'); this.orientationChanged(); this.directionChanged(); this.indexChanged(); @@ -536,7 +531,25 @@ module.exports = kind( return this.generated && this.getPanels().length > 1 && this.animate; }, + /** + * @private + */ + addChild: kind.inherit(function (sup) { + return function (control) { + control.observe('state', this._handleStateChange); + sup.apply(this, arguments); + }; + }), + /** + * @private + */ + removeChild: kind.inherit(function (sup) { + return function (control) { + sup.apply(this, arguments); + control.unobserve('state', this._handleStateChange); + }; + }), /* ============== @@ -547,23 +560,22 @@ module.exports = kind( /** * @private */ - transitionFinished: function (sender, ev) { - var panel = ev.originator; - - if (panel === this._currentPanel) { - if ((this._indexDirection < 0 && (this.popOnBack || this.cacheViews) && this.index < this.getPanels().length - 1) || - (this._indexDirection > 0 && (this.popOnForward || this.cacheViews) && this.index > 0)) { - this.popPanels(this.index, this._indexDirection); - } - if (this.popQueue && this.popQueue.length) this.finalizePurge(); - this.transitioning = false; + handleStateChange: function (was, is) { + var panel; + if (was == States.ACTIVATING || was == States.DEACTIVATING) { + panel = was == States.ACTIVATING ? this._currentPanel : this._previousPanel; + panel.removeClass('transitioning'); + + // async'ing this as it seems to improve ending transition performance on the TV. Requires + // further investigation into its behavior. + asyncMethod(function () { + if (panel.postTransition) panel.postTransition(); + }); + + if ((this._currentPanel.state == States.ACTIVE) && + (!this._previousPanel || this._previousPanel.state == States.INACTIVE)) + this.finishTransition(); } - - panel.removeClass('transitioning'); - - asyncMethod(function () { - if (panel.postTransition) panel.postTransition(); - }); }, @@ -574,6 +586,20 @@ module.exports = kind( ======================= */ + /** + * When all transitions (i.e. next/previous panel) have completed, we perform some clean-up work. + * + * @private + */ + finishTransition: function () { + if ((this._indexDirection < 0 && (this.popOnBack || this.cacheViews) && this.index < this.getPanels().length - 1) || + (this._indexDirection > 0 && (this.popOnForward || this.cacheViews) && this.index > 0)) { + this.popPanels(this.index, this._indexDirection); + } + if (this.popQueue && this.popQueue.length) this.finalizePurge(); + this.transitioning = false; + }, + /** * Retrieves a cached panel or, if not found, creates a new panel * @@ -614,7 +640,7 @@ module.exports = kind( this.transitioning = true; if (currPanel) { - currPanel.set('active', false); + currPanel.set('state', States.INACTIVE); if (currPanel.preTransition) currPanel.preTransition(); } @@ -622,7 +648,7 @@ module.exports = kind( nextPanel.render(); } - nextPanel.set('active', true); + nextPanel.set('state', States.ACTIVE); if (nextPanel.preTransition) nextPanel.preTransition(); // only animate transition if there is more than one panel and/or we're animating @@ -656,19 +682,21 @@ module.exports = kind( * @private */ applyTransitions: function (nextPanel, direct) { - var previousPanel = this._currentPanel; + var previousPanel = this._previousPanel = this._currentPanel; // apply the transition for the next panel + nextPanel.set('state', States.ACTIVATING); dom.transformValue(nextPanel, 'translate' + this.orientation, '0%'); if (this._currentPanel) { // apply the transition for the current panel + this._currentPanel.set('state', States.DEACTIVATING); this.shiftPanel(this._currentPanel, this._indexDirection); } this._currentPanel = nextPanel; if (!this.shouldAnimate() || direct) { // ensure that `transitionFinished is called, regardless of animation - this.transitionFinished(nextPanel, {originator: nextPanel}); - if (previousPanel) this.transitionFinished(previousPanel, {originator: previousPanel}); + nextPanel.set('state', States.ACTIVE); + if (previousPanel) previousPanel.set('state', States.INACTIVE); } }, From 1e4134e71415183b8f35c73fb3502a34540a494b Mon Sep 17 00:00:00 2001 From: Ankur Mishra Date: Wed, 5 Aug 2015 20:41:34 +0530 Subject: [PATCH 017/195] ENYO-2181- Interfaces for animations Enyo-DCO-1.1-Signed-off-by:Ankur Mishra ankur.mishra@lge.com --- lib/AnimationSupport/Core.js | 1 + lib/AnimationSupport/Fadeable.js | 54 ++++++++++++++++++++++++++ lib/AnimationSupport/Flippable.js | 55 +++++++++++++++++++++++++++ lib/AnimationSupport/Slideable.js | 63 +++++++++++++++++++++++++++++++ lib/AnimationSupport/Tween.js | 6 +++ 5 files changed, 179 insertions(+) create mode 100644 lib/AnimationSupport/Fadeable.js create mode 100644 lib/AnimationSupport/Flippable.js create mode 100644 lib/AnimationSupport/Slideable.js diff --git a/lib/AnimationSupport/Core.js b/lib/AnimationSupport/Core.js index 4ec9837c7..e9e21b41e 100644 --- a/lib/AnimationSupport/Core.js +++ b/lib/AnimationSupport/Core.js @@ -138,6 +138,7 @@ module.exports = kind.singleton({ if (curr && curr.ready()) { tween.update(curr, ts); if (ts >= curr._lastTime) { + tween.complete(curr); this.remove(curr); curr.completed(curr); } diff --git a/lib/AnimationSupport/Fadeable.js b/lib/AnimationSupport/Fadeable.js new file mode 100644 index 000000000..cff156f7f --- /dev/null +++ b/lib/AnimationSupport/Fadeable.js @@ -0,0 +1,54 @@ +var + kind = require('../kind'), + animation = require('./Core'); + +/** +* Interface to achieve fade animation +* +* @module enyo/AnimationSupport/Fadeable +* @public +*/ +module.exports = { + + /** + * @private + */ + name: 'Fadeable', + + /** + * @public + * Make the character invisible + */ + invisible: function () { + this.setAnimation({opacity: 0}); + doFade(this); + }, + + /** + * @public + * Make the character transparent + * @default 0.5 + * @parameter value - set transparency value + */ + transparent: function (value) { + this.setAnimation({opacity: value ? value : 0.5}); + doFade(this); + }, + + /** + * @public + * Make the character visible + */ + opaque: function () { + this.setAnimation({opacity: 1}); + doFade(this); + }, +}; + +/** +* @private +*/ +function doFade (charc) { + animation.trigger(charc); + charc.start(); +} \ No newline at end of file diff --git a/lib/AnimationSupport/Flippable.js b/lib/AnimationSupport/Flippable.js new file mode 100644 index 000000000..beb884f2f --- /dev/null +++ b/lib/AnimationSupport/Flippable.js @@ -0,0 +1,55 @@ +var + kind = require('../kind'), + animation = require('./Core'); + +/** +* Interface to achieve flip animation +* +* @module enyo/AnimationSupport/Flippable +* @public +*/ +module.exports = { + + /** + * @private + */ + name: 'Flippable', + + /** + * Specifies the direction of flip. Accepted value are 'X', 'Y', 'Z' + * + * @type {String} + * @default 'X' + * @public + */ + flipDirection: 'X', + + /** + * Specifies the flip up-to angle. Accepted value are in degree + * + * @type {Number} + * @default '0' + * @public + */ + flipAngle: 0, + + /** + * @public + * apply animation to the flippable DOM object + */ + doFlip: function() { + this.setAxis(); + animation.trigger(this); + this.start(); + }, + + /** + * @public + * set axis of rotation of flippable DOM object + */ + setAxis: function() { + var css = {}; + css["rotate" + this.flipDirection] = this.flipAngle; + this.setAnimation(css); + } +}; \ No newline at end of file diff --git a/lib/AnimationSupport/Slideable.js b/lib/AnimationSupport/Slideable.js new file mode 100644 index 000000000..0456f44a0 --- /dev/null +++ b/lib/AnimationSupport/Slideable.js @@ -0,0 +1,63 @@ +var + kind = require('../kind'), + animation = require('./Core'); +/** +* Interface to achieve slide animation +* +* @module enyo/AnimationSupport/Slideable +* @public +*/ +module.exports = { + + /** + * @private + */ + name: 'Slideable', + + /** + * @public + * slide animation in left direction + * @parameter: slideDistance - distance in pixels to slide in left direction + */ + left: function (slideDistance) { + this.slide({translateX: -1 * slideDistance}); + }, + + /** + * @public + * slide animation in right direction + * @parameter: slideDistance - distance in pixels to slide in right direction + */ + right: function (slideDistance) { + this.slide({translateX: slideDistance}); + }, + + /** + * @public + * slide animation upward + * @parameter: slideDistance - distance in pixels to slide upward + */ + up: function (slideDistance) { + this.slide({translateY: -1 * slideDistance}); + }, + + /** + * @public + * slide animation downward + * @parameter: slideDistance - distance in pixels to slide downward + */ + down: function (slideDistance) { + this.slide({translateY: slideDistance}); + }, + + /** + * @public + * slide animation in custom direction + * @parameter: anim - css property to slide in any direction + */ + slide: function (anim) { + this.setAnimation(anim); + animation.trigger(this); + this.start(); + } +}; \ No newline at end of file diff --git a/lib/AnimationSupport/Tween.js b/lib/AnimationSupport/Tween.js index f58716dd8..c079afc68 100644 --- a/lib/AnimationSupport/Tween.js +++ b/lib/AnimationSupport/Tween.js @@ -93,5 +93,11 @@ module.exports = { accelerate: function (ele, m) { m = m ? m : frame.IDENTIY; frame.setTransformProperty(ele, m); + }, + + complete: function (charc) { + charc.animating = false; + charc._start = undefined; + charc._end = undefined; } }; \ No newline at end of file From 4e153016f8a9ba83cee2592731cd2261d5aacfab Mon Sep 17 00:00:00 2001 From: Anish_Ramesan Date: Thu, 6 Aug 2015 15:52:07 +0530 Subject: [PATCH 018/195] ENYO-2182 - Hierarchical mixin added Enyo-DCO-1.1-Signed-off-by: Anish Ramesan anish.ramesan@lge.com --- lib/AnimationSupport/AnimationSupport.js | 27 ++--- lib/AnimationSupport/Core.js | 34 +++--- lib/AnimationSupport/Frame.js | 2 +- lib/AnimationSupport/HierarchicalMixin.js | 120 ++++++++++++++++++++++ lib/AnimationSupport/KeyFrame.js | 18 ++-- lib/AnimationSupport/Tween.js | 4 +- 6 files changed, 158 insertions(+), 47 deletions(-) create mode 100644 lib/AnimationSupport/HierarchicalMixin.js diff --git a/lib/AnimationSupport/AnimationSupport.js b/lib/AnimationSupport/AnimationSupport.js index 5830d0ac4..f032a2792 100644 --- a/lib/AnimationSupport/AnimationSupport.js +++ b/lib/AnimationSupport/AnimationSupport.js @@ -1,4 +1,3 @@ - require('enyo'); var @@ -19,6 +18,8 @@ var AnimationSupport = { //name: 'AnimationSupport', animating: false, + active: false, + /** * Check if the character is suitable for animation * @public @@ -40,22 +41,12 @@ var AnimationSupport = { * @parameter accelerate- Turns on/off hardware acceleration * @public */ - getInitial: function (accelerate) { + initiate: function (current) { var dom = this.hasNode(), prop = this.getAnimation(), - init = frame.getCompoutedProperty(dom, prop, this._start); - - utils.mixin(this, init); - if (accelerate) frame.accelerate(dom, this.matrix); - - }, + init = frame.getCompoutedProperty(dom, prop, current); - /** - * Gets HTML representation of this character - * @public - */ - getDom: function() { - return this.hasNode && this.hasNode(); + utils.mixin(this, init); }, /** @@ -103,12 +94,13 @@ var AnimationSupport = { * Trigger animation for this character. * @public */ - start: function () { + start: function (active) { this._duration = parseInt(this.getDuration(), 10); this._startTime = utils.perfNow(); this._lastTime = this._startTime + this._duration; this.animating = true; - this.getInitial(false); + this.active = active; + this.initiate(); }, /** @@ -125,7 +117,8 @@ var AnimationSupport = { rendered: kind.inherit(function (sup) { return function () { sup.apply(this, arguments); - this.getInitial(true); + this.initiate(); + frame.accelerate(this.hasNode(), this.matrix); }; }) }; diff --git a/lib/AnimationSupport/Core.js b/lib/AnimationSupport/Core.js index e9e21b41e..7b55fd9f8 100644 --- a/lib/AnimationSupport/Core.js +++ b/lib/AnimationSupport/Core.js @@ -1,18 +1,5 @@ -/** -* Core module is responsible for handling all animations happening in Enyo. -* The responsibilities of this module is to; -* - Trigger vendor specific rAF. -* - Knowing all elements which have requested for animation. -* - Tween animation frames for each characters. -* -* @public -*/ require('enyo'); -/** -* This module returns the Loop singleton -* @module enyo/Core -*/ var kind = require('../kind'), animation = require('../animation'), @@ -22,6 +9,16 @@ var var CoreObject = require('../CoreObject'); +/** +* This module returns the Loop singleton +* Core module is responsible for handling all animations happening in Enyo. +* The responsibilities of this module is to; +* - Trigger vendor specific rAF. +* - Knowing all elements which have requested for animation. +* - Tween animation frames for each characters. +* +* @module enyo/Core +*/ module.exports = kind.singleton({ /** @lends module:enyo/Core */ @@ -84,7 +81,7 @@ module.exports = kind.singleton({ */ exists: function (eventTarget) { for (var i = 0; i < this.chracs.length; i++) { - if (this.chracs[i].getDom() === eventTarget) { // Already Animating + if (this.chracs[i].hasNode() === eventTarget) { // Already Animating return this.chracs[i]; } } @@ -138,9 +135,11 @@ module.exports = kind.singleton({ if (curr && curr.ready()) { tween.update(curr, ts); if (ts >= curr._lastTime) { - tween.complete(curr); - this.remove(curr); curr.completed(curr); + tween.complete(curr); + if(!curr.active) { + this.remove(curr); + } } } } @@ -153,5 +152,4 @@ module.exports = kind.singleton({ dummy: function () { animation.requestAnimationFrame(function() {}); } -}); - +}); \ No newline at end of file diff --git a/lib/AnimationSupport/Frame.js b/lib/AnimationSupport/Frame.js index f000f8c5d..69fcbf832 100644 --- a/lib/AnimationSupport/Frame.js +++ b/lib/AnimationSupport/Frame.js @@ -7,7 +7,7 @@ var * This module exposes bunch of animation API's like transform, matrix calculation, * fetching inital DOM properties and also applying style updates to DOM. * -* @public +* @module enyo/Frame */ var frame = module.exports = { /** diff --git a/lib/AnimationSupport/HierarchicalMixin.js b/lib/AnimationSupport/HierarchicalMixin.js new file mode 100644 index 000000000..c3144cc15 --- /dev/null +++ b/lib/AnimationSupport/HierarchicalMixin.js @@ -0,0 +1,120 @@ +require('enyo'); + +var + kind = require('../kind'), + Dom = require('../dom'), + animation = require('./Core'), + VerticalDelegate = require('../VerticalDelegate'); + +/** +* A mixin support for Hierarchical components +* @module enyo/HierarchicalMixin +*/ +module.exports = { + /** + * @private + */ + _pageScrolltop: 0, + + /** + * @private + */ + _paged: false, + + /** + * Mixin creation + * + * @method + * @private + */ + create: kind.inherit(function(sup) { + return function() { + sup.apply(this, arguments); + this.addListener('paging', this._pagingHandler.bind(this)); + this.clientHeight = Dom.getWindowHeight(); + }; + }), + + /** + * Mixin creation + * + * @method + * @private + */ + didScroll: kind.inherit(function(sup) { + return function() { + sup.apply(this, arguments); + var top = event ? event.scrollBounds.top : 0; + if (this._paged) { + this._pageScrolltop = top; + this._paged = false; + } + this._animateChild(this.controls, event ? event.scrollBounds.top - this._pageScrolltop: 0); + }; + }), + + /** + * Handler for pagging event when its triggered from vertical delegate of data grid list + * + * @method + * @private + */ + _pagingHandler: function() { + this._paged = true; + for (var i=0, node; (node = this.controls[i]); i++) { + node._bounds = node.getAbsoluteBounds(); + } + }, + + /** + * Apply animation on all the child nodes which are visible inside the viewport + * + * @method + * @private + */ + _animateChild: function(nodes, top) { + var showing; + for (var i=0, node; (node = nodes[i]); i++) { + showing = this._getNodeShowing(node, top); + if (node.hasNode() && showing && !node.animating) { + node.start(true); + } + } + }, + + /** + * Checks if the node element in visible inside the viewport + * + * @method + * @private + */ + _getNodeShowing: function(node, top) { + var showing, rect = node._bounds; + if (rect) { + showing = ((rect.top >= top) && ((rect.top - top) <= this.clientHeight)); + } else { + showing = false; + } + return showing; + } +}; + +/** + Hijacking original behaviour of delegates. +*/ +var sup = VerticalDelegate.generate; + +VerticalDelegate.generate = function(list) { + sup.call(this, list); + for (var i=0, p; (p=list.pages[i]); ++i) { + for (var j=0, c; (c=p.children[j]); ++j) { + c._bounds = c.getAbsoluteBounds(); + c.animate = list.animate; + c.duration = list.duration; + animation.trigger(c); + if(list._getNodeShowing(c, 0)) { + c.start(true); + } + } + } +}; \ No newline at end of file diff --git a/lib/AnimationSupport/KeyFrame.js b/lib/AnimationSupport/KeyFrame.js index 8f8884b09..fa2b6bc58 100644 --- a/lib/AnimationSupport/KeyFrame.js +++ b/lib/AnimationSupport/KeyFrame.js @@ -1,4 +1,3 @@ - require('enyo'); var @@ -43,16 +42,15 @@ var keyFrame = module.exports = kind.singleton({ cb = proto.completed, keyframe = proto.keyFrame; - charc.keyframeCallback = cb; - charc.initialTime = utils.perfNow(); - charc.totalDuration = proto.duration; charc.keyProps = []; charc.keyTime = []; - for (prop in keyframe) { charc.keyTime.push(prop); charc.keyProps.push(keyframe[prop]); } + charc.keyframeCallback = cb; + charc.initialTime = utils.perfNow(); + charc.totalDuration = proto.duration; charc.completed = this.bindSafely(this.reframe); this.keyFraming(charc); @@ -99,12 +97,14 @@ var keyFrame = module.exports = kind.singleton({ * @private */ keyFrame.keyFraming = function (charc, callback) { - var index = charc.currentIndex || 0; - var time = charc.currentIndex ? - charc.totalDuration * ((charc.keyTime[index] - charc.keyTime[index -1])/100) : "0"; + var index = charc.currentIndex || 0, + old = charc.keyTime[index -1], + next = charc.keyTime[index], + total = charc.totalDuration, + time = charc.currentIndex ? total * ((next - old)/100) : "0"; charc.setAnimation(charc.keyProps[index]); - charc.setInitial(index > 0 ? charc.keyProps[index -1] : {}); + charc.setInitial(charc.currentState); charc.setDuration(time); charc.animating = false; charc.currentIndex = index; diff --git a/lib/AnimationSupport/Tween.js b/lib/AnimationSupport/Tween.js index c079afc68..e427d0eca 100644 --- a/lib/AnimationSupport/Tween.js +++ b/lib/AnimationSupport/Tween.js @@ -8,7 +8,7 @@ var * - Interpolating current state of character. * - Update DOM based on current state, using matrix for tranform and styles for others. * -* @public +* @module enyo/Tween */ module.exports = { /** @@ -46,7 +46,7 @@ module.exports = { matrix, start, end, states = [], - dom = charc.getDom(), + dom = charc.node, prop = charc.getAnimation(), fMatrix = frame.IDENTIY, iMatrix = charc._matrix || frame.IDENTIY; From 586cd564ae0c35f314b3695a178e4e83e4d36786 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Fri, 7 Aug 2015 10:42:27 -0700 Subject: [PATCH 019/195] ENYO-1981: Call activated/deactivated lifecycle methods, for now. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- lib/LightPanels/LightPanel.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/LightPanels/LightPanel.js b/lib/LightPanels/LightPanel.js index de2fa4119..b85fc9318 100644 --- a/lib/LightPanels/LightPanel.js +++ b/lib/LightPanels/LightPanel.js @@ -68,7 +68,11 @@ module.exports = kind( * * @public */ - postTransition: function () {}, + postTransition: function () { + // TODO: this is added for backwards-compatibility and is deprecated functionality + if (this.state == States.ACTIVE && this.activated) this.activated(); + else if (this.state == States.INACTIVE && this.deactivated) this.deactivated(); + }, /** * @private From 2cd8237adf50f860f514292005a5f16e713a2deb Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Tue, 11 Aug 2015 16:19:02 -0700 Subject: [PATCH 020/195] JSDoc and inline code sample cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- lib/Image/Image.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/Image/Image.js b/lib/Image/Image.js index f281a9c7e..0ef63e8c0 100644 --- a/lib/Image/Image.js +++ b/lib/Image/Image.js @@ -41,27 +41,32 @@ var * [onerror]{@link module:enyo/Image~Image#onerror} [events]{@glossary event}. Image dragging is suppressed by * default, so as not to interfere with touch interfaces. * -* When {@link enyo.Image#sizing} is used, the control will not have a natural size and must be -* manually sized using CSS `height` and `width`. Also, when {@link enyo.Image#placeholder} is used -* without {@link enyo.Image#sizing}, you may wish to specify the size as the image will not have a -* natural size until the image loads causing the placeholder to not be visible. +* When [sizing]{@link module:enyo/Image~Image#sizing} is used, the control will not have a natural size and must be +* manually sized using CSS `height` and `width`. Also, when [placeholder]{@link module:enyo/Image~Image#placeholder} is used +* without `sizing`, you may wish to specify the size, as the image will not have a +* natural size until the image loads, causing the placeholder to not be visible. * * {@link module:enyo/Image~Image} also has support for multi-resolution images. If you are developing assets * for specific screen sizes, HD (720p), FHD (1080p), UHD (4k), for example, you may provide * specific image assets in a hash/object format to the `src` property, instead of the usual * string. The image sources will be used automatically when the screen resolution is less than * or equal to those screen types. For more informaton on our resolution support, and how to -* enable this feature, see our [resolution independence docs]{@link module:enyo/resolution}. +* enable this feature, see our [resolution independence documentation]{@link module:enyo/resolution}. * * ``` * // Take advantage of the multi-rez mode -* {kind: 'enyo.Image', src: { +* var +* kind = require('enyo/kind'), +* Image = require('enyo/Image'); +* +* {kind: Image, src: { * 'hd': 'http://lorempixel.com/64/64/city/1/', * 'fhd': 'http://lorempixel.com/128/128/city/1/', * 'uhd': 'http://lorempixel.com/256/256/city/1/' * }, alt: 'Multi-rez'}, +* * // Standard string `src` -* {kind: 'enyo.Image', src: http://lorempixel.com/128/128/city/1/', alt: 'Large'}, +* {kind: Image, src: 'http://lorempixel.com/128/128/city/1/', alt: 'Large'} * ``` * * @class Image From c9acd0d9f4332db3cd366227a65702efdd5bd048 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 14 Aug 2015 16:21:16 -0700 Subject: [PATCH 021/195] Update version string to 2.6.0-pre.14.1 --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 9b988016e..c4de82750 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ 'use strict'; exports = module.exports = require('./lib/options'); -exports.version = '2.6.0-pre'; \ No newline at end of file +exports.version = '2.6.0-pre'; From 8ccaac090136e8f92c83f93d3e74fefc9c22e204 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 14 Aug 2015 17:08:02 -0700 Subject: [PATCH 022/195] Update version string to 2.6.0-pre.14.1 --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index c4de82750..0bc33de10 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ 'use strict'; exports = module.exports = require('./lib/options'); -exports.version = '2.6.0-pre'; +exports.version = '2.6.0-pre.14.1'; From 6bf9f5b38055f5e9a80540af495e098669f53e82 Mon Sep 17 00:00:00 2001 From: Ankur Mishra Date: Mon, 17 Aug 2015 12:41:50 +0530 Subject: [PATCH 023/195] Add mixins for Interfaces --- lib/AnimationSupport/AnimationSupport.js | 12 ++++++++++++ lib/AnimationSupport/Fadeable.js | 7 +++---- lib/AnimationSupport/Flippable.js | 4 ++-- lib/AnimationSupport/Slideable.js | 11 +++++------ 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/lib/AnimationSupport/AnimationSupport.js b/lib/AnimationSupport/AnimationSupport.js index f032a2792..75435fed3 100644 --- a/lib/AnimationSupport/AnimationSupport.js +++ b/lib/AnimationSupport/AnimationSupport.js @@ -57,6 +57,18 @@ var AnimationSupport = { return this._prop || (this._prop = this.animate); }, + /** + * Adds new animation on already existing animation for this character. + * @public + */ + addAnimation: function (newProp) { + if (this._prop === undefined) { + this._prop = newProp; + } else { + utils.mixin(this._prop, newProp); + } + }, + /** * Sets new animation for this character. * @public diff --git a/lib/AnimationSupport/Fadeable.js b/lib/AnimationSupport/Fadeable.js index cff156f7f..22176e941 100644 --- a/lib/AnimationSupport/Fadeable.js +++ b/lib/AnimationSupport/Fadeable.js @@ -20,7 +20,7 @@ module.exports = { * Make the character invisible */ invisible: function () { - this.setAnimation({opacity: 0}); + this.addAnimation({opacity: 0}); doFade(this); }, @@ -31,7 +31,7 @@ module.exports = { * @parameter value - set transparency value */ transparent: function (value) { - this.setAnimation({opacity: value ? value : 0.5}); + this.addAnimation({opacity: value ? value : 0.5}); doFade(this); }, @@ -40,7 +40,7 @@ module.exports = { * Make the character visible */ opaque: function () { - this.setAnimation({opacity: 1}); + this.addAnimation({opacity: 1}); doFade(this); }, }; @@ -50,5 +50,4 @@ module.exports = { */ function doFade (charc) { animation.trigger(charc); - charc.start(); } \ No newline at end of file diff --git a/lib/AnimationSupport/Flippable.js b/lib/AnimationSupport/Flippable.js index beb884f2f..72586d9c7 100644 --- a/lib/AnimationSupport/Flippable.js +++ b/lib/AnimationSupport/Flippable.js @@ -40,7 +40,7 @@ module.exports = { doFlip: function() { this.setAxis(); animation.trigger(this); - this.start(); + // this.start(); }, /** @@ -50,6 +50,6 @@ module.exports = { setAxis: function() { var css = {}; css["rotate" + this.flipDirection] = this.flipAngle; - this.setAnimation(css); + this.addAnimation(css); } }; \ No newline at end of file diff --git a/lib/AnimationSupport/Slideable.js b/lib/AnimationSupport/Slideable.js index 0456f44a0..856a46a59 100644 --- a/lib/AnimationSupport/Slideable.js +++ b/lib/AnimationSupport/Slideable.js @@ -20,7 +20,7 @@ module.exports = { * @parameter: slideDistance - distance in pixels to slide in left direction */ left: function (slideDistance) { - this.slide({translateX: -1 * slideDistance}); + this.slide({translateX: -1 * slideDistance, translateY: 0}); }, /** @@ -29,7 +29,7 @@ module.exports = { * @parameter: slideDistance - distance in pixels to slide in right direction */ right: function (slideDistance) { - this.slide({translateX: slideDistance}); + this.slide({translateX: slideDistance, translateY: 0}); }, /** @@ -38,7 +38,7 @@ module.exports = { * @parameter: slideDistance - distance in pixels to slide upward */ up: function (slideDistance) { - this.slide({translateY: -1 * slideDistance}); + this.slide({translateX: 0, translateY: -1 * slideDistance}); }, /** @@ -47,7 +47,7 @@ module.exports = { * @parameter: slideDistance - distance in pixels to slide downward */ down: function (slideDistance) { - this.slide({translateY: slideDistance}); + this.slide({translateX: 0, translateY: slideDistance}); }, /** @@ -56,8 +56,7 @@ module.exports = { * @parameter: anim - css property to slide in any direction */ slide: function (anim) { - this.setAnimation(anim); + this.addAnimation(anim); animation.trigger(this); - this.start(); } }; \ No newline at end of file From b1d0eddc3c53c8bd7d96015acf0e6498aeec1f9f Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Tue, 18 Aug 2015 11:13:39 -0700 Subject: [PATCH 024/195] Fixed typo Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- lib/PriorityQueue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/PriorityQueue.js b/lib/PriorityQueue.js index ada196e3e..752ba4818 100644 --- a/lib/PriorityQueue.js +++ b/lib/PriorityQueue.js @@ -1,7 +1,7 @@ require('enyo'); /** -* Exports Prioriy options used by {@link module:enyo/BackgroundTaskManager} +* Exports Priority options used by {@link module:enyo/BackgroundTaskManager}. * @module enyo/PriorityQueue */ From e4a3bd2da1d49359e5c1dfc30bbf7de2d4597131 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Tue, 18 Aug 2015 16:50:39 -0700 Subject: [PATCH 025/195] ENYO-2327: Fix file mode settings. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- lib/VerticalDelegate.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 lib/VerticalDelegate.js diff --git a/lib/VerticalDelegate.js b/lib/VerticalDelegate.js old mode 100755 new mode 100644 From 8ff0e9dbd2891cbfe3ebdc57334a2598b3944b6f Mon Sep 17 00:00:00 2001 From: Gray Norton Date: Wed, 19 Aug 2015 14:13:53 -0700 Subject: [PATCH 026/195] ENYO-2083: Documenting enyo/History as WIP The refactoring of moon/History (including creation of the new enyo/History singleton) fixes multiple known issues and is a significant improvement, but I'm not confident that we have things quite right yet. I therefore want to mark enyo/History as an experimental / work-in-progress API for the time being to allow us to make breaking changes or replace the API altogether in subsequent releases. Enyo-DCO-1.1-Signed-Off-By: Gray Norton (gray.norton@lge.com) --- lib/History.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/History.js b/lib/History.js index 56c740c02..f0728370a 100644 --- a/lib/History.js +++ b/lib/History.js @@ -1,7 +1,14 @@ /** -* [EnyoHistory description] +* The enyo/History singleton is a specialized application history manager. +* It is built on top of the standard HTML5 History API and centrally manages +* interactions with that API to reduce the likelihood that "competing" uses +* of the API within a single app will conflict, causing unpredictable behavior. +* +* CAUTION: This API is experimental. It is likely to change or to be removed +* altogether in a future release. * * @module enyo/History +* @wip */ /** From 84a731005d5141b862ae0c9068033fe2dacad7cf Mon Sep 17 00:00:00 2001 From: Roy Sutton Date: Fri, 7 Aug 2015 12:27:55 -0400 Subject: [PATCH 027/195] ENYO-2205 Initialize selected items when collection changed Enyo-DCO-1.1-Signed-off-by: Roy Sutton --- lib/DataRepeater.js | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/DataRepeater.js b/lib/DataRepeater.js index 434a0985a..a88973588 100644 --- a/lib/DataRepeater.js +++ b/lib/DataRepeater.js @@ -16,6 +16,14 @@ function at (idx) { return this[idx]; } +function arrayFilter (record) { + return record[this.selectionProperty]; +} + +function modelFilter (record) { + return record.get(this.selectionProperty); +} + /** * {@link module:enyo/DataRepeater~DataRepeater} iterates over the items in an {@link module:enyo/Collection~Collection} to * repeatedly render and synchronize records (instances of {@link module:enyo/Model~Model}) to its @@ -60,9 +68,9 @@ var DataRepeater = module.exports = kind( * selection and deselection of a single item at a time. The 'multi' selection mode allows * multiple children to be selected simultaneously, while the 'group' selection mode allows * group-selection behavior such that only one child may be selected at a time and, once a - * child is selected, it cannot be deselected via user input. The child may still be + * child is selected, it cannot be deselected via user input. The child may still be * deselected via the selection API methods. - * + * * @type {String} * @default 'single' * @public @@ -454,7 +462,8 @@ var DataRepeater = module.exports = kind( * @private */ initCollection: function (c, p) { - var e; + var e, filter, isArray = c && c instanceof Array; + if (c && c.addListener) { for (e in this._handlers) { c.addListener(e, this._handlers[e]); @@ -464,13 +473,19 @@ var DataRepeater = module.exports = kind( // access members of our dataset consistently, regardless // of whether our data is in an array or a Collection if (c && !c.at) { - c.at = at.bind(c); + Object.defineProperty(c, 'at', {value: at, enumerable: false}); } if (p && p.removeListener) { for (e in this._handlers) { p.removeListener(e, this._handlers[e]); } } + if (c && this.selectionProperty) { + filter = isArray ? arrayFilter : modelFilter; + this._selection = c.filter(filter, this); + } else { + this._selection = []; + } }, /** From 3c2a8eebe8f2c4c7c5127c71b3eb8c346bc0bad2 Mon Sep 17 00:00:00 2001 From: Ryan Duffy Date: Fri, 21 Aug 2015 10:09:51 -0500 Subject: [PATCH 028/195] fix jshint * Fix the gulpfile to actually test the source in lib. * Refactored Control, floatingLayer, and fullscreen to be in a single module to resolve a hidden circular dependency issue. * Fixed various jshint warnings Issue: ENYO-2356 Enyo-DCO-1.1-Signed-off-by: Ryan Duffy (ryan.duffy@lge.com) --- gulpfile.js | 2 +- lib/{ => Control}/Control.js | 42 ++-- lib/{ => Control}/floatingLayer.js | 26 +- lib/Control/fullscreen.js | 262 ++++++++++++++++++++ lib/{fullscreen => Control}/fullscreen.less | 0 lib/{fullscreen => Control}/package.json | 2 +- lib/RichText/RichText.js | 2 + lib/Scroller/Scroller.js | 1 - lib/SpriteAnimation/SpriteAnimation.js | 6 +- lib/fullscreen/fullscreen.js | 262 -------------------- lib/gesture/drag.js | 2 +- lib/gesture/touchGestures.js | 3 +- 12 files changed, 309 insertions(+), 301 deletions(-) rename lib/{ => Control}/Control.js (98%) rename lib/{ => Control}/floatingLayer.js (70%) create mode 100644 lib/Control/fullscreen.js rename lib/{fullscreen => Control}/fullscreen.less (100%) rename lib/{fullscreen => Control}/package.json (61%) delete mode 100644 lib/fullscreen/fullscreen.js diff --git a/gulpfile.js b/gulpfile.js index 0313895db..88d607bf2 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -14,7 +14,7 @@ gulp.task('jshint', lint); function lint () { return gulp - .src('./lib/**.js') + .src('./lib/**/*.js') .pipe(jshint()) .pipe(jshint.reporter(stylish, {verbose: true})) .pipe(jshint.reporter('fail')); diff --git a/lib/Control.js b/lib/Control/Control.js similarity index 98% rename from lib/Control.js rename to lib/Control/Control.js index e7fcffd5f..92f4fa62a 100644 --- a/lib/Control.js +++ b/lib/Control/Control.js @@ -6,24 +6,26 @@ require('enyo'); */ var - kind = require('./kind'), - utils = require('./utils'), - platform = require('./platform'), - dispatcher = require('./dispatcher'), - fullscreen = require('./fullscreen'), - options = require('./options'), - roots = require('./roots'); + kind = require('../kind'), + utils = require('../utils'), + platform = require('../platform'), + dispatcher = require('../dispatcher'), + options = require('../options'), + roots = require('../roots'); + +var + AccessibilitySupport = require('../AccessibilitySupport'), + UiComponent = require('../UiComponent'), + HTMLStringDelegate = require('../HTMLStringDelegate'), + Dom = require('../dom'); var - AccessibilitySupport = require('./AccessibilitySupport'), - UiComponent = require('./UiComponent'), - HTMLStringDelegate = require('./HTMLStringDelegate'), - Dom = require('./dom'), + fullscreen = require('./fullscreen'), FloatingLayer = require('./floatingLayer'); // While the namespace isn't needed here, gesture is required for ontap events for which Control // has a handler. Bringing them all in now for the time being. -require('./gesture'); +require('../gesture'); var nodePurgatory; @@ -897,7 +899,7 @@ var Control = module.exports = kind( * @public */ isFullscreen: function () { - return (this.hasNode() && this.node === fullscreen.getFullscreenElement()); + return (this.hasNode() && this.node === Control.Fullscreen.getFullscreenElement()); }, /** @@ -912,7 +914,7 @@ var Control = module.exports = kind( requestFullscreen: function () { if (!this.hasNode()) return false; - if (fullscreen.requestFullscreen(this)) { + if (Control.Fullscreen.requestFullscreen(this)) { return true; } @@ -929,7 +931,7 @@ var Control = module.exports = kind( */ cancelFullscreen: function() { if (this.isFullscreen()) { - fullscreen.cancelFullscreen(); + Control.Fullscreen.cancelFullscreen(); return true; } @@ -1687,8 +1689,8 @@ Control.concat = function (ctor, props, instance) { Control.prototype.defaultKind = Control; -// Control has to be *completely* set up before creating the floating layer otherwise things will -// error out during construction. +// Control has to be *completely* set up before creating the floating layer setting up the +// fullscreen object because fullscreen depends on floating layer which depends on Control. /** * @static @@ -1701,3 +1703,9 @@ Control.FloatingLayer = FloatingLayer(Control); * @public */ Control.floatingLayer = new Control.FloatingLayer({id: 'floatingLayer'}); + +/** +* @static +* @public +*/ +Control.Fullscreen = fullscreen(Control); \ No newline at end of file diff --git a/lib/floatingLayer.js b/lib/Control/floatingLayer.js similarity index 70% rename from lib/floatingLayer.js rename to lib/Control/floatingLayer.js index 56dab2434..b58708e07 100644 --- a/lib/floatingLayer.js +++ b/lib/Control/floatingLayer.js @@ -1,20 +1,18 @@ /** -* Exports the {@link module:enyo/floatingLayer~FloatingLayer} singleton instance. -* @module enyo/floatingLayer +* Exports the {@link module:enyo/Control/floatingLayer~FloatingLayer} singleton instance. +* @module enyo/Control/floatingLayer */ -require('enyo'); - var - kind = require('./kind'), - platform = require('./platform'); + kind = require('../kind'), + platform = require('../platform'); module.exports = function (Control) { /** - * {@link module:enyo/floatingLayer~FloatingLayer} is a [control]{@link module:enyo/Control~Control} that provides a layer for - * controls that should be displayed above an [application]{@link module:enyo/Application~Application}. The - * `floatingLayer` singleton can be set as a control's parent to have the control float - * above the application, e.g.: + * {@link module:enyo/Control/floatingLayer~FloatingLayer} is a + * [control]{@link module:enyo/Control~Control} that provides a layer for controls that should be + * displayed above an [application]{@link module:enyo/Application~Application}. The `floatingLayer` + * singleton can be set as a control's parent to have the control float above the application, e.g.: * * ``` * var floatingLayer = require('enyo/floatingLayer'); @@ -34,8 +32,8 @@ module.exports = function (Control) { * @ui * @protected */ - return kind( - /** @lends module:enyo/floatingLayer~FloatingLayer.prototype */ { + var FloatingLayer = kind( + /** @lends module:enyo/Control/floatingLayer~FloatingLayer.prototype */ { /** * @private @@ -116,4 +114,6 @@ module.exports = function (Control) { teardownChildren: function () { } }); -}; + + return FloatingLayer; +}; \ No newline at end of file diff --git a/lib/Control/fullscreen.js b/lib/Control/fullscreen.js new file mode 100644 index 000000000..82e128bd5 --- /dev/null +++ b/lib/Control/fullscreen.js @@ -0,0 +1,262 @@ +var + dispatcher = require('../dispatcher'), + utils = require('../utils'), + ready = require('../ready'), + Signals = require('../Signals'); + +/** +* Normalizes and provides fullscreen support for [controls]{@link module:enyo/Control~Control}, +* based on the [fullscreen]{@glossary fullscreen} API. +* +* @module enyo/Control/fullscreen +* @public +*/ +module.exports = function (Control) { + var floatingLayer = Control.floatingLayer; + var fullscreen = { + + /** + * Reference to the current fullscreen [control]{@link module:enyo/Control~Control}. + * + * @private + */ + fullscreenControl: null, + + /** + * Reference to the current fullscreen element (fallback for platforms + * without native support). + * + * @private + */ + fullscreenElement: null, + + /** + * Reference to that [control]{@link module:enyo/Control~Control} that requested fullscreen. + * + * @private + */ + requestor: null, + + /** + * Native accessor used to get reference to the current fullscreen element. + * + * @private + */ + elementAccessor: + ('fullscreenElement' in document) ? 'fullscreenElement' : + ('mozFullScreenElement' in document) ? 'mozFullScreenElement' : + ('webkitFullscreenElement' in document) ? 'webkitFullscreenElement' : + null, + + /** + * Native accessor used to request fullscreen. + * + * @private + */ + requestAccessor: + ('requestFullscreen' in document.documentElement) ? 'requestFullscreen' : + ('mozRequestFullScreen' in document.documentElement) ? 'mozRequestFullScreen' : + ('webkitRequestFullscreen' in document.documentElement) ? 'webkitRequestFullscreen' : + null, + + /** + * Native accessor used to cancel fullscreen. + * + * @private + */ + cancelAccessor: + ('cancelFullScreen' in document) ? 'cancelFullScreen' : + ('mozCancelFullScreen' in document) ? 'mozCancelFullScreen' : + ('webkitCancelFullScreen' in document) ? 'webkitCancelFullScreen' : + null, + + /** + * Determines whether the platform supports the [fullscreen]{@glossary fullscreen} API. + * + * @returns {Boolean} Returns `true` if platform supports all of the + * [fullscreen]{@glossary fullscreen} API, `false` otherwise. + * @public + */ + nativeSupport: function() { + return (this.elementAccessor !== null && this.requestAccessor !== null && this.cancelAccessor !== null); + }, + + /** + * Normalizes `getFullscreenElement()`. + * + * @public + */ + getFullscreenElement: function() { + return (this.nativeSupport()) ? document[this.elementAccessor] : this.fullscreenElement; + }, + + /** + * Returns current fullscreen [control]{@link module:enyo/Control~Control}. + * + * @public + */ + getFullscreenControl: function() { + return this.fullscreenControl; + }, + + /** + * Normalizes `requestFullscreen()`. + * + * @public + */ + requestFullscreen: function(ctl) { + if (this.getFullscreenControl() || !(ctl.hasNode())) { + return false; + } + + this.requestor = ctl; + + // Only use native request if platform supports all of the API + if (this.nativeSupport()) { + ctl.hasNode()[this.requestAccessor](); + } else { + this.fallbackRequestFullscreen(); + } + + return true; + }, + + /** + * Normalizes `cancelFullscreen()`. + * + * @public + */ + cancelFullscreen: function() { + if (this.nativeSupport()) { + document[this.cancelAccessor](); + } else { + this.fallbackCancelFullscreen(); + } + }, + + /** + * Fallback support for setting fullscreen element (done by browser on platforms with + * native support). + * + * @private + */ + setFullscreenElement: function(node) { + this.fullscreenElement = node; + }, + + /** + * Sets current fullscreen [control]{@link module:enyo/Control~Control}. + * + * @private + */ + setFullscreenControl: function(ctl) { + this.fullscreenControl = ctl; + }, + + /** + * Fallback fullscreen request for platforms without fullscreen support. + * + * @private + */ + fallbackRequestFullscreen: function() { + var control = this.requestor; + + if (!control) { + return; + } + + // Get before node to allow us to exit floating layer to the proper position + control.prevAddBefore = control.parent.controlAtIndex(control.indexInContainer() + 1); + + // Render floating layer if we need to + if (!floatingLayer.hasNode()) { + floatingLayer.render(); + } + + control.addClass('enyo-fullscreen'); + control.appendNodeToParent(floatingLayer.hasNode()); + control.resize(); + + this.setFullscreenControl(control); + this.setFullscreenElement(control.hasNode()); + }, + + /** + * Fallback cancel fullscreen for platforms without fullscreen support. + * + * @private + */ + fallbackCancelFullscreen: function() { + var control = this.fullscreenControl, + beforeNode, + parentNode + ; + + if (!control) { + return; + } + + // Find beforeNode based on _this.addBefore_ and _this.prevAddBefore_ + beforeNode = (control.prevAddBefore) ? control.prevAddBefore.hasNode() : null; + parentNode = control.parent.hasNode(); + control.prevAddBefore = null; + + control.removeClass('enyo-fullscreen'); + + if (!beforeNode) { + control.appendNodeToParent(parentNode); + } else { + control.insertNodeInParent(parentNode, beforeNode); + } + + control.resize(); + + this.setFullscreenControl(null); + this.setFullscreenElement(null); + }, + + /** + * Listens for fullscreen change {@glossary event} and broadcasts it as a + * normalized event. + * + * @private + */ + detectFullscreenChangeEvent: function() { + this.setFullscreenControl(this.requestor); + this.requestor = null; + + // Broadcast change + Signals.send('onFullscreenChange'); + } + }; + + /** + * Normalizes platform-specific fullscreen change [events]{@glossary event}. + * + * @private + */ + ready(function() { + // no need for IE8 fallback, since it won't ever send this event + if (document.addEventListener) { + document.addEventListener('webkitfullscreenchange', utils.bind(fullscreen, 'detectFullscreenChangeEvent'), false); + document.addEventListener('mozfullscreenchange', utils.bind(fullscreen, 'detectFullscreenChangeEvent'), false); + document.addEventListener('fullscreenchange', utils.bind(fullscreen, 'detectFullscreenChangeEvent'), false); + } + }); + + /** + * If this platform doesn't have native support for fullscreen, add an escape handler to mimic + * native behavior. + */ + if(!fullscreen.nativeSupport()) { + dispatcher.features.push( + function(e) { + if (e.type === 'keydown' && e.keyCode === 27) { + fullscreen.cancelFullscreen(); + } + } + ); + } + + return fullscreen; +}; \ No newline at end of file diff --git a/lib/fullscreen/fullscreen.less b/lib/Control/fullscreen.less similarity index 100% rename from lib/fullscreen/fullscreen.less rename to lib/Control/fullscreen.less diff --git a/lib/fullscreen/package.json b/lib/Control/package.json similarity index 61% rename from lib/fullscreen/package.json rename to lib/Control/package.json index 29aabd187..159482766 100644 --- a/lib/fullscreen/package.json +++ b/lib/Control/package.json @@ -1,5 +1,5 @@ { - "main": "fullscreen.js", + "main": "Control.js", "styles": [ "fullscreen.less" ] diff --git a/lib/RichText/RichText.js b/lib/RichText/RichText.js index 0588fbaae..c1e57fa4c 100644 --- a/lib/RichText/RichText.js +++ b/lib/RichText/RichText.js @@ -12,7 +12,9 @@ var options = require('../options'), utils = require('../utils'), platform = require('../platform'); + var + Control = require('../Control'), Input = require('../Input'), RichTextAccessibilitySupport = require('./RichTextAccessibilitySupport'); diff --git a/lib/Scroller/Scroller.js b/lib/Scroller/Scroller.js index 430569b3c..377c070ef 100644 --- a/lib/Scroller/Scroller.js +++ b/lib/Scroller/Scroller.js @@ -9,7 +9,6 @@ require('enyo'); var kind = require('../kind'), - utils = require('../utils'), platform = require('../platform'); var diff --git a/lib/SpriteAnimation/SpriteAnimation.js b/lib/SpriteAnimation/SpriteAnimation.js index e3ea6b446..ad32a32ad 100644 --- a/lib/SpriteAnimation/SpriteAnimation.js +++ b/lib/SpriteAnimation/SpriteAnimation.js @@ -6,12 +6,12 @@ require('enyo'); */ var - kind = require('../kind'); + kind = require('../kind'), scope = window || global; var Control = require('../Control'), - Image = require('../Image'), + EnyoImage = require('../Image'), StylesheetSupport = require('../StylesheetSupport'); /** @@ -213,7 +213,7 @@ module.exports = kind( * @private */ components: [ - {kind: Image, name: 'spriteImage', classes: 'enyo-sprite-animation-image', mixins: [StylesheetSupport], sizing: 'cover'} + {kind: EnyoImage, name: 'spriteImage', classes: 'enyo-sprite-animation-image', mixins: [StylesheetSupport], sizing: 'cover'} ], /** diff --git a/lib/fullscreen/fullscreen.js b/lib/fullscreen/fullscreen.js deleted file mode 100644 index 7786182a0..000000000 --- a/lib/fullscreen/fullscreen.js +++ /dev/null @@ -1,262 +0,0 @@ -require('enyo'); - -var - dispatcher = require('../dispatcher'), - utils = require('../utils'); -var - Signals = require('../Signals'), - ready = require('../ready'); - -/** -* Normalizes and provides fullscreen support for [controls]{@link module:enyo/Control~Control}, -* based on the [fullscreen]{@glossary fullscreen} API. -* -* @module enyo/fullscreen -* @public -*/ -var fullscreen = module.exports = { - - /** - * Reference to the current fullscreen [control]{@link module:enyo/Control~Control}. - * - * @private - */ - fullscreenControl: null, - - /** - * Reference to the current fullscreen element (fallback for platforms - * without native support). - * - * @private - */ - fullscreenElement: null, - - /** - * Reference to that [control]{@link module:enyo/Control~Control} that requested fullscreen. - * - * @private - */ - requestor: null, - - /** - * Native accessor used to get reference to the current fullscreen element. - * - * @private - */ - elementAccessor: - ('fullscreenElement' in document) ? 'fullscreenElement' : - ('mozFullScreenElement' in document) ? 'mozFullScreenElement' : - ('webkitFullscreenElement' in document) ? 'webkitFullscreenElement' : - null, - - /** - * Native accessor used to request fullscreen. - * - * @private - */ - requestAccessor: - ('requestFullscreen' in document.documentElement) ? 'requestFullscreen' : - ('mozRequestFullScreen' in document.documentElement) ? 'mozRequestFullScreen' : - ('webkitRequestFullscreen' in document.documentElement) ? 'webkitRequestFullscreen' : - null, - - /** - * Native accessor used to cancel fullscreen. - * - * @private - */ - cancelAccessor: - ('cancelFullScreen' in document) ? 'cancelFullScreen' : - ('mozCancelFullScreen' in document) ? 'mozCancelFullScreen' : - ('webkitCancelFullScreen' in document) ? 'webkitCancelFullScreen' : - null, - - /** - * Determines whether the platform supports the [fullscreen]{@glossary fullscreen} API. - * - * @returns {Boolean} Returns `true` if platform supports all of the - * [fullscreen]{@glossary fullscreen} API, `false` otherwise. - * @public - */ - nativeSupport: function() { - return (this.elementAccessor !== null && this.requestAccessor !== null && this.cancelAccessor !== null); - }, - - /** - * Normalizes `getFullscreenElement()`. - * - * @public - */ - getFullscreenElement: function() { - return (this.nativeSupport()) ? document[this.elementAccessor] : this.fullscreenElement; - }, - - /** - * Returns current fullscreen [control]{@link module:enyo/Control~Control}. - * - * @public - */ - getFullscreenControl: function() { - return this.fullscreenControl; - }, - - /** - * Normalizes `requestFullscreen()`. - * - * @public - */ - requestFullscreen: function(ctl) { - if (this.getFullscreenControl() || !(ctl.hasNode())) { - return false; - } - - this.requestor = ctl; - - // Only use native request if platform supports all of the API - if (this.nativeSupport()) { - ctl.hasNode()[this.requestAccessor](); - } else { - this.fallbackRequestFullscreen(); - } - - return true; - }, - - /** - * Normalizes `cancelFullscreen()`. - * - * @public - */ - cancelFullscreen: function() { - if (this.nativeSupport()) { - document[this.cancelAccessor](); - } else { - this.fallbackCancelFullscreen(); - } - }, - - /** - * Fallback support for setting fullscreen element (done by browser on platforms with - * native support). - * - * @private - */ - setFullscreenElement: function(node) { - this.fullscreenElement = node; - }, - - /** - * Sets current fullscreen [control]{@link module:enyo/Control~Control}. - * - * @private - */ - setFullscreenControl: function(ctl) { - this.fullscreenControl = ctl; - }, - - /** - * Fallback fullscreen request for platforms without fullscreen support. - * - * @private - */ - fallbackRequestFullscreen: function() { - var control = this.requestor; - - if (!control) { - return; - } - - // Get before node to allow us to exit floating layer to the proper position - control.prevAddBefore = control.parent.controlAtIndex(control.indexInContainer() + 1); - - var floatingLayer = Control.floatingLayer; - - // Render floating layer if we need to - if (!floatingLayer.hasNode()) { - floatingLayer.render(); - } - - control.addClass('enyo-fullscreen'); - control.appendNodeToParent(floatingLayer.hasNode()); - control.resize(); - - this.setFullscreenControl(control); - this.setFullscreenElement(control.hasNode()); - }, - - /** - * Fallback cancel fullscreen for platforms without fullscreen support. - * - * @private - */ - fallbackCancelFullscreen: function() { - var control = this.fullscreenControl, - beforeNode, - parentNode - ; - - if (!control) { - return; - } - - // Find beforeNode based on _this.addBefore_ and _this.prevAddBefore_ - beforeNode = (control.prevAddBefore) ? control.prevAddBefore.hasNode() : null; - parentNode = control.parent.hasNode(); - control.prevAddBefore = null; - - control.removeClass('enyo-fullscreen'); - - if (!beforeNode) { - control.appendNodeToParent(parentNode); - } else { - control.insertNodeInParent(parentNode, beforeNode); - } - - control.resize(); - - this.setFullscreenControl(null); - this.setFullscreenElement(null); - }, - - /** - * Listens for fullscreen change {@glossary event} and broadcasts it as a - * normalized event. - * - * @private - */ - detectFullscreenChangeEvent: function() { - this.setFullscreenControl(this.requestor); - this.requestor = null; - - // Broadcast change - Signals.send('onFullscreenChange'); - } -}; - -/** -* Normalizes platform-specific fullscreen change [events]{@glossary event}. -* -* @private -*/ -ready(function() { - // no need for IE8 fallback, since it won't ever send this event - if (document.addEventListener) { - document.addEventListener('webkitfullscreenchange', utils.bind(fullscreen, 'detectFullscreenChangeEvent'), false); - document.addEventListener('mozfullscreenchange', utils.bind(fullscreen, 'detectFullscreenChangeEvent'), false); - document.addEventListener('fullscreenchange', utils.bind(fullscreen, 'detectFullscreenChangeEvent'), false); - } -}); - -/** -* If this platform doesn't have native support for fullscreen, add an escape handler to mimic -* native behavior. -*/ -if(!fullscreen.nativeSupport()) { - dispatcher.features.push( - function(e) { - if (e.type === 'keydown' && e.keyCode === 27) { - fullscreen.cancelFullscreen(); - } - } - ); -} diff --git a/lib/gesture/drag.js b/lib/gesture/drag.js index 1b1c549a7..8de40002d 100644 --- a/lib/gesture/drag.js +++ b/lib/gesture/drag.js @@ -33,7 +33,7 @@ var * @module enyo/gesture/drag * @public */ -var drag = module.exports = { +module.exports = { /** * @private diff --git a/lib/gesture/touchGestures.js b/lib/gesture/touchGestures.js index 91be53901..eed1a4cb0 100644 --- a/lib/gesture/touchGestures.js +++ b/lib/gesture/touchGestures.js @@ -1,6 +1,5 @@ var dispatcher = require('../dispatcher'), - platform = require('../platform'), utils = require('../utils'); /** @@ -18,7 +17,7 @@ var * @module enyo/gesture/touchGestures * @private */ -var touchGestures = module.exports = { +module.exports = { /** * @private From abb007d2952e708f688e0e4b2a8adb4024cf852b Mon Sep 17 00:00:00 2001 From: Gray Norton Date: Fri, 21 Aug 2015 16:55:22 -0700 Subject: [PATCH 029/195] ENYO-2313: In enyo/Image, ensure that placeholder image fits At least in cases where the image size was not explicitly specified and the sizing option was not being used, the placeholder image was not properly scaled to the size of the image element. Some simple CSS refactoring takes care of that. Enyo-DCO-1.1-Signed-Off-By: Gray Norton (gray.norton@lge.com) --- lib/Image/Image.css | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/Image/Image.css b/lib/Image/Image.css index a795b7638..e8e51c5b7 100644 --- a/lib/Image/Image.css +++ b/lib/Image/Image.css @@ -1,7 +1,10 @@ -.enyo-image.sized { - display: inline-block; +.enyo-image { background-position: center; background-repeat: no-repeat; + background-size: cover; +} +.enyo-image.sized { + display: inline-block; } .enyo-image.contain { background-size: contain; From a55d8014e029f3d4f41a4f303b96cec4152c8b3c Mon Sep 17 00:00:00 2001 From: Anish_Ramesan Date: Mon, 24 Aug 2015 16:43:41 +0530 Subject: [PATCH 030/195] Animations using matrix Animation transformation using matrix values. Enyo-DCO-1.1-Signed-off-by: Anish Ramesan --- lib/AnimationSupport/AnimationSupport.js | 6 +- lib/AnimationSupport/Core.js | 4 +- lib/AnimationSupport/Frame.js | 408 +++++++++++----------- lib/AnimationSupport/HierarchicalMixin.js | 4 +- lib/AnimationSupport/KeyFrame.js | 2 +- lib/AnimationSupport/Matrix.js | 159 +++++++++ lib/AnimationSupport/Tween.js | 104 +++--- lib/AnimationSupport/Vector.js | 100 ++++++ 8 files changed, 536 insertions(+), 251 deletions(-) create mode 100644 lib/AnimationSupport/Matrix.js create mode 100644 lib/AnimationSupport/Vector.js diff --git a/lib/AnimationSupport/AnimationSupport.js b/lib/AnimationSupport/AnimationSupport.js index 75435fed3..89945dec7 100644 --- a/lib/AnimationSupport/AnimationSupport.js +++ b/lib/AnimationSupport/AnimationSupport.js @@ -33,7 +33,7 @@ var AnimationSupport = { * @public */ setInitial: function (initial) { - this._start = initial; + this._startAnim = initial; }, /** @@ -46,7 +46,7 @@ var AnimationSupport = { prop = this.getAnimation(), init = frame.getCompoutedProperty(dom, prop, current); - utils.mixin(this, init); + utils.mixin(this, init); }, /** @@ -112,7 +112,7 @@ var AnimationSupport = { this._lastTime = this._startTime + this._duration; this.animating = true; this.active = active; - this.initiate(); + this.initiate(this.currentState); }, /** diff --git a/lib/AnimationSupport/Core.js b/lib/AnimationSupport/Core.js index 7b55fd9f8..1a27930bf 100644 --- a/lib/AnimationSupport/Core.js +++ b/lib/AnimationSupport/Core.js @@ -134,9 +134,9 @@ module.exports = kind.singleton({ curr = this.chracs[i]; if (curr && curr.ready()) { tween.update(curr, ts); - if (ts >= curr._lastTime) { - curr.completed(curr); + if (!curr._lastTime || ts >= curr._lastTime) { tween.complete(curr); + curr.completed(curr); if(!curr.active) { this.remove(curr); } diff --git a/lib/AnimationSupport/Frame.js b/lib/AnimationSupport/Frame.js index 69fcbf832..44d42bffa 100644 --- a/lib/AnimationSupport/Frame.js +++ b/lib/AnimationSupport/Frame.js @@ -1,171 +1,210 @@ require('enyo'); var - Dom = require('../dom'); + Dom = require('../dom'), + Vector = require('./Vector'), + Matrix = require('./Matrix'); + +var + COLOR = {"color": 1, "background-color": 1}, + TRANSFORM = {"translate": 1, "translateX": 1, "translateY": 1, "translateZ": 1, "rotateX": 1, "rotateY": 1, "rotateZ": 1, "rotate": 1, "skew": 1, "scale": 1, "perspective": 1}; + /** * Frame is a module responsible for providing animation features required for a frame. -* This module exposes bunch of animation API's like transform, matrix calculation, +* This module exposes bunch of animation API's like matrix calculation, * fetching inital DOM properties and also applying style updates to DOM. +* +* These methods need to be merged with DOM API's of enyo. * -* @module enyo/Frame +* @module enyo/AnimationSupport/Frame */ var frame = module.exports = { /** * @public + * Creates a matrix based on transformation vectors. + * @param: trns- translate vector + * rot - rotate quaternion vector + * sc - scale vector + * sq - sqew vector + * per - perspective vector */ - translate: function (x, y, z) { - return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y ? y : 0, z ? z : 0, 1]; - }, + recomposeMatrix: function(trns, rot, sc, sq, per) { + var i, + x = rot[0], + y = rot[1], + z = rot[2], + w = rot[3], + m = Matrix.identity(), + sM = Matrix.identity(), + rM = Matrix.identity(); - /** - * @public - */ - translateX: function (x) { - return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x ? x : 0, 0, 0, 1]; - }, + // apply perspective + if(per) { + m[3] = per[0]; + m[7] = per[1]; + m[11] = per[2]; + m[15] = per[3]; + } - /** - * @public - */ - translateY: function (y) { - return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, y ? y : 0, 0, 1]; - }, + m[12] = trns[0]; + m[13] = trns[1]; + m[14] = trns[2]; - /** - * @public - */ - translateZ: function (z) { - return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, z ? z : 0, 1]; - }, + // apply rotate + rM[0] = 1 - 2 * (y * y + z * z); + rM[1] = 2 * (x * y - z * w); + rM[2] = 2 * (x * z + y * w); + rM[4] = 2 * (x * y + z * w); + rM[5] = 1 - 2 * (x * x + z * z); + rM[6] = 2 * (y * z - x * w); + rM[8] = 2 * (x * z - y * w); + rM[9] = 2 * (y * z + x * w); + rM[10] = 1 - 2 * (x * x + y * y); - /** - * @public - */ - scale: function (x, y, z) { - return [x, 0, 0, 0, 0, y ? y : 1, 0, 0, 0, 0, z ? z : 1, 0, 0, 0, 0, 1]; - }, + m = Matrix.multiply(m, rM); - /** - * @public - */ - skew: function (a, b) { - a = a ? Math.tan(a * Math.PI / 180): 0; - b = b ? Math.tan(b * Math.PI / 180): 0; - return [1, b, 0, 0, a, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; - }, + // apply skew + if (sq[2]) { + sM[9] = sq[2]; + m = Matrix.multiply(m, sM); + } - /** - * @public - */ - rotateX: function (a) { - var cosa, sina; - a = a * Math.PI / 180; - cosa = Math.cos(a); - sina = Math.sin(a); - return [1, 0, 0, 0, 0, cosa, -sina, 0, 0, sina, cosa, 0, 0, 0, 0, 1]; - }, + if (sq[1]) { + sM[9] = 0; + sM[8] = sq[1]; + m = Matrix.multiply(m, sM); + } - /** - * @public - */ - rotateY: function (b) { - var cosb, sinb; - b = b * Math.PI / 180; - cosb = Math.cos(b); - sinb = Math.sin(b); - return [cosb, 0, sinb, 0, 0, 1, 0, 0, -sinb, 0, cosb, 0, 0, 0, 0, 1]; - }, + if (sq[0]) { + sM[8] = 0; + sM[4] = sq[0]; + m = Matrix.multiply(m, sM); + } - /** - * @public - */ - rotateZ: function (g) { - var cosg, sing; - g = g * Math.PI / 180; - cosg = Math.cos(g); - sing = Math.sin(g); - return [cosg, -sing, 0, 0, sing, cosg, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + // apply scale + for (i = 0; i < 12; i += 4) { + m[0 + i] *= sc[0]; + m[1 + i] *= sc[1]; + m[2 + i] *= sc[2]; + } + return m; }, /** * @public + * Get transformation vectors out of matrix + * @param matrix - Transformation matrix + * ret - Return object which holds translate, + * rotate, scale, sqew & perspective. */ - rotate: function (a, b, g) { - a = a * Math.PI / 180; - b = b * Math.PI / 180; - g = g * Math.PI / 180; - var ca = Math.cos(a); - var sa = Math.sin(a); - var cb = Math.cos(b); - var sb = Math.sin(b); - var cg = Math.cos(g); - var sg = Math.sin(g); - var m = [ - cb * cg, - ca * sg + sa * sb * cg, - sa * sg - ca * sb * cg, - 0, - -cb * sg, - ca * cg - sa * sb * sg, - sa * cg + ca * sb * sg, - 0, - sb, - -sa * cb, - ca * cb, - 0, - 0, 0, 0, 1 - ]; - return m; + decomposeMatrix: function(matrix, ret) { + var i, + tV = [], + rV = [], + pV = [], + skV = [], + scV = [], + row = [], + pdum3 = {}; + + if (matrix[15] === 0) return false; + + for (i = 0; i < 16; i++) + matrix[0] /= matrix[15]; + + //TODO: decompose perspective + pV = [0, 0, 0, 0]; + + for (i = 0; i < 3; i++) + tV[i] = matrix[12 + i]; + + for (i = 0; i < 12; i += 4) { + row.push([ + matrix[0 + i], + matrix[1 + i], + matrix[2 + i] + ]); + } + + scV[0] = Vector.len(row[0]); + row[0] = Vector.normalize(row[0]); + skV[0] = Vector.dot(row[0], row[1]); + row[1] = Vector.combine(row[1], row[0], 1.0, -skV[0]); + + scV[1] = Vector.len(row[1]); + row[1] = Vector.normalize(row[1]); + skV[0] /= scV[1]; + + // Compute XZ and YZ shears, orthogonalize 3rd row + skV[1] = Vector.dot(row[0], row[2]); + row[2] = Vector.combine(row[2], row[0], 1.0, -skV[1]); + skV[2] = Vector.dot(row[1], row[2]); + row[2] = Vector.combine(row[2], row[1], 1.0, -skV[2]); + + // Next, get Z scale and normalize 3rd row. + scV[2] = Vector.len(row[2]); + row[2] = Vector.normalize(row[2]); + skV[1] /= scV[2]; + skV[2] /= scV[2]; + + pdum3 = Vector.cross(row[1], row[2]); + if (Vector.dot(row[0], pdum3) < 0) { + for (i = 0; i < 3; i++) { + scV[i] *= -1; + row[i][0] *= -1; + row[i][1] *= -1; + row[i][2] *= -1; + } + } + + rV[0] = 0.5 * Math.sqrt(Math.max(1 + row[0][0] - row[1][1] - row[2][2], 0)); + rV[1] = 0.5 * Math.sqrt(Math.max(1 - row[0][0] + row[1][1] - row[2][2], 0)); + rV[2] = 0.5 * Math.sqrt(Math.max(1 - row[0][0] - row[1][1] + row[2][2], 0)); + rV[3] = 0.5 * Math.sqrt(Math.max(1 + row[0][0] + row[1][1] + row[2][2], 0)); + + if (row[2][1] > row[1][2]) rV[0] = -rV[0]; + if (row[0][2] > row[2][0]) rV[1] = -rV[1]; + if (row[1][0] > row[0][1]) rV[2] = -rV[2]; + + ret.translate = tV; + ret.rotate = rV; + ret.scale = scV; + ret.skew = skV; + ret.perspective = pV; + return true; }, /** + * Clones an array based on offset value. * @public */ - multiply: function(m1, m2) { - return [ - m1[0] * m2[0] + m1[4] * m2[1] + m1[8] * m2[2], - m1[1] * m2[0] + m1[5] * m2[1] + m1[9] * m2[2], - m1[2] * m2[0] + m1[6] * m2[1] + m1[10] * m2[2], - 0, - m1[0] * m2[4] + m1[4] * m2[5] + m1[8] * m2[6], - m1[1] * m2[4] + m1[5] * m2[5] + m1[9] * m2[6], - m1[2] * m2[4] + m1[6] * m2[5] + m1[10] * m2[6], - 0, - m1[0] * m2[8] + m1[4] * m2[9] + m1[8] * m2[10], - m1[1] * m2[8] + m1[5] * m2[9] + m1[9] * m2[10], - m1[2] * m2[8] + m1[6] * m2[9] + m1[10] * m2[10], - 0, - m1[0] * m2[12] + m1[4] * m2[13] + m1[8] * m2[14] + m1[12], - m1[1] * m2[12] + m1[5] * m2[13] + m1[9] * m2[14] + m1[13], - m1[2] * m2[12] + m1[6] * m2[13] + m1[10] * m2[14] + m1[14], - 1 - ]; + copy: function(v, offset) { + return Array.prototype.slice.call(v, offset || 0); }, /** + * Validates if property is a transform property. * @public */ - formatMatrix: function(m) { - var matrix = 'matrix3d('; - for (var i = 0; i < 15; i++) { - matrix += (m[i] < 0.000001 && m[i] > -0.000001) ? '0,' : m[i] + ','; - } - matrix += m[15] + ')'; - return matrix; + isTransform: function(transform) { + return TRANSFORM[transform]; }, /** + * Applies trasnformation to DOM element with the matrix values. * @public */ - isTransform: function(transform) { - return this.TRANSFORM[transform]; + accelerate: function (ele, m) { + m = m ? m : Matrix.identity(); + frame.setTransformProperty(ele, m); }, /** + * Reform matrix 2D to 3D * @public */ parseMatrix: function (v) { - var m = this.IDENTIY; + var m = Matrix.identity(); v = v.replace(/^\w*\(/, '').replace(')', ''); v = this.parseValue(v); if (v.length <= 6) { @@ -182,6 +221,7 @@ var frame = module.exports = { }, /** + * Converts comma seperated values to array. * @public */ parseValue: function (val) { @@ -191,6 +231,7 @@ var frame = module.exports = { }, /** + * Gets a matrix for DOM element. * @public */ getMatrix: function (style) { @@ -206,6 +247,9 @@ var frame = module.exports = { }, /** + * Gets a style property applied from the DOM element. + * @param: style - Computed style of a DOM. + * key - property name for which style has to be fetched. * @public */ getStyleValue: function (style, key) { @@ -213,44 +257,37 @@ var frame = module.exports = { if (v === undefined || v === null || v == "auto" || isNaN(v)) { return 0; } - if (frame.COLOR[key]) { + if (COLOR[key]) { return v.replace(/^\w*\(/, '').replace(')', ''); } v = parseFloat(v, 10); return v; }, - /** - * @public - */ - accelerate: function (ele, m) { - m = m ? m : this.IDENTIY; - this.setTransformProperty(ele, m); - }, /** + * Applies style property to DOM element. * @public */ - setProperty: function (element, elProperty, elValue) { - if (this.COLOR[elProperty]) { - elValue = elValue.map(function(v) { - return parseInt(v, 10); - }); - element.style[elProperty] = "rgb("+ elValue + ")"; - } else if (elProperty == "opacity") { - var opacity = elValue[0].toFixed(6); - opacity = (opacity <= 0) ? '0.000001' : opacity; - element.style.opacity = opacity; + setProperty: function (ele, prop, val) { + if (COLOR[prop]) { + val = val.map(function(v) { return parseInt(v, 10);}); + val = 'rgb('+ val + ')'; + } else if (prop == 'opacity') { + val = val[0].toFixed(6); + val = (val <= 0) ? '0.000001' : val; } else { - element.style[elProperty] = elValue[0] + "px"; + val = val[0] + 'px'; } + ele.style[prop] = val; }, /** + * Applies transform property to DOM element. * @public */ setTransformProperty: function (element, matrix) { - var mat = this.formatMatrix(matrix); + var mat = Matrix.toString(matrix); element.style.transform = mat; element.style.webkitTransform = mat; element.style.MozTransform = mat; @@ -259,68 +296,41 @@ var frame = module.exports = { }, /** + * Get DOM node animation properties. + * @param: node- DOM node + * props- Properties to fetch from DOM. + * initial-Default properties to be applied. * @public */ - getCompoutedProperty: function (node, props, inital) { - if(!node && !props) return; - - var end = {}, - start = {}, - matrix = "", - key, val, - style = Dom.getComputedStyle(node); - - //initilize start and end values - for (key in this.TRANSFORM) { - if (props[key]) { - start[key] = inital ? inital[key] : undefined; - end[key] = undefined; + getCompoutedProperty: function (node, props, initial) { + if(!node || !props) return; + + var eP = {}, + sP = {}, + tP = {}, + dP = {}, + m, k, v, + s = Dom.getComputedStyle(node); + + for (k in props) { + v = sP[k]; + if (!this.isTransform(k)) { + v = v || this.getStyleValue(s, k); + eP[k] = this.parseValue(props[k]); + sP[k] = this.parseValue(v); + } else { + v = this.parseValue(props[k]); + tP[k] = k == 'rotate' ? Vector.toQuant(v) : v; } } - for (key in props) { - val = start[key]; - if (!val) { - if (this.isTransform(key)) { - matrix = this.getMatrix(style); - val = ""; - } else { - val = this.getStyleValue(style, key); - } + m = this.getMatrix(s) || Matrix.identity(); + if(m && this.decomposeMatrix(m, dP)){ + for(k in dP) { + sP[k] = dP[k]; + eP[k] = tP[k] || dP[k]; } - end[key] = this.parseValue(props[key]); - start[key] = this.parseValue(val); } - - return {_start: start, _end: end, _matrix: matrix}; + return {_startAnim: sP, _endAnim: eP, _transform: dP}; } -}; - -/** -* @private -*/ -frame.TRANSFORM = { - "translate": 1, - "translateX": 1, - "translateY": 1, - "translateZ": 1, - "rotateX": 1, - "rotateY": 1, - "rotateZ": 1, - "rotate": 1, - "skew": 1, - "scale": 1 -}; - -/** -* @private -*/ -frame.COLOR = { - "color" : 1, - "background-color": 1 -}; - -/** -* @private -*/ -frame.IDENTIY = [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]; \ No newline at end of file +}; \ No newline at end of file diff --git a/lib/AnimationSupport/HierarchicalMixin.js b/lib/AnimationSupport/HierarchicalMixin.js index c3144cc15..e618f58c4 100644 --- a/lib/AnimationSupport/HierarchicalMixin.js +++ b/lib/AnimationSupport/HierarchicalMixin.js @@ -112,9 +112,9 @@ VerticalDelegate.generate = function(list) { c.animate = list.animate; c.duration = list.duration; animation.trigger(c); - if(list._getNodeShowing(c, 0)) { + if (list._getNodeShowing(c, 0)) { c.start(true); - } + } } } }; \ No newline at end of file diff --git a/lib/AnimationSupport/KeyFrame.js b/lib/AnimationSupport/KeyFrame.js index fa2b6bc58..bfd87caa8 100644 --- a/lib/AnimationSupport/KeyFrame.js +++ b/lib/AnimationSupport/KeyFrame.js @@ -73,7 +73,7 @@ var keyFrame = module.exports = kind.singleton({ var charc = animation.exists(dom), finalState, duration; if (charc) { - finalState = charc._start; + finalState = charc._startAnim; duration = utils.perfNow() - charc.initialTime; animation.remove(charc); diff --git a/lib/AnimationSupport/Matrix.js b/lib/AnimationSupport/Matrix.js new file mode 100644 index 000000000..c9186a01c --- /dev/null +++ b/lib/AnimationSupport/Matrix.js @@ -0,0 +1,159 @@ +require('enyo'); + +/** +* Matrix module for matrix related calculation +* +* @module enyo/AnimationSupport/Matrix +*/ +module.exports = { + /** + * @public + */ + identity: function() { + return [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]; + }, + + /** + * @public + */ + translate: function (x, y, z) { + return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y ? y : 0, z ? z : 0, 1]; + }, + + /** + * @public + */ + translateX: function (x) { + return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x ? x : 0, 0, 0, 1]; + }, + + /** + * @public + */ + translateY: function (y) { + return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, y ? y : 0, 0, 1]; + }, + + /** + * @public + */ + translateZ: function (z) { + return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, z ? z : 0, 1]; + }, + + /** + * @public + */ + scale: function (x, y, z) { + return [x, 0, 0, 0, 0, y ? y : 1, 0, 0, 0, 0, z ? z : 1, 0, 0, 0, 0, 1]; + }, + + /** + * @public + */ + skew: function (a, b) { + a = a ? Math.tan(a * Math.PI / 180): 0; + b = b ? Math.tan(b * Math.PI / 180): 0; + return [1, b, 0, 0, a, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + }, + + /** + * @public + */ + rotateX: function (a) { + var cosa, sina; + a = a * Math.PI / 180; + cosa = Math.cos(a); + sina = Math.sin(a); + return [1, 0, 0, 0, 0, cosa, -sina, 0, 0, sina, cosa, 0, 0, 0, 0, 1]; + }, + + /** + * @public + */ + rotateY: function (b) { + var cosb, sinb; + b = b * Math.PI / 180; + cosb = Math.cos(b); + sinb = Math.sin(b); + return [cosb, 0, sinb, 0, 0, 1, 0, 0, -sinb, 0, cosb, 0, 0, 0, 0, 1]; + }, + + /** + * @public + */ + rotateZ: function (g) { + var cosg, sing; + g = g * Math.PI / 180; + cosg = Math.cos(g); + sing = Math.sin(g); + return [cosg, -sing, 0, 0, sing, cosg, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + }, + + /** + * @public + */ + rotate: function (a, b, g) { + a = a * Math.PI / 180; + b = b * Math.PI / 180; + g = g * Math.PI / 180; + var ca = Math.cos(a); + var sa = Math.sin(a); + var cb = Math.cos(b); + var sb = Math.sin(b); + var cg = Math.cos(g); + var sg = Math.sin(g); + var m = [ + cb * cg, + ca * sg + sa * sb * cg, + sa * sg - ca * sb * cg, + 0, + -cb * sg, + ca * cg - sa * sb * sg, + sa * cg + ca * sb * sg, + 0, + sb, + -sa * cb, + ca * cb, + 0, + 0, 0, 0, 1 + ]; + return m; + }, + + /** + * @public + */ + multiply: function(m1, m2) { + return [ + m1[0] * m2[0] + m1[4] * m2[1] + m1[8] * m2[2], + m1[1] * m2[0] + m1[5] * m2[1] + m1[9] * m2[2], + m1[2] * m2[0] + m1[6] * m2[1] + m1[10] * m2[2], + 0, + m1[0] * m2[4] + m1[4] * m2[5] + m1[8] * m2[6], + m1[1] * m2[4] + m1[5] * m2[5] + m1[9] * m2[6], + m1[2] * m2[4] + m1[6] * m2[5] + m1[10] * m2[6], + 0, + m1[0] * m2[8] + m1[4] * m2[9] + m1[8] * m2[10], + m1[1] * m2[8] + m1[5] * m2[9] + m1[9] * m2[10], + m1[2] * m2[8] + m1[6] * m2[9] + m1[10] * m2[10], + 0, + m1[0] * m2[12] + m1[4] * m2[13] + m1[8] * m2[14] + m1[12], + m1[1] * m2[12] + m1[5] * m2[13] + m1[9] * m2[14] + m1[13], + m1[2] * m2[12] + m1[6] * m2[13] + m1[10] * m2[14] + m1[14], + 1 + ]; + }, + + /** + * @public + */ + toString: function (m) { + var ms = 'matrix3d('; + for (var i = 0; i < 15; i++) { + ms += (m[i] < 0.000001 && m[i] > -0.000001) ? '0,' : m[i] + ','; + } + ms += m[15] + ')'; + return ms; + } +}; \ No newline at end of file diff --git a/lib/AnimationSupport/Tween.js b/lib/AnimationSupport/Tween.js index e427d0eca..978895506 100644 --- a/lib/AnimationSupport/Tween.js +++ b/lib/AnimationSupport/Tween.js @@ -1,14 +1,17 @@ require('enyo'); var - frame = require('./Frame'); + frame = require('./Frame'), + Vector = require('./Vector'); + +var oldState, newState, node, matrix, cState = []; /** * Tween is a module responsible for creating intermediate frames for an animation. * The responsibilities of this module is to; * - Interpolating current state of character. * - Update DOM based on current state, using matrix for tranform and styles for others. * -* @module enyo/Tween +* @module enyo/AnimationSupport/Tween */ module.exports = { /** @@ -42,33 +45,33 @@ module.exports = { * @private */ step: function(charc, t) { - var key, i, - matrix, - start, end, - states = [], - dom = charc.node, - prop = charc.getAnimation(), - fMatrix = frame.IDENTIY, - iMatrix = charc._matrix || frame.IDENTIY; + var k, c; - charc.currentState = {}; - for (key in prop) { - start = charc._start[key]; - end = charc._end[key]; - for (i = 0; i < end.length; i++) { - states[i] = t ? this.interpolate(start[i], end[i], this.ease(t)): end[i]; - } - if (frame.isTransform(key)) { - matrix = frame[key](states[0], states[1], states[2]); - fMatrix = frame.multiply(matrix, iMatrix); - iMatrix = fMatrix; - } else { - frame.setProperty(dom, key, states); + node = charc.node; + newState = charc._endAnim; + oldState = charc._startAnim; + charc.currentState = charc.currentState || {}; + + for (k in newState) { + cState = frame.copy(charc.currentState[k] || []); + c = k == 'rotate'? this.slerp : this.lerp; + cState = t ? c(oldState[k], newState[k], this.ease(t), cState) : newState[k]; + if (!frame.isTransform(k)) { + frame.setProperty(node, k, cState); } - charc.currentState[key] = states; + charc.currentState[k] = cState; } - if (fMatrix) this.accelerate(dom, fMatrix); + if(charc._transform) { + matrix = frame.recomposeMatrix( + charc.currentState.translate, + charc.currentState.rotate, + charc.currentState.scale, + charc.currentState.skew, + charc.currentState.perspective + ); + frame.accelerate(node, matrix); + } }, /** @@ -77,27 +80,40 @@ module.exports = { ease: function (t) { return t; }, - - /** - * @private - */ - interpolate: function(a, b, t) { - a = parseFloat(a || 0, 10); - b = parseFloat(b || 0, 10); - return ((1 - t) * a) + (t * b); - }, - - /** - * @private - */ - accelerate: function (ele, m) { - m = m ? m : frame.IDENTIY; - frame.setTransformProperty(ele, m); - }, complete: function (charc) { charc.animating = false; - charc._start = undefined; - charc._end = undefined; + charc._startAnim = undefined; + charc._endAnim = undefined; + }, + + lerp: function (vA, vB, t, vR) { + if (!vR) vR = []; + var i, l = vA.length; + + for (i = 0; i < l; i++) { + vR[i] = (1 - t) * vA[i] + t * vB[i]; + } + return vR; + }, + + slerp: function (qA, qB, t, qR) { + if (!qR) qR = []; + var a, b, w, theta, dot = Vector.dot(qA, qB); + + dot = Math.min(Math.max(dot, -1.0), 1.0); + if (dot == 1.0) { + qR = frame.copy(qA); + return qR; + } + + theta = Math.acos(dot); + w = Math.sin(t * theta) * 1 / Math.sqrt(1 - dot * dot); + for (var i = 0; i < 4; i++) { + a = qA[i] * (Math.cos(t * theta) - dot * w); + b = qB[i] * w; + qR[i] = a + b; + } + return qR; } }; \ No newline at end of file diff --git a/lib/AnimationSupport/Vector.js b/lib/AnimationSupport/Vector.js new file mode 100644 index 000000000..a55df1cb3 --- /dev/null +++ b/lib/AnimationSupport/Vector.js @@ -0,0 +1,100 @@ +require('enyo'); + +/** +* Vector module for vector related calculations. +* Also provides API's for Quaternion vectors for rotation. +* +* @module enyo/AnimationSupport/Vector +*/ +module.exports = { + /** + * Divides vector with a scalar value. + * @public + */ + divide: function (q, s) { + return [q[0] / s, q[1] / s, q[2] / s]; + }, + + /** + * Length of 3D vectors + * @public + */ + len: function (q) { + return Math.sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2]); + }, + + /** + * Dot product of 3D vectors + * @public + */ + dot: function (q1, q2) { + return (q1[0] * q2[0]) + (q1[1] * q2[1]) + (q1[2] * q2[2]) + q1[3] && q2[3] ? (q1[3] * q2[3]) : 0; + }, + + /** + * Cross product of two vectors + * @public + */ + cross: function (q1, q2) { + return [ + q1[1] * q2[2] - q1[2] * q2[1], + q1[2] * q2[0] - q1[0] * q2[2], + q1[0] * q2[1] - q1[1] * q2[0] + ]; + }, + + /** + * Normalizing a vector is obtaining another unit vector in the same direction. + * To normalize a vector, divide the vector by its magnitude. + * @public + */ + normalize: function (q) { + return this.divide(q, this.len(q)); + }, + + /** + * Combine scalar values with two vectors. + * Required during parsing scaler values matrix. + * @public + */ + combine: function (a, b, ascl, bscl) { + return [ + (ascl * a[0]) + (bscl * b[0]), + (ascl * a[1]) + (bscl * b[1]), + (ascl * a[2]) + (bscl * b[2]) + ]; + }, + + /** + * Converts a quaternion vector to a rotation vector. + * @public + */ + toVector: function (rv) { + var r = 2 * Math.acos(rv[3]); + var sA = Math.sqrt(1.0 - rv[3] * rv[3]); + if (Math.abs(sA) < 0.0005) sA = 1; + return [rv[0] / sA, rv[1] / sA, rv[2] / sA, r * 180 / Math.PI]; + }, + + /** + * Converts a rotation vector to a quaternion vector. + * @public + */ + toQuant: function (q) { + if (!q) q = []; + + var x = q[0] || 0, + y = q[1] || 0, + z = q[2] || 0, + deg = q[3] || 0, + r = deg * (Math.PI / 360), + sR = Math.sin(r), + cR = Math.cos(r); + + q[0] = x * sR; + q[1] = y * sR; + q[2] = z * sR; + q[3] = cR; + return q; + } +}; \ No newline at end of file From ae83acb5cd7991c56d74c560c8998b8a63e8736b Mon Sep 17 00:00:00 2001 From: Ryan Duffy Date: Mon, 24 Aug 2015 16:37:44 -0500 Subject: [PATCH 031/195] add updateHistory property to enyo/History When false, enyo/History will only maintain its internal history stack and will not update window.history. Issue: ENYO-2368 Enyo-DCO-1.1-Signed-off-by: Ryan Duffy (ryan.duffy@lge.com) --- lib/History.js | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/History.js b/lib/History.js index f0728370a..189a24435 100644 --- a/lib/History.js +++ b/lib/History.js @@ -80,6 +80,16 @@ var EnyoHistory = module.exports = kind.singleton( */ enabled: true, + /** + * When true, the browser's history will be updated when history entries are added or removed. If + * the platform does not support this feature, the value will always be false. The default is + * true if supported by the platform and false otherwise. + * + * @type {Boolean} + * @private + */ + updateHistory: _supports, + /** * @private */ @@ -100,6 +110,15 @@ var EnyoHistory = module.exports = kind.singleton( this.stopJob('history.go'); }, + /** + * Resets the value to false if the platform does not support the History API + * + * @private + */ + updateHistoryChanged: function () { + this.updateHistory = this.updateHistory && _supports; + }, + // Public methods /** @@ -300,8 +319,8 @@ var EnyoHistory = module.exports = kind.singleton( // defer the actual 'back' action so pop() or drop() can be called multiple times in the // same frame. Otherwise, only the first go() would be observed. this.startJob('history.go', function () { - // If the platform supports pushState and the effective change is positive - if (_supports && _popQueueCount > 0) { + // If we are able to and supposed to update history and there are pending pops + if (this.updateHistory && _popQueueCount > 0) { // go back that many entries global.history.go(-_popQueueCount); } else { @@ -338,7 +357,7 @@ var EnyoHistory = module.exports = kind.singleton( var id = entry.context && entry.context.id || 'anonymous', location = entry.location || ''; _history.push(entry); - if (_supports && !silenced) { + if (this.updateHistory && !silenced) { global.history.pushState({id: id}, '', location); } }, @@ -361,7 +380,7 @@ var EnyoHistory = module.exports = kind.singleton( */ handleKeyUp: function (sender, event) { var current = this.peek(); - if (event.keySymbol == 'back' && current && current.getShowing()) { + if (event.keySymbol == 'back' && current && current.context.getShowing()) { this.pop(); } return true; From f94e6c257833b13ba4ed3659b1c80dbfab0967e4 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Mon, 24 Aug 2015 18:54:17 -0700 Subject: [PATCH 032/195] Revert "Merge pull request #1256 from enyojs/ENYO-2313-graynorton" This reverts commit 65c07bdfb6a684c269b843232b465a682a573387, reversing changes made to ee55e3c801849a2e35a50f68357a51d6f6172e1b. --- lib/Image/Image.css | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/Image/Image.css b/lib/Image/Image.css index e8e51c5b7..a795b7638 100644 --- a/lib/Image/Image.css +++ b/lib/Image/Image.css @@ -1,10 +1,7 @@ -.enyo-image { - background-position: center; - background-repeat: no-repeat; - background-size: cover; -} .enyo-image.sized { display: inline-block; + background-position: center; + background-repeat: no-repeat; } .enyo-image.contain { background-size: contain; From aef2159c76665f3550b3b6197e7e80138a65e439 Mon Sep 17 00:00:00 2001 From: Cole Davis Date: Thu, 27 Aug 2015 13:11:45 -0700 Subject: [PATCH 033/195] update for 0.5.1 tools release --- .enyoconfig | 43 +++++++++++++++++++ .gitignore | 4 +- index.js | 2 +- package.json | 13 +++--- .../AccessibilitySupport.css | 0 .../AccessibilitySupport.js | 0 .../AccessibilitySupport/package.json | 0 {lib => src}/Ajax.js | 0 {lib => src}/AjaxProperties.js | 0 {lib => src}/AjaxSource.js | 0 {lib => src}/Anchor.js | 0 {lib => src}/Animator.js | 0 {lib => src}/Application.js | 0 {lib => src}/ApplicationSupport.js | 0 {lib => src}/Async.js | 0 {lib => src}/Audio.js | 0 {lib => src}/BackgroundTaskManager.js | 0 {lib => src}/BaseLayout.js | 0 {lib => src}/Binding.js | 0 {lib => src}/BindingSupport.js | 0 {lib => src}/Blob.js | 0 {lib => src}/BooleanBinding.js | 0 {lib => src}/BooleanOnlyBinding.js | 0 {lib => src}/BucketFilter.js | 0 {lib => src}/Button/Button.css | 0 {lib => src}/Button/Button.js | 0 .../Button/ButtonAccessibilitySupport.js | 0 {lib => src}/Button/package.json | 0 {lib => src}/Checkbox/Checkbox.js | 0 .../Checkbox/CheckboxAccessibilitySupport.js | 0 {lib => src}/Checkbox/package.json | 0 {lib => src}/Collection.js | 0 {lib => src}/Component.js | 0 {lib => src}/ComponentBindingSupport.js | 0 {lib => src}/ComputedSupport.js | 0 {lib => src}/ContentAreaSupport.js | 0 {lib => src}/Control/Control.js | 0 {lib => src}/Control/floatingLayer.js | 0 {lib => src}/Control/fullscreen.js | 0 {lib => src}/Control/fullscreen.less | 0 {lib => src}/Control/package.json | 0 {lib => src}/Controller.js | 0 {lib => src}/CoreObject.js | 0 {lib => src}/DataGridList/DataGridList.js | 0 {lib => src}/DataGridList/DataGridList.less | 0 {lib => src}/DataGridList/package.json | 0 {lib => src}/DataList/DataList.js | 0 {lib => src}/DataList/DataList.less | 0 {lib => src}/DataList/package.json | 0 {lib => src}/DataRepeater.js | 0 {lib => src}/DataTable.js | 0 {lib => src}/DragAvatar.js | 0 {lib => src}/Drawer.js | 0 {lib => src}/EmptyBinding.js | 0 {lib => src}/EventEmitter.js | 0 {lib => src}/Filter.js | 0 {lib => src}/FluxDispatcher.js | 0 {lib => src}/FluxStore.js | 0 {lib => src}/FormData.js | 0 {lib => src}/Group/Group.js | 0 .../Group/GroupAccessibilitySupport.js | 0 {lib => src}/Group/package.json | 0 {lib => src}/GroupItem.js | 0 {lib => src}/HTMLStringDelegate.js | 0 {lib => src}/History.js | 0 {lib => src}/HorizontalDelegate.js | 0 {lib => src}/Image/Image.css | 0 {lib => src}/Image/Image.js | 0 {lib => src}/Image/package.json | 0 {lib => src}/Input/Input.js | 0 .../Input/InputAccessibilitySupport.js | 0 {lib => src}/Input/package.json | 0 {lib => src}/InputBinding.js | 0 {lib => src}/InvertBooleanBinding.js | 0 {lib => src}/Jsonp.js | 0 {lib => src}/JsonpSource.js | 0 {lib => src}/Layout.js | 0 {lib => src}/LightPanels/LightPanel.js | 0 {lib => src}/LightPanels/LightPanels.css | 0 {lib => src}/LightPanels/LightPanels.js | 0 {lib => src}/LightPanels/package.json | 0 {lib => src}/LinkedList.js | 0 {lib => src}/LinkedListNode.js | 0 {lib => src}/LocalStorageSource.js | 0 {lib => src}/Loop.js | 0 {lib => src}/Media.js | 0 {lib => src}/MediaSource.js | 0 {lib => src}/MixinSupport.js | 0 {lib => src}/Model.js | 0 {lib => src}/ModelController.js | 0 {lib => src}/ModelList.js | 0 {lib => src}/MultipleDispatchComponent.js | 0 {lib => src}/MultipleDispatchSupport.js | 0 {lib => src}/NewAnimator.js | 0 {lib => src}/NewDataList.js | 0 {lib => src}/NewDrawer/NewDrawer.css | 0 {lib => src}/NewDrawer/NewDrawer.js | 0 {lib => src}/NewDrawer/package.json | 0 {lib => src}/NewThumb/NewThumb.css | 0 {lib => src}/NewThumb/NewThumb.js | 0 {lib => src}/NewThumb/package.json | 0 {lib => src}/ObserverChain.js | 0 {lib => src}/ObserverChainNode.js | 0 {lib => src}/ObserverSupport.js | 0 {lib => src}/Option.js | 0 {lib => src}/OptionGroup.js | 0 {lib => src}/PathResolverFactory.js | 0 {lib => src}/Popup/Popup.css | 0 {lib => src}/Popup/Popup.js | 0 .../Popup/PopupAccessibilitySupport.js | 0 {lib => src}/Popup/package.json | 0 {lib => src}/PriorityQueue.js | 0 {lib => src}/ProgressiveFilter.js | 0 {lib => src}/ProxyObject.js | 0 {lib => src}/Relation.js | 0 .../RelationalModel/RelationalCollection.js | 0 .../RelationalModel/RelationalModel.js | 0 {lib => src}/RelationalModel/index.js | 0 {lib => src}/RelationalModel/manyToMany.js | 0 {lib => src}/RelationalModel/package.json | 0 {lib => src}/RelationalModel/toMany.js | 0 {lib => src}/RelationalModel/toOne.js | 0 {lib => src}/Repeater.js | 0 {lib => src}/RepeaterChildSupport.js | 0 {lib => src}/RichText/RichText.css | 0 {lib => src}/RichText/RichText.js | 0 .../RichText/RichTextAccessibilitySupport.js | 0 {lib => src}/RichText/package.json | 0 {lib => src}/Router.js | 0 {lib => src}/Scrim/Scrim.css | 0 {lib => src}/Scrim/Scrim.js | 0 {lib => src}/Scrim/package.json | 0 {lib => src}/ScrollMath.js | 0 {lib => src}/ScrollStrategy.js | 0 {lib => src}/ScrollThumb/ScrollThumb.css | 0 {lib => src}/ScrollThumb/ScrollThumb.js | 0 {lib => src}/ScrollThumb/package.json | 0 {lib => src}/Scrollable/Scrollable.js | 0 {lib => src}/Scrollable/Scrollable.less | 0 {lib => src}/Scrollable/package.json | 0 {lib => src}/Scroller/Scroller.css | 0 {lib => src}/Scroller/Scroller.js | 0 {lib => src}/Scroller/package.json | 0 {lib => src}/Select.js | 0 {lib => src}/Selection.js | 0 {lib => src}/Signals.js | 0 {lib => src}/Source.js | 0 .../SpriteAnimation/SpriteAnimation.css | 0 .../SpriteAnimation/SpriteAnimation.js | 0 {lib => src}/SpriteAnimation/package.json | 0 {lib => src}/StateSupport.js | 0 {lib => src}/States.js | 0 {lib => src}/Store.js | 0 {lib => src}/StringBinding.js | 0 {lib => src}/Style.js | 0 {lib => src}/StylesheetSupport.js | 0 {lib => src}/SystemMonitor.js | 0 {lib => src}/Table/Table.js | 0 .../Table/TableAccessibilitySupport.js | 0 {lib => src}/Table/package.json | 0 {lib => src}/TableCell/TableCell.js | 0 .../TableCellAccessibilitySupport.js | 0 {lib => src}/TableCell/package.json | 0 {lib => src}/TableRow/TableRow.js | 0 .../TableRow/TableRowAccessibilitySupport.js | 0 {lib => src}/TableRow/package.json | 0 {lib => src}/TaskManagerSupport.js | 0 {lib => src}/TextArea/TextArea.js | 0 .../TextArea/TextAreaAccessibilitySupport.js | 0 {lib => src}/TextArea/package.json | 0 {lib => src}/ToolDecorator/ToolDecorator.css | 0 {lib => src}/ToolDecorator/ToolDecorator.js | 0 {lib => src}/ToolDecorator/package.json | 0 {lib => src}/TouchScrollStrategy.js | 0 {lib => src}/TransitionScrollStrategy.js | 0 {lib => src}/TranslateScrollStrategy.js | 0 {lib => src}/UiComponent.js | 0 {lib => src}/VerticalDelegate.js | 0 {lib => src}/VerticalGridDelegate.js | 0 {lib => src}/Video.js | 0 {lib => src}/ViewController.js | 0 {lib => src}/ViewPreloadSupport.js | 0 {lib => src}/VirtualDataRepeater.js | 0 {lib => src}/WebService.js | 0 {lib => src}/XhrSource.js | 0 {lib => src}/animation.js | 0 {lib => src}/cookie.js | 0 {lib => src}/dev.js | 0 {lib => src}/dispatcher.js | 0 {lib => src}/dom.js | 0 {lib => src}/gesture/drag.js | 0 {lib => src}/gesture/gesture.js | 0 {lib => src}/gesture/package.json | 0 {lib => src}/gesture/touchGestures.js | 0 {lib => src}/gesture/util.js | 0 {lib => src}/hooks.js | 0 {lib => src}/i18n.js | 0 {lib => src}/job.js | 0 {lib => src}/jobs.js | 0 {lib => src}/json.js | 0 {lib => src}/kind.js | 0 {lib => src}/logger.js | 0 {lib => src}/macroize.js | 0 {lib => src}/master.js | 0 {lib => src}/options.js | 0 {lib => src}/pageVisibility.js | 0 {lib => src}/path.js | 0 {lib => src}/pathResolver.js | 0 {lib => src}/platform.js | 0 {lib => src}/ready.js | 0 {lib => src}/resolution.js | 0 {lib => src}/roots.js | 0 {lib => src}/touch.js | 0 {lib => src}/utils.js | 0 {lib => src}/xhr.js | 0 215 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 .enyoconfig rename {lib => src}/AccessibilitySupport/AccessibilitySupport.css (100%) rename {lib => src}/AccessibilitySupport/AccessibilitySupport.js (100%) rename {lib => src}/AccessibilitySupport/package.json (100%) rename {lib => src}/Ajax.js (100%) rename {lib => src}/AjaxProperties.js (100%) rename {lib => src}/AjaxSource.js (100%) rename {lib => src}/Anchor.js (100%) rename {lib => src}/Animator.js (100%) rename {lib => src}/Application.js (100%) rename {lib => src}/ApplicationSupport.js (100%) rename {lib => src}/Async.js (100%) rename {lib => src}/Audio.js (100%) rename {lib => src}/BackgroundTaskManager.js (100%) rename {lib => src}/BaseLayout.js (100%) rename {lib => src}/Binding.js (100%) rename {lib => src}/BindingSupport.js (100%) rename {lib => src}/Blob.js (100%) rename {lib => src}/BooleanBinding.js (100%) rename {lib => src}/BooleanOnlyBinding.js (100%) rename {lib => src}/BucketFilter.js (100%) rename {lib => src}/Button/Button.css (100%) rename {lib => src}/Button/Button.js (100%) rename {lib => src}/Button/ButtonAccessibilitySupport.js (100%) rename {lib => src}/Button/package.json (100%) rename {lib => src}/Checkbox/Checkbox.js (100%) rename {lib => src}/Checkbox/CheckboxAccessibilitySupport.js (100%) rename {lib => src}/Checkbox/package.json (100%) rename {lib => src}/Collection.js (100%) rename {lib => src}/Component.js (100%) rename {lib => src}/ComponentBindingSupport.js (100%) rename {lib => src}/ComputedSupport.js (100%) rename {lib => src}/ContentAreaSupport.js (100%) rename {lib => src}/Control/Control.js (100%) rename {lib => src}/Control/floatingLayer.js (100%) rename {lib => src}/Control/fullscreen.js (100%) rename {lib => src}/Control/fullscreen.less (100%) rename {lib => src}/Control/package.json (100%) rename {lib => src}/Controller.js (100%) rename {lib => src}/CoreObject.js (100%) rename {lib => src}/DataGridList/DataGridList.js (100%) rename {lib => src}/DataGridList/DataGridList.less (100%) rename {lib => src}/DataGridList/package.json (100%) rename {lib => src}/DataList/DataList.js (100%) rename {lib => src}/DataList/DataList.less (100%) rename {lib => src}/DataList/package.json (100%) rename {lib => src}/DataRepeater.js (100%) rename {lib => src}/DataTable.js (100%) rename {lib => src}/DragAvatar.js (100%) rename {lib => src}/Drawer.js (100%) rename {lib => src}/EmptyBinding.js (100%) rename {lib => src}/EventEmitter.js (100%) rename {lib => src}/Filter.js (100%) rename {lib => src}/FluxDispatcher.js (100%) rename {lib => src}/FluxStore.js (100%) rename {lib => src}/FormData.js (100%) rename {lib => src}/Group/Group.js (100%) rename {lib => src}/Group/GroupAccessibilitySupport.js (100%) rename {lib => src}/Group/package.json (100%) rename {lib => src}/GroupItem.js (100%) rename {lib => src}/HTMLStringDelegate.js (100%) rename {lib => src}/History.js (100%) rename {lib => src}/HorizontalDelegate.js (100%) rename {lib => src}/Image/Image.css (100%) rename {lib => src}/Image/Image.js (100%) rename {lib => src}/Image/package.json (100%) rename {lib => src}/Input/Input.js (100%) rename {lib => src}/Input/InputAccessibilitySupport.js (100%) rename {lib => src}/Input/package.json (100%) rename {lib => src}/InputBinding.js (100%) rename {lib => src}/InvertBooleanBinding.js (100%) rename {lib => src}/Jsonp.js (100%) rename {lib => src}/JsonpSource.js (100%) rename {lib => src}/Layout.js (100%) rename {lib => src}/LightPanels/LightPanel.js (100%) rename {lib => src}/LightPanels/LightPanels.css (100%) rename {lib => src}/LightPanels/LightPanels.js (100%) rename {lib => src}/LightPanels/package.json (100%) rename {lib => src}/LinkedList.js (100%) rename {lib => src}/LinkedListNode.js (100%) rename {lib => src}/LocalStorageSource.js (100%) rename {lib => src}/Loop.js (100%) rename {lib => src}/Media.js (100%) rename {lib => src}/MediaSource.js (100%) rename {lib => src}/MixinSupport.js (100%) rename {lib => src}/Model.js (100%) rename {lib => src}/ModelController.js (100%) rename {lib => src}/ModelList.js (100%) rename {lib => src}/MultipleDispatchComponent.js (100%) rename {lib => src}/MultipleDispatchSupport.js (100%) rename {lib => src}/NewAnimator.js (100%) rename {lib => src}/NewDataList.js (100%) rename {lib => src}/NewDrawer/NewDrawer.css (100%) rename {lib => src}/NewDrawer/NewDrawer.js (100%) rename {lib => src}/NewDrawer/package.json (100%) rename {lib => src}/NewThumb/NewThumb.css (100%) rename {lib => src}/NewThumb/NewThumb.js (100%) rename {lib => src}/NewThumb/package.json (100%) rename {lib => src}/ObserverChain.js (100%) rename {lib => src}/ObserverChainNode.js (100%) rename {lib => src}/ObserverSupport.js (100%) rename {lib => src}/Option.js (100%) rename {lib => src}/OptionGroup.js (100%) rename {lib => src}/PathResolverFactory.js (100%) rename {lib => src}/Popup/Popup.css (100%) rename {lib => src}/Popup/Popup.js (100%) rename {lib => src}/Popup/PopupAccessibilitySupport.js (100%) rename {lib => src}/Popup/package.json (100%) rename {lib => src}/PriorityQueue.js (100%) rename {lib => src}/ProgressiveFilter.js (100%) rename {lib => src}/ProxyObject.js (100%) rename {lib => src}/Relation.js (100%) rename {lib => src}/RelationalModel/RelationalCollection.js (100%) rename {lib => src}/RelationalModel/RelationalModel.js (100%) rename {lib => src}/RelationalModel/index.js (100%) rename {lib => src}/RelationalModel/manyToMany.js (100%) rename {lib => src}/RelationalModel/package.json (100%) rename {lib => src}/RelationalModel/toMany.js (100%) rename {lib => src}/RelationalModel/toOne.js (100%) rename {lib => src}/Repeater.js (100%) rename {lib => src}/RepeaterChildSupport.js (100%) rename {lib => src}/RichText/RichText.css (100%) rename {lib => src}/RichText/RichText.js (100%) rename {lib => src}/RichText/RichTextAccessibilitySupport.js (100%) rename {lib => src}/RichText/package.json (100%) rename {lib => src}/Router.js (100%) rename {lib => src}/Scrim/Scrim.css (100%) rename {lib => src}/Scrim/Scrim.js (100%) rename {lib => src}/Scrim/package.json (100%) rename {lib => src}/ScrollMath.js (100%) rename {lib => src}/ScrollStrategy.js (100%) rename {lib => src}/ScrollThumb/ScrollThumb.css (100%) rename {lib => src}/ScrollThumb/ScrollThumb.js (100%) rename {lib => src}/ScrollThumb/package.json (100%) rename {lib => src}/Scrollable/Scrollable.js (100%) rename {lib => src}/Scrollable/Scrollable.less (100%) rename {lib => src}/Scrollable/package.json (100%) rename {lib => src}/Scroller/Scroller.css (100%) rename {lib => src}/Scroller/Scroller.js (100%) rename {lib => src}/Scroller/package.json (100%) rename {lib => src}/Select.js (100%) rename {lib => src}/Selection.js (100%) rename {lib => src}/Signals.js (100%) rename {lib => src}/Source.js (100%) rename {lib => src}/SpriteAnimation/SpriteAnimation.css (100%) rename {lib => src}/SpriteAnimation/SpriteAnimation.js (100%) rename {lib => src}/SpriteAnimation/package.json (100%) rename {lib => src}/StateSupport.js (100%) rename {lib => src}/States.js (100%) rename {lib => src}/Store.js (100%) rename {lib => src}/StringBinding.js (100%) rename {lib => src}/Style.js (100%) rename {lib => src}/StylesheetSupport.js (100%) rename {lib => src}/SystemMonitor.js (100%) rename {lib => src}/Table/Table.js (100%) rename {lib => src}/Table/TableAccessibilitySupport.js (100%) rename {lib => src}/Table/package.json (100%) rename {lib => src}/TableCell/TableCell.js (100%) rename {lib => src}/TableCell/TableCellAccessibilitySupport.js (100%) rename {lib => src}/TableCell/package.json (100%) rename {lib => src}/TableRow/TableRow.js (100%) rename {lib => src}/TableRow/TableRowAccessibilitySupport.js (100%) rename {lib => src}/TableRow/package.json (100%) rename {lib => src}/TaskManagerSupport.js (100%) rename {lib => src}/TextArea/TextArea.js (100%) rename {lib => src}/TextArea/TextAreaAccessibilitySupport.js (100%) rename {lib => src}/TextArea/package.json (100%) rename {lib => src}/ToolDecorator/ToolDecorator.css (100%) rename {lib => src}/ToolDecorator/ToolDecorator.js (100%) rename {lib => src}/ToolDecorator/package.json (100%) rename {lib => src}/TouchScrollStrategy.js (100%) rename {lib => src}/TransitionScrollStrategy.js (100%) rename {lib => src}/TranslateScrollStrategy.js (100%) rename {lib => src}/UiComponent.js (100%) rename {lib => src}/VerticalDelegate.js (100%) rename {lib => src}/VerticalGridDelegate.js (100%) rename {lib => src}/Video.js (100%) rename {lib => src}/ViewController.js (100%) rename {lib => src}/ViewPreloadSupport.js (100%) rename {lib => src}/VirtualDataRepeater.js (100%) rename {lib => src}/WebService.js (100%) rename {lib => src}/XhrSource.js (100%) rename {lib => src}/animation.js (100%) rename {lib => src}/cookie.js (100%) rename {lib => src}/dev.js (100%) rename {lib => src}/dispatcher.js (100%) rename {lib => src}/dom.js (100%) rename {lib => src}/gesture/drag.js (100%) rename {lib => src}/gesture/gesture.js (100%) rename {lib => src}/gesture/package.json (100%) rename {lib => src}/gesture/touchGestures.js (100%) rename {lib => src}/gesture/util.js (100%) rename {lib => src}/hooks.js (100%) rename {lib => src}/i18n.js (100%) rename {lib => src}/job.js (100%) rename {lib => src}/jobs.js (100%) rename {lib => src}/json.js (100%) rename {lib => src}/kind.js (100%) rename {lib => src}/logger.js (100%) rename {lib => src}/macroize.js (100%) rename {lib => src}/master.js (100%) rename {lib => src}/options.js (100%) rename {lib => src}/pageVisibility.js (100%) rename {lib => src}/path.js (100%) rename {lib => src}/pathResolver.js (100%) rename {lib => src}/platform.js (100%) rename {lib => src}/ready.js (100%) rename {lib => src}/resolution.js (100%) rename {lib => src}/roots.js (100%) rename {lib => src}/touch.js (100%) rename {lib => src}/utils.js (100%) rename {lib => src}/xhr.js (100%) diff --git a/.enyoconfig b/.enyoconfig new file mode 100644 index 000000000..5dad386fb --- /dev/null +++ b/.enyoconfig @@ -0,0 +1,43 @@ +{ + "name": "enyo", + "libDir": "lib", + "paths": [], + "libraries": [], + "sources": {}, + "targets": {}, + "links": [], + "linkAllLibs": false, + "logLevel": "warn", + "production": false, + "devMode": true, + "cache": true, + "resetCache": false, + "trustCache": false, + "cacheFile": ".enyocache", + "clean": false, + "sourceMaps": true, + "externals": true, + "strict": false, + "skip": [], + "library": true, + "wip": false, + "outdir": "dist", + "lessPlugins": [], + "assetRoots": [], + "lessOnlyLess": false, + "minifyCss": false, + "inlineCss": true, + "outCssFile": "output.css", + "outJsFile": "output.js", + "inlineJs": true, + "templateIndex": "", + "watch": false, + "watchPaths": [], + "polling": false, + "pollingInterval": 100, + "headScripts": [], + "tailScripts": [], + "promisePolyfill": false, + "styleOnly": false, + "lessVars": [] +} diff --git a/.gitignore b/.gitignore index 22b7d97c4..84a5e94b7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,6 @@ docs/out out lcov-report lcov.info -npm-* \ No newline at end of file +npm-* +dist +.enyocache \ No newline at end of file diff --git a/index.js b/index.js index 0bc33de10..09d5e3a79 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ 'use strict'; -exports = module.exports = require('./lib/options'); +exports = module.exports = require('./src/options'); exports.version = '2.6.0-pre.14.1'; diff --git a/package.json b/package.json index 95c9ddb93..17aa8d921 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,18 @@ { "name": "enyo", + "assets": [], + "devAssets": [], + "styles": [ + "css/enyo.css", + "css/mixins.less" + ], + "main": "index.js", + "moduleDir": "src", "filename": "enyo.js", "version": "2.6.0-pre", "description": "Enyo is an open source object-oriented JavaScript framework emphasizing encapsulation and modularity. Enyo contains everything you need to create a fast, scalable mobile or web application.", "homepage": "http://enyojs.com/", "bugs": "http://jira.enyojs.com/", - "main": "index.js", - "styles": [ - "css/enyo.css", - "css/mixins.less" - ], "keywords": [ "framework", "toolkit", diff --git a/lib/AccessibilitySupport/AccessibilitySupport.css b/src/AccessibilitySupport/AccessibilitySupport.css similarity index 100% rename from lib/AccessibilitySupport/AccessibilitySupport.css rename to src/AccessibilitySupport/AccessibilitySupport.css diff --git a/lib/AccessibilitySupport/AccessibilitySupport.js b/src/AccessibilitySupport/AccessibilitySupport.js similarity index 100% rename from lib/AccessibilitySupport/AccessibilitySupport.js rename to src/AccessibilitySupport/AccessibilitySupport.js diff --git a/lib/AccessibilitySupport/package.json b/src/AccessibilitySupport/package.json similarity index 100% rename from lib/AccessibilitySupport/package.json rename to src/AccessibilitySupport/package.json diff --git a/lib/Ajax.js b/src/Ajax.js similarity index 100% rename from lib/Ajax.js rename to src/Ajax.js diff --git a/lib/AjaxProperties.js b/src/AjaxProperties.js similarity index 100% rename from lib/AjaxProperties.js rename to src/AjaxProperties.js diff --git a/lib/AjaxSource.js b/src/AjaxSource.js similarity index 100% rename from lib/AjaxSource.js rename to src/AjaxSource.js diff --git a/lib/Anchor.js b/src/Anchor.js similarity index 100% rename from lib/Anchor.js rename to src/Anchor.js diff --git a/lib/Animator.js b/src/Animator.js similarity index 100% rename from lib/Animator.js rename to src/Animator.js diff --git a/lib/Application.js b/src/Application.js similarity index 100% rename from lib/Application.js rename to src/Application.js diff --git a/lib/ApplicationSupport.js b/src/ApplicationSupport.js similarity index 100% rename from lib/ApplicationSupport.js rename to src/ApplicationSupport.js diff --git a/lib/Async.js b/src/Async.js similarity index 100% rename from lib/Async.js rename to src/Async.js diff --git a/lib/Audio.js b/src/Audio.js similarity index 100% rename from lib/Audio.js rename to src/Audio.js diff --git a/lib/BackgroundTaskManager.js b/src/BackgroundTaskManager.js similarity index 100% rename from lib/BackgroundTaskManager.js rename to src/BackgroundTaskManager.js diff --git a/lib/BaseLayout.js b/src/BaseLayout.js similarity index 100% rename from lib/BaseLayout.js rename to src/BaseLayout.js diff --git a/lib/Binding.js b/src/Binding.js similarity index 100% rename from lib/Binding.js rename to src/Binding.js diff --git a/lib/BindingSupport.js b/src/BindingSupport.js similarity index 100% rename from lib/BindingSupport.js rename to src/BindingSupport.js diff --git a/lib/Blob.js b/src/Blob.js similarity index 100% rename from lib/Blob.js rename to src/Blob.js diff --git a/lib/BooleanBinding.js b/src/BooleanBinding.js similarity index 100% rename from lib/BooleanBinding.js rename to src/BooleanBinding.js diff --git a/lib/BooleanOnlyBinding.js b/src/BooleanOnlyBinding.js similarity index 100% rename from lib/BooleanOnlyBinding.js rename to src/BooleanOnlyBinding.js diff --git a/lib/BucketFilter.js b/src/BucketFilter.js similarity index 100% rename from lib/BucketFilter.js rename to src/BucketFilter.js diff --git a/lib/Button/Button.css b/src/Button/Button.css similarity index 100% rename from lib/Button/Button.css rename to src/Button/Button.css diff --git a/lib/Button/Button.js b/src/Button/Button.js similarity index 100% rename from lib/Button/Button.js rename to src/Button/Button.js diff --git a/lib/Button/ButtonAccessibilitySupport.js b/src/Button/ButtonAccessibilitySupport.js similarity index 100% rename from lib/Button/ButtonAccessibilitySupport.js rename to src/Button/ButtonAccessibilitySupport.js diff --git a/lib/Button/package.json b/src/Button/package.json similarity index 100% rename from lib/Button/package.json rename to src/Button/package.json diff --git a/lib/Checkbox/Checkbox.js b/src/Checkbox/Checkbox.js similarity index 100% rename from lib/Checkbox/Checkbox.js rename to src/Checkbox/Checkbox.js diff --git a/lib/Checkbox/CheckboxAccessibilitySupport.js b/src/Checkbox/CheckboxAccessibilitySupport.js similarity index 100% rename from lib/Checkbox/CheckboxAccessibilitySupport.js rename to src/Checkbox/CheckboxAccessibilitySupport.js diff --git a/lib/Checkbox/package.json b/src/Checkbox/package.json similarity index 100% rename from lib/Checkbox/package.json rename to src/Checkbox/package.json diff --git a/lib/Collection.js b/src/Collection.js similarity index 100% rename from lib/Collection.js rename to src/Collection.js diff --git a/lib/Component.js b/src/Component.js similarity index 100% rename from lib/Component.js rename to src/Component.js diff --git a/lib/ComponentBindingSupport.js b/src/ComponentBindingSupport.js similarity index 100% rename from lib/ComponentBindingSupport.js rename to src/ComponentBindingSupport.js diff --git a/lib/ComputedSupport.js b/src/ComputedSupport.js similarity index 100% rename from lib/ComputedSupport.js rename to src/ComputedSupport.js diff --git a/lib/ContentAreaSupport.js b/src/ContentAreaSupport.js similarity index 100% rename from lib/ContentAreaSupport.js rename to src/ContentAreaSupport.js diff --git a/lib/Control/Control.js b/src/Control/Control.js similarity index 100% rename from lib/Control/Control.js rename to src/Control/Control.js diff --git a/lib/Control/floatingLayer.js b/src/Control/floatingLayer.js similarity index 100% rename from lib/Control/floatingLayer.js rename to src/Control/floatingLayer.js diff --git a/lib/Control/fullscreen.js b/src/Control/fullscreen.js similarity index 100% rename from lib/Control/fullscreen.js rename to src/Control/fullscreen.js diff --git a/lib/Control/fullscreen.less b/src/Control/fullscreen.less similarity index 100% rename from lib/Control/fullscreen.less rename to src/Control/fullscreen.less diff --git a/lib/Control/package.json b/src/Control/package.json similarity index 100% rename from lib/Control/package.json rename to src/Control/package.json diff --git a/lib/Controller.js b/src/Controller.js similarity index 100% rename from lib/Controller.js rename to src/Controller.js diff --git a/lib/CoreObject.js b/src/CoreObject.js similarity index 100% rename from lib/CoreObject.js rename to src/CoreObject.js diff --git a/lib/DataGridList/DataGridList.js b/src/DataGridList/DataGridList.js similarity index 100% rename from lib/DataGridList/DataGridList.js rename to src/DataGridList/DataGridList.js diff --git a/lib/DataGridList/DataGridList.less b/src/DataGridList/DataGridList.less similarity index 100% rename from lib/DataGridList/DataGridList.less rename to src/DataGridList/DataGridList.less diff --git a/lib/DataGridList/package.json b/src/DataGridList/package.json similarity index 100% rename from lib/DataGridList/package.json rename to src/DataGridList/package.json diff --git a/lib/DataList/DataList.js b/src/DataList/DataList.js similarity index 100% rename from lib/DataList/DataList.js rename to src/DataList/DataList.js diff --git a/lib/DataList/DataList.less b/src/DataList/DataList.less similarity index 100% rename from lib/DataList/DataList.less rename to src/DataList/DataList.less diff --git a/lib/DataList/package.json b/src/DataList/package.json similarity index 100% rename from lib/DataList/package.json rename to src/DataList/package.json diff --git a/lib/DataRepeater.js b/src/DataRepeater.js similarity index 100% rename from lib/DataRepeater.js rename to src/DataRepeater.js diff --git a/lib/DataTable.js b/src/DataTable.js similarity index 100% rename from lib/DataTable.js rename to src/DataTable.js diff --git a/lib/DragAvatar.js b/src/DragAvatar.js similarity index 100% rename from lib/DragAvatar.js rename to src/DragAvatar.js diff --git a/lib/Drawer.js b/src/Drawer.js similarity index 100% rename from lib/Drawer.js rename to src/Drawer.js diff --git a/lib/EmptyBinding.js b/src/EmptyBinding.js similarity index 100% rename from lib/EmptyBinding.js rename to src/EmptyBinding.js diff --git a/lib/EventEmitter.js b/src/EventEmitter.js similarity index 100% rename from lib/EventEmitter.js rename to src/EventEmitter.js diff --git a/lib/Filter.js b/src/Filter.js similarity index 100% rename from lib/Filter.js rename to src/Filter.js diff --git a/lib/FluxDispatcher.js b/src/FluxDispatcher.js similarity index 100% rename from lib/FluxDispatcher.js rename to src/FluxDispatcher.js diff --git a/lib/FluxStore.js b/src/FluxStore.js similarity index 100% rename from lib/FluxStore.js rename to src/FluxStore.js diff --git a/lib/FormData.js b/src/FormData.js similarity index 100% rename from lib/FormData.js rename to src/FormData.js diff --git a/lib/Group/Group.js b/src/Group/Group.js similarity index 100% rename from lib/Group/Group.js rename to src/Group/Group.js diff --git a/lib/Group/GroupAccessibilitySupport.js b/src/Group/GroupAccessibilitySupport.js similarity index 100% rename from lib/Group/GroupAccessibilitySupport.js rename to src/Group/GroupAccessibilitySupport.js diff --git a/lib/Group/package.json b/src/Group/package.json similarity index 100% rename from lib/Group/package.json rename to src/Group/package.json diff --git a/lib/GroupItem.js b/src/GroupItem.js similarity index 100% rename from lib/GroupItem.js rename to src/GroupItem.js diff --git a/lib/HTMLStringDelegate.js b/src/HTMLStringDelegate.js similarity index 100% rename from lib/HTMLStringDelegate.js rename to src/HTMLStringDelegate.js diff --git a/lib/History.js b/src/History.js similarity index 100% rename from lib/History.js rename to src/History.js diff --git a/lib/HorizontalDelegate.js b/src/HorizontalDelegate.js similarity index 100% rename from lib/HorizontalDelegate.js rename to src/HorizontalDelegate.js diff --git a/lib/Image/Image.css b/src/Image/Image.css similarity index 100% rename from lib/Image/Image.css rename to src/Image/Image.css diff --git a/lib/Image/Image.js b/src/Image/Image.js similarity index 100% rename from lib/Image/Image.js rename to src/Image/Image.js diff --git a/lib/Image/package.json b/src/Image/package.json similarity index 100% rename from lib/Image/package.json rename to src/Image/package.json diff --git a/lib/Input/Input.js b/src/Input/Input.js similarity index 100% rename from lib/Input/Input.js rename to src/Input/Input.js diff --git a/lib/Input/InputAccessibilitySupport.js b/src/Input/InputAccessibilitySupport.js similarity index 100% rename from lib/Input/InputAccessibilitySupport.js rename to src/Input/InputAccessibilitySupport.js diff --git a/lib/Input/package.json b/src/Input/package.json similarity index 100% rename from lib/Input/package.json rename to src/Input/package.json diff --git a/lib/InputBinding.js b/src/InputBinding.js similarity index 100% rename from lib/InputBinding.js rename to src/InputBinding.js diff --git a/lib/InvertBooleanBinding.js b/src/InvertBooleanBinding.js similarity index 100% rename from lib/InvertBooleanBinding.js rename to src/InvertBooleanBinding.js diff --git a/lib/Jsonp.js b/src/Jsonp.js similarity index 100% rename from lib/Jsonp.js rename to src/Jsonp.js diff --git a/lib/JsonpSource.js b/src/JsonpSource.js similarity index 100% rename from lib/JsonpSource.js rename to src/JsonpSource.js diff --git a/lib/Layout.js b/src/Layout.js similarity index 100% rename from lib/Layout.js rename to src/Layout.js diff --git a/lib/LightPanels/LightPanel.js b/src/LightPanels/LightPanel.js similarity index 100% rename from lib/LightPanels/LightPanel.js rename to src/LightPanels/LightPanel.js diff --git a/lib/LightPanels/LightPanels.css b/src/LightPanels/LightPanels.css similarity index 100% rename from lib/LightPanels/LightPanels.css rename to src/LightPanels/LightPanels.css diff --git a/lib/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js similarity index 100% rename from lib/LightPanels/LightPanels.js rename to src/LightPanels/LightPanels.js diff --git a/lib/LightPanels/package.json b/src/LightPanels/package.json similarity index 100% rename from lib/LightPanels/package.json rename to src/LightPanels/package.json diff --git a/lib/LinkedList.js b/src/LinkedList.js similarity index 100% rename from lib/LinkedList.js rename to src/LinkedList.js diff --git a/lib/LinkedListNode.js b/src/LinkedListNode.js similarity index 100% rename from lib/LinkedListNode.js rename to src/LinkedListNode.js diff --git a/lib/LocalStorageSource.js b/src/LocalStorageSource.js similarity index 100% rename from lib/LocalStorageSource.js rename to src/LocalStorageSource.js diff --git a/lib/Loop.js b/src/Loop.js similarity index 100% rename from lib/Loop.js rename to src/Loop.js diff --git a/lib/Media.js b/src/Media.js similarity index 100% rename from lib/Media.js rename to src/Media.js diff --git a/lib/MediaSource.js b/src/MediaSource.js similarity index 100% rename from lib/MediaSource.js rename to src/MediaSource.js diff --git a/lib/MixinSupport.js b/src/MixinSupport.js similarity index 100% rename from lib/MixinSupport.js rename to src/MixinSupport.js diff --git a/lib/Model.js b/src/Model.js similarity index 100% rename from lib/Model.js rename to src/Model.js diff --git a/lib/ModelController.js b/src/ModelController.js similarity index 100% rename from lib/ModelController.js rename to src/ModelController.js diff --git a/lib/ModelList.js b/src/ModelList.js similarity index 100% rename from lib/ModelList.js rename to src/ModelList.js diff --git a/lib/MultipleDispatchComponent.js b/src/MultipleDispatchComponent.js similarity index 100% rename from lib/MultipleDispatchComponent.js rename to src/MultipleDispatchComponent.js diff --git a/lib/MultipleDispatchSupport.js b/src/MultipleDispatchSupport.js similarity index 100% rename from lib/MultipleDispatchSupport.js rename to src/MultipleDispatchSupport.js diff --git a/lib/NewAnimator.js b/src/NewAnimator.js similarity index 100% rename from lib/NewAnimator.js rename to src/NewAnimator.js diff --git a/lib/NewDataList.js b/src/NewDataList.js similarity index 100% rename from lib/NewDataList.js rename to src/NewDataList.js diff --git a/lib/NewDrawer/NewDrawer.css b/src/NewDrawer/NewDrawer.css similarity index 100% rename from lib/NewDrawer/NewDrawer.css rename to src/NewDrawer/NewDrawer.css diff --git a/lib/NewDrawer/NewDrawer.js b/src/NewDrawer/NewDrawer.js similarity index 100% rename from lib/NewDrawer/NewDrawer.js rename to src/NewDrawer/NewDrawer.js diff --git a/lib/NewDrawer/package.json b/src/NewDrawer/package.json similarity index 100% rename from lib/NewDrawer/package.json rename to src/NewDrawer/package.json diff --git a/lib/NewThumb/NewThumb.css b/src/NewThumb/NewThumb.css similarity index 100% rename from lib/NewThumb/NewThumb.css rename to src/NewThumb/NewThumb.css diff --git a/lib/NewThumb/NewThumb.js b/src/NewThumb/NewThumb.js similarity index 100% rename from lib/NewThumb/NewThumb.js rename to src/NewThumb/NewThumb.js diff --git a/lib/NewThumb/package.json b/src/NewThumb/package.json similarity index 100% rename from lib/NewThumb/package.json rename to src/NewThumb/package.json diff --git a/lib/ObserverChain.js b/src/ObserverChain.js similarity index 100% rename from lib/ObserverChain.js rename to src/ObserverChain.js diff --git a/lib/ObserverChainNode.js b/src/ObserverChainNode.js similarity index 100% rename from lib/ObserverChainNode.js rename to src/ObserverChainNode.js diff --git a/lib/ObserverSupport.js b/src/ObserverSupport.js similarity index 100% rename from lib/ObserverSupport.js rename to src/ObserverSupport.js diff --git a/lib/Option.js b/src/Option.js similarity index 100% rename from lib/Option.js rename to src/Option.js diff --git a/lib/OptionGroup.js b/src/OptionGroup.js similarity index 100% rename from lib/OptionGroup.js rename to src/OptionGroup.js diff --git a/lib/PathResolverFactory.js b/src/PathResolverFactory.js similarity index 100% rename from lib/PathResolverFactory.js rename to src/PathResolverFactory.js diff --git a/lib/Popup/Popup.css b/src/Popup/Popup.css similarity index 100% rename from lib/Popup/Popup.css rename to src/Popup/Popup.css diff --git a/lib/Popup/Popup.js b/src/Popup/Popup.js similarity index 100% rename from lib/Popup/Popup.js rename to src/Popup/Popup.js diff --git a/lib/Popup/PopupAccessibilitySupport.js b/src/Popup/PopupAccessibilitySupport.js similarity index 100% rename from lib/Popup/PopupAccessibilitySupport.js rename to src/Popup/PopupAccessibilitySupport.js diff --git a/lib/Popup/package.json b/src/Popup/package.json similarity index 100% rename from lib/Popup/package.json rename to src/Popup/package.json diff --git a/lib/PriorityQueue.js b/src/PriorityQueue.js similarity index 100% rename from lib/PriorityQueue.js rename to src/PriorityQueue.js diff --git a/lib/ProgressiveFilter.js b/src/ProgressiveFilter.js similarity index 100% rename from lib/ProgressiveFilter.js rename to src/ProgressiveFilter.js diff --git a/lib/ProxyObject.js b/src/ProxyObject.js similarity index 100% rename from lib/ProxyObject.js rename to src/ProxyObject.js diff --git a/lib/Relation.js b/src/Relation.js similarity index 100% rename from lib/Relation.js rename to src/Relation.js diff --git a/lib/RelationalModel/RelationalCollection.js b/src/RelationalModel/RelationalCollection.js similarity index 100% rename from lib/RelationalModel/RelationalCollection.js rename to src/RelationalModel/RelationalCollection.js diff --git a/lib/RelationalModel/RelationalModel.js b/src/RelationalModel/RelationalModel.js similarity index 100% rename from lib/RelationalModel/RelationalModel.js rename to src/RelationalModel/RelationalModel.js diff --git a/lib/RelationalModel/index.js b/src/RelationalModel/index.js similarity index 100% rename from lib/RelationalModel/index.js rename to src/RelationalModel/index.js diff --git a/lib/RelationalModel/manyToMany.js b/src/RelationalModel/manyToMany.js similarity index 100% rename from lib/RelationalModel/manyToMany.js rename to src/RelationalModel/manyToMany.js diff --git a/lib/RelationalModel/package.json b/src/RelationalModel/package.json similarity index 100% rename from lib/RelationalModel/package.json rename to src/RelationalModel/package.json diff --git a/lib/RelationalModel/toMany.js b/src/RelationalModel/toMany.js similarity index 100% rename from lib/RelationalModel/toMany.js rename to src/RelationalModel/toMany.js diff --git a/lib/RelationalModel/toOne.js b/src/RelationalModel/toOne.js similarity index 100% rename from lib/RelationalModel/toOne.js rename to src/RelationalModel/toOne.js diff --git a/lib/Repeater.js b/src/Repeater.js similarity index 100% rename from lib/Repeater.js rename to src/Repeater.js diff --git a/lib/RepeaterChildSupport.js b/src/RepeaterChildSupport.js similarity index 100% rename from lib/RepeaterChildSupport.js rename to src/RepeaterChildSupport.js diff --git a/lib/RichText/RichText.css b/src/RichText/RichText.css similarity index 100% rename from lib/RichText/RichText.css rename to src/RichText/RichText.css diff --git a/lib/RichText/RichText.js b/src/RichText/RichText.js similarity index 100% rename from lib/RichText/RichText.js rename to src/RichText/RichText.js diff --git a/lib/RichText/RichTextAccessibilitySupport.js b/src/RichText/RichTextAccessibilitySupport.js similarity index 100% rename from lib/RichText/RichTextAccessibilitySupport.js rename to src/RichText/RichTextAccessibilitySupport.js diff --git a/lib/RichText/package.json b/src/RichText/package.json similarity index 100% rename from lib/RichText/package.json rename to src/RichText/package.json diff --git a/lib/Router.js b/src/Router.js similarity index 100% rename from lib/Router.js rename to src/Router.js diff --git a/lib/Scrim/Scrim.css b/src/Scrim/Scrim.css similarity index 100% rename from lib/Scrim/Scrim.css rename to src/Scrim/Scrim.css diff --git a/lib/Scrim/Scrim.js b/src/Scrim/Scrim.js similarity index 100% rename from lib/Scrim/Scrim.js rename to src/Scrim/Scrim.js diff --git a/lib/Scrim/package.json b/src/Scrim/package.json similarity index 100% rename from lib/Scrim/package.json rename to src/Scrim/package.json diff --git a/lib/ScrollMath.js b/src/ScrollMath.js similarity index 100% rename from lib/ScrollMath.js rename to src/ScrollMath.js diff --git a/lib/ScrollStrategy.js b/src/ScrollStrategy.js similarity index 100% rename from lib/ScrollStrategy.js rename to src/ScrollStrategy.js diff --git a/lib/ScrollThumb/ScrollThumb.css b/src/ScrollThumb/ScrollThumb.css similarity index 100% rename from lib/ScrollThumb/ScrollThumb.css rename to src/ScrollThumb/ScrollThumb.css diff --git a/lib/ScrollThumb/ScrollThumb.js b/src/ScrollThumb/ScrollThumb.js similarity index 100% rename from lib/ScrollThumb/ScrollThumb.js rename to src/ScrollThumb/ScrollThumb.js diff --git a/lib/ScrollThumb/package.json b/src/ScrollThumb/package.json similarity index 100% rename from lib/ScrollThumb/package.json rename to src/ScrollThumb/package.json diff --git a/lib/Scrollable/Scrollable.js b/src/Scrollable/Scrollable.js similarity index 100% rename from lib/Scrollable/Scrollable.js rename to src/Scrollable/Scrollable.js diff --git a/lib/Scrollable/Scrollable.less b/src/Scrollable/Scrollable.less similarity index 100% rename from lib/Scrollable/Scrollable.less rename to src/Scrollable/Scrollable.less diff --git a/lib/Scrollable/package.json b/src/Scrollable/package.json similarity index 100% rename from lib/Scrollable/package.json rename to src/Scrollable/package.json diff --git a/lib/Scroller/Scroller.css b/src/Scroller/Scroller.css similarity index 100% rename from lib/Scroller/Scroller.css rename to src/Scroller/Scroller.css diff --git a/lib/Scroller/Scroller.js b/src/Scroller/Scroller.js similarity index 100% rename from lib/Scroller/Scroller.js rename to src/Scroller/Scroller.js diff --git a/lib/Scroller/package.json b/src/Scroller/package.json similarity index 100% rename from lib/Scroller/package.json rename to src/Scroller/package.json diff --git a/lib/Select.js b/src/Select.js similarity index 100% rename from lib/Select.js rename to src/Select.js diff --git a/lib/Selection.js b/src/Selection.js similarity index 100% rename from lib/Selection.js rename to src/Selection.js diff --git a/lib/Signals.js b/src/Signals.js similarity index 100% rename from lib/Signals.js rename to src/Signals.js diff --git a/lib/Source.js b/src/Source.js similarity index 100% rename from lib/Source.js rename to src/Source.js diff --git a/lib/SpriteAnimation/SpriteAnimation.css b/src/SpriteAnimation/SpriteAnimation.css similarity index 100% rename from lib/SpriteAnimation/SpriteAnimation.css rename to src/SpriteAnimation/SpriteAnimation.css diff --git a/lib/SpriteAnimation/SpriteAnimation.js b/src/SpriteAnimation/SpriteAnimation.js similarity index 100% rename from lib/SpriteAnimation/SpriteAnimation.js rename to src/SpriteAnimation/SpriteAnimation.js diff --git a/lib/SpriteAnimation/package.json b/src/SpriteAnimation/package.json similarity index 100% rename from lib/SpriteAnimation/package.json rename to src/SpriteAnimation/package.json diff --git a/lib/StateSupport.js b/src/StateSupport.js similarity index 100% rename from lib/StateSupport.js rename to src/StateSupport.js diff --git a/lib/States.js b/src/States.js similarity index 100% rename from lib/States.js rename to src/States.js diff --git a/lib/Store.js b/src/Store.js similarity index 100% rename from lib/Store.js rename to src/Store.js diff --git a/lib/StringBinding.js b/src/StringBinding.js similarity index 100% rename from lib/StringBinding.js rename to src/StringBinding.js diff --git a/lib/Style.js b/src/Style.js similarity index 100% rename from lib/Style.js rename to src/Style.js diff --git a/lib/StylesheetSupport.js b/src/StylesheetSupport.js similarity index 100% rename from lib/StylesheetSupport.js rename to src/StylesheetSupport.js diff --git a/lib/SystemMonitor.js b/src/SystemMonitor.js similarity index 100% rename from lib/SystemMonitor.js rename to src/SystemMonitor.js diff --git a/lib/Table/Table.js b/src/Table/Table.js similarity index 100% rename from lib/Table/Table.js rename to src/Table/Table.js diff --git a/lib/Table/TableAccessibilitySupport.js b/src/Table/TableAccessibilitySupport.js similarity index 100% rename from lib/Table/TableAccessibilitySupport.js rename to src/Table/TableAccessibilitySupport.js diff --git a/lib/Table/package.json b/src/Table/package.json similarity index 100% rename from lib/Table/package.json rename to src/Table/package.json diff --git a/lib/TableCell/TableCell.js b/src/TableCell/TableCell.js similarity index 100% rename from lib/TableCell/TableCell.js rename to src/TableCell/TableCell.js diff --git a/lib/TableCell/TableCellAccessibilitySupport.js b/src/TableCell/TableCellAccessibilitySupport.js similarity index 100% rename from lib/TableCell/TableCellAccessibilitySupport.js rename to src/TableCell/TableCellAccessibilitySupport.js diff --git a/lib/TableCell/package.json b/src/TableCell/package.json similarity index 100% rename from lib/TableCell/package.json rename to src/TableCell/package.json diff --git a/lib/TableRow/TableRow.js b/src/TableRow/TableRow.js similarity index 100% rename from lib/TableRow/TableRow.js rename to src/TableRow/TableRow.js diff --git a/lib/TableRow/TableRowAccessibilitySupport.js b/src/TableRow/TableRowAccessibilitySupport.js similarity index 100% rename from lib/TableRow/TableRowAccessibilitySupport.js rename to src/TableRow/TableRowAccessibilitySupport.js diff --git a/lib/TableRow/package.json b/src/TableRow/package.json similarity index 100% rename from lib/TableRow/package.json rename to src/TableRow/package.json diff --git a/lib/TaskManagerSupport.js b/src/TaskManagerSupport.js similarity index 100% rename from lib/TaskManagerSupport.js rename to src/TaskManagerSupport.js diff --git a/lib/TextArea/TextArea.js b/src/TextArea/TextArea.js similarity index 100% rename from lib/TextArea/TextArea.js rename to src/TextArea/TextArea.js diff --git a/lib/TextArea/TextAreaAccessibilitySupport.js b/src/TextArea/TextAreaAccessibilitySupport.js similarity index 100% rename from lib/TextArea/TextAreaAccessibilitySupport.js rename to src/TextArea/TextAreaAccessibilitySupport.js diff --git a/lib/TextArea/package.json b/src/TextArea/package.json similarity index 100% rename from lib/TextArea/package.json rename to src/TextArea/package.json diff --git a/lib/ToolDecorator/ToolDecorator.css b/src/ToolDecorator/ToolDecorator.css similarity index 100% rename from lib/ToolDecorator/ToolDecorator.css rename to src/ToolDecorator/ToolDecorator.css diff --git a/lib/ToolDecorator/ToolDecorator.js b/src/ToolDecorator/ToolDecorator.js similarity index 100% rename from lib/ToolDecorator/ToolDecorator.js rename to src/ToolDecorator/ToolDecorator.js diff --git a/lib/ToolDecorator/package.json b/src/ToolDecorator/package.json similarity index 100% rename from lib/ToolDecorator/package.json rename to src/ToolDecorator/package.json diff --git a/lib/TouchScrollStrategy.js b/src/TouchScrollStrategy.js similarity index 100% rename from lib/TouchScrollStrategy.js rename to src/TouchScrollStrategy.js diff --git a/lib/TransitionScrollStrategy.js b/src/TransitionScrollStrategy.js similarity index 100% rename from lib/TransitionScrollStrategy.js rename to src/TransitionScrollStrategy.js diff --git a/lib/TranslateScrollStrategy.js b/src/TranslateScrollStrategy.js similarity index 100% rename from lib/TranslateScrollStrategy.js rename to src/TranslateScrollStrategy.js diff --git a/lib/UiComponent.js b/src/UiComponent.js similarity index 100% rename from lib/UiComponent.js rename to src/UiComponent.js diff --git a/lib/VerticalDelegate.js b/src/VerticalDelegate.js similarity index 100% rename from lib/VerticalDelegate.js rename to src/VerticalDelegate.js diff --git a/lib/VerticalGridDelegate.js b/src/VerticalGridDelegate.js similarity index 100% rename from lib/VerticalGridDelegate.js rename to src/VerticalGridDelegate.js diff --git a/lib/Video.js b/src/Video.js similarity index 100% rename from lib/Video.js rename to src/Video.js diff --git a/lib/ViewController.js b/src/ViewController.js similarity index 100% rename from lib/ViewController.js rename to src/ViewController.js diff --git a/lib/ViewPreloadSupport.js b/src/ViewPreloadSupport.js similarity index 100% rename from lib/ViewPreloadSupport.js rename to src/ViewPreloadSupport.js diff --git a/lib/VirtualDataRepeater.js b/src/VirtualDataRepeater.js similarity index 100% rename from lib/VirtualDataRepeater.js rename to src/VirtualDataRepeater.js diff --git a/lib/WebService.js b/src/WebService.js similarity index 100% rename from lib/WebService.js rename to src/WebService.js diff --git a/lib/XhrSource.js b/src/XhrSource.js similarity index 100% rename from lib/XhrSource.js rename to src/XhrSource.js diff --git a/lib/animation.js b/src/animation.js similarity index 100% rename from lib/animation.js rename to src/animation.js diff --git a/lib/cookie.js b/src/cookie.js similarity index 100% rename from lib/cookie.js rename to src/cookie.js diff --git a/lib/dev.js b/src/dev.js similarity index 100% rename from lib/dev.js rename to src/dev.js diff --git a/lib/dispatcher.js b/src/dispatcher.js similarity index 100% rename from lib/dispatcher.js rename to src/dispatcher.js diff --git a/lib/dom.js b/src/dom.js similarity index 100% rename from lib/dom.js rename to src/dom.js diff --git a/lib/gesture/drag.js b/src/gesture/drag.js similarity index 100% rename from lib/gesture/drag.js rename to src/gesture/drag.js diff --git a/lib/gesture/gesture.js b/src/gesture/gesture.js similarity index 100% rename from lib/gesture/gesture.js rename to src/gesture/gesture.js diff --git a/lib/gesture/package.json b/src/gesture/package.json similarity index 100% rename from lib/gesture/package.json rename to src/gesture/package.json diff --git a/lib/gesture/touchGestures.js b/src/gesture/touchGestures.js similarity index 100% rename from lib/gesture/touchGestures.js rename to src/gesture/touchGestures.js diff --git a/lib/gesture/util.js b/src/gesture/util.js similarity index 100% rename from lib/gesture/util.js rename to src/gesture/util.js diff --git a/lib/hooks.js b/src/hooks.js similarity index 100% rename from lib/hooks.js rename to src/hooks.js diff --git a/lib/i18n.js b/src/i18n.js similarity index 100% rename from lib/i18n.js rename to src/i18n.js diff --git a/lib/job.js b/src/job.js similarity index 100% rename from lib/job.js rename to src/job.js diff --git a/lib/jobs.js b/src/jobs.js similarity index 100% rename from lib/jobs.js rename to src/jobs.js diff --git a/lib/json.js b/src/json.js similarity index 100% rename from lib/json.js rename to src/json.js diff --git a/lib/kind.js b/src/kind.js similarity index 100% rename from lib/kind.js rename to src/kind.js diff --git a/lib/logger.js b/src/logger.js similarity index 100% rename from lib/logger.js rename to src/logger.js diff --git a/lib/macroize.js b/src/macroize.js similarity index 100% rename from lib/macroize.js rename to src/macroize.js diff --git a/lib/master.js b/src/master.js similarity index 100% rename from lib/master.js rename to src/master.js diff --git a/lib/options.js b/src/options.js similarity index 100% rename from lib/options.js rename to src/options.js diff --git a/lib/pageVisibility.js b/src/pageVisibility.js similarity index 100% rename from lib/pageVisibility.js rename to src/pageVisibility.js diff --git a/lib/path.js b/src/path.js similarity index 100% rename from lib/path.js rename to src/path.js diff --git a/lib/pathResolver.js b/src/pathResolver.js similarity index 100% rename from lib/pathResolver.js rename to src/pathResolver.js diff --git a/lib/platform.js b/src/platform.js similarity index 100% rename from lib/platform.js rename to src/platform.js diff --git a/lib/ready.js b/src/ready.js similarity index 100% rename from lib/ready.js rename to src/ready.js diff --git a/lib/resolution.js b/src/resolution.js similarity index 100% rename from lib/resolution.js rename to src/resolution.js diff --git a/lib/roots.js b/src/roots.js similarity index 100% rename from lib/roots.js rename to src/roots.js diff --git a/lib/touch.js b/src/touch.js similarity index 100% rename from lib/touch.js rename to src/touch.js diff --git a/lib/utils.js b/src/utils.js similarity index 100% rename from lib/utils.js rename to src/utils.js diff --git a/lib/xhr.js b/src/xhr.js similarity index 100% rename from lib/xhr.js rename to src/xhr.js From 4ae4762c0ba3d5f45cffbdbd8c873882c283ec2e Mon Sep 17 00:00:00 2001 From: Ryan Duffy Date: Thu, 27 Aug 2015 16:04:12 -0500 Subject: [PATCH 034/195] =?UTF-8?q?refactors=20accessibility=20implementat?= =?UTF-8?q?ion=20=E2=80=A6=20Adopts=20a=20new=20design=20that=20uses=20oth?= =?UTF-8?q?erwise=20inert=20properties=20to=20apply=20the=20necessary=20a1?= =?UTF-8?q?1y=20attributes.=20Also=20adds=20more=20comprehensive=20aria=20?= =?UTF-8?q?support=20to=20the=20core=20Enyo=20controls.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: ENYO-2176 Enyo-DCO-1.1-Signed-off-by: Ryan Duffy (ryan.duffy@lge.com) --- .../AccessibilitySupport.js | 229 +++++++++++++----- src/Button/Button.js | 44 +++- src/Button/ButtonAccessibilitySupport.js | 29 --- src/Checkbox/Checkbox.js | 28 ++- src/Checkbox/CheckboxAccessibilitySupport.js | 29 --- src/DataGridList/DataGridList.js | 12 +- src/DataRepeater.js | 14 +- src/DataTable.js | 11 +- src/Group/Group.js | 32 ++- src/Group/GroupAccessibilitySupport.js | 29 --- src/Image/Image.js | 12 +- src/Input/Input.js | 28 ++- src/Input/InputAccessibilitySupport.js | 29 --- src/LightPanels/LightPanels.js | 1 - src/Popup/Popup.js | 26 +- src/Popup/PopupAccessibilitySupport.js | 26 -- src/Repeater.js | 12 +- src/RichText/RichText.js | 20 +- src/RichText/RichTextAccessibilitySupport.js | 22 -- src/SpriteAnimation/SpriteAnimation.js | 12 +- src/Table/Table.js | 23 +- src/Table/TableAccessibilitySupport.js | 21 -- src/TableCell/TableCell.js | 17 +- .../TableCellAccessibilitySupport.js | 21 -- src/TableRow/TableRow.js | 19 +- src/TableRow/TableRowAccessibilitySupport.js | 21 -- src/TextArea/TextArea.js | 23 +- src/TextArea/TextAreaAccessibilitySupport.js | 30 --- 28 files changed, 400 insertions(+), 420 deletions(-) delete mode 100644 src/Button/ButtonAccessibilitySupport.js delete mode 100644 src/Checkbox/CheckboxAccessibilitySupport.js delete mode 100644 src/Group/GroupAccessibilitySupport.js delete mode 100644 src/Input/InputAccessibilitySupport.js delete mode 100644 src/Popup/PopupAccessibilitySupport.js delete mode 100644 src/RichText/RichTextAccessibilitySupport.js delete mode 100644 src/Table/TableAccessibilitySupport.js delete mode 100644 src/TableCell/TableCellAccessibilitySupport.js delete mode 100644 src/TableRow/TableRowAccessibilitySupport.js delete mode 100644 src/TextArea/TextAreaAccessibilitySupport.js diff --git a/src/AccessibilitySupport/AccessibilitySupport.js b/src/AccessibilitySupport/AccessibilitySupport.js index ee60b756a..bf43e1103 100644 --- a/src/AccessibilitySupport/AccessibilitySupport.js +++ b/src/AccessibilitySupport/AccessibilitySupport.js @@ -1,7 +1,38 @@ +/** +* Mixin for adding WAI-ARIA attributes to controls +* +* @module enyo/AccessibilitySupport +*/ + var dispatcher = require('../dispatcher'), - kind = require('../kind'); + kind = require('../kind'), + utils = require('../utils'); +var defaultObservers = [ + {from: 'accessibilityDisabled', method: function () { + this.setAriaAttribute('aria-hidden', this.accessibilityDisabled ? 'true' : null); + }}, + {from: 'accessibilityLive', method: function () { + var live = this.accessibilityLive === true && 'assertive' || this.accessibilityLive || null; + this.setAriaAttribute('aria-live', live); + }}, + {path: ['accessibilityAlert', 'accessibilityRole'], method: function () { + var role = this.accessibilityAlert && 'alert' || this.accessibilityRole || null; + this.setAriaAttribute('role', role); + }}, + {path: ['ariaContent', 'accessibilityHint', 'accessibilityLabel'], method: function () { + var focusable = this.accessibilityLabel || this.content || this.accessibilityHint || null, + prefix = this.accessibilityLabel || this.content || null, + label = this.accessibilityHint && prefix && (prefix + ' ' + this.accessibilityHint) || + this.accessibilityHint || + this.accessibilityLabel || + null; + + this.setAriaAttribute('tabindex', focusable ? 0 : null); + this.setAriaAttribute('aria-label', label); + }} +]; /** * Prevents browser-initiated scrolling contained controls into view when those controls are @@ -18,11 +49,101 @@ function preventScroll (node) { } } +function updateAriaAttributes (all) { + var i, l, obs; + + for (i = 0, l = this._ariaObservers.length; i < l; i++) { + obs = this._ariaObservers[i]; + if ((all || obs.pending) && obs.method) { + obs.method(); + obs.pending = false; + } + } +} + +function registerAriaUpdate (obj) { + var fn; + if (!obj.pending) { + obj.pending = true; + fn = this.bindSafely(updateAriaAttributes); + if (!this.accessibilityDefer) { + fn(); + } else { + this.startJob('updateAriaAttributes', fn, 16); + } + } +} + +function toAriaAttribute (from, to) { + this.setAriaAttribute(to, this[from]); +} + +function staticToAriaAttribute (to, value) { + this.setAriaAttribute(to, value); +} + +function initAriaObservers (control) { + var conf = control._ariaObservers, + i, l, fn; + + control._ariaObservers = []; + for (i = 0, l = defaultObservers.length; i < l; i++) { + initAriaObserver(control, defaultObservers[i]); + } + if (conf) { + for (i = 0, l = conf.length; i < l; i++) { + initAriaObserver(control, conf[i]); + } + } + + // setup disabled observer and kickoff first run of observers + fn = updateAriaAttributes.bind(control, true); + control.addObserver('accessibilityDisabled', fn); + fn(); +} + +function initAriaObserver (control, c) { + var + // path can either source from 'path' or 'from' (for binding-style configs) + path = c.path || c.from, + + // method is either: + // 'method', if it exists, or + // staticToAriaAttribute if 'to' and 'value' exist - static binding-style config, or + // toAriaAttribute if a 'to' path exists - binding-style config + method = c.method && control.bindSafely(c.method) || + !path && c.to && c.value !== undefined && control.bindSafely(staticToAriaAttribute, c.to, c.value) || + c.to && control.bindSafely(toAriaAttribute, path, c.to) || + null, + + // import the relevant and pre-validated parts into the instance-level config + config = { + path: path, + method: method, + pending: false + }, + + // pre-bind the register method as it's used multiple times when 'path' is an array + fn = registerAriaUpdate.bind(control, config), + + // iterator + l; + + control._ariaObservers.push(config); + if (utils.isArray(path)) { + for (l = path.length - 1; l >= 0; --l) { + control.addObserver(path[l], fn); + } + } + else if (path) { + control.addObserver(path, fn); + } +} + /** -* @name AccessibilityMixin * @mixin */ -module.exports = { +var AccessibilitySupport = { /** * @private @@ -50,6 +171,15 @@ module.exports = { */ accessibilityHint: '', + /** + * The `role` of the control. May be superceded by a truthy `accessibilityAlert` value. + * + * @type {String} + * @default '' + * @public + */ + accessibilityRole: '', + /** * AccessibilityAlert is for alert message or page description. * If accessibilityAlert is true, aria role will be set to "alert" and @@ -107,20 +237,6 @@ module.exports = { */ accessibilityPreventScroll: false, - /** - * @private - */ - observers: [ - {method: 'updateAccessibilityAttributes', path: [ - 'content', - 'accessibilityHint', - 'accessibilityLabel', - 'accessibilityAlert', - 'accessibilityLive', - 'accessibilityDisabled' - ]} - ], - /** * @method * @private @@ -128,55 +244,27 @@ module.exports = { create: kind.inherit(function (sup) { return function (props) { sup.apply(this, arguments); - this.initAccessibility(); + initAriaObservers(this); }; }), /** - * One-time intialization logic for control accessibility should be done here. By default, it - * invokes the accessibility property observer, - * {@link AccessibilityMixin#updateAccessibilityAttributes} - * - * @protected - */ - initAccessibility: function () { - this.updateAccessibilityAttributes(); - }, - - /** - * Observes changes on properties that affect the accessibility attributes. Control-specific - * accessibility mixins should add an observer block for any additional properties. - * - * ```javascript - * observers: [ - * {method: 'updateAccessibilityAttributes', path: 'checked'} - * ], - * - * updateAccessibilityAttributes: kind.inherit(function (sup) { - * return function (was, is, prop) { - * var enabled = !this.accessibilityDisabled; - * sup.apply(this, arguments); - * this.setAttribute('aria-checked', enabled && this.checked || null); - * }; - * }); - * ``` + * If accessibilityDisabled is `false`, sets the node attribute. Otherwise, removes it. * - * @protected + * @param {String} name Attribute name + * @param {String} value Attribute value + * @public */ - updateAccessibilityAttributes: function (was, is, prop) { - var enabled = !this.accessibilityDisabled, - focusable = this.accessibilityLabel || this.content || this.accessibilityHint || null, - prefix = this.accessibilityLabel || this.content || null, - label = this.accessibilityHint && prefix && (prefix + ' ' + this.accessibilityHint) || - this.accessibilityHint || - this.accessibilityLabel || - null; - - this.setAttribute('tabindex', focusable && enabled ? 0 : null); - this.setAttribute('aria-label', enabled ? label : null); - this.setAttribute('role', this.accessibilityAlert && enabled ? 'alert' : null); - this.setAttribute('aria-live', this.accessibilityLive && enabled ? 'assertive' : null); - this.setAttribute('aria-hidden', enabled ? null : 'true'); + setAriaAttribute: function (name, value) { + // if the control is disabled, don't set any aria properties except aria-hidden + if (this.accessibilityDisabled && name != 'aria-hidden') { + value = null; + } + // if the value is defined and non-null, cast it to a String + else if (value !== undefined && value !== null) { + value = String(value); + } + this.setAttribute(name, value); }, /** @@ -191,3 +279,24 @@ module.exports = { }; }) }; + +var sup = kind.concatHandler; +kind.concatHandler = function (ctor, props, instance) { + sup.call(this, ctor, props, instance); + + var proto = ctor.prototype || ctor, + ariaObservers = proto._ariaObservers && proto._ariaObservers.slice(), + incoming = props.ariaObservers; + + if (incoming && incoming instanceof Array) { + if (ariaObservers) { + ariaObservers.push.apply(ariaObservers, incoming); + } else { + ariaObservers = incoming.slice(); + } + } + + proto._ariaObservers = ariaObservers; +}; + +module.exports = AccessibilitySupport; \ No newline at end of file diff --git a/src/Button/Button.js b/src/Button/Button.js index f985a1552..f6ccbcee9 100644 --- a/src/Button/Button.js +++ b/src/Button/Button.js @@ -6,11 +6,9 @@ require('enyo'); */ var - kind = require('../kind'), - options = require('../options'); + kind = require('../kind'); var - ToolDecorator = require('../ToolDecorator'), - ButtonAccessibilitySupport = require('./ButtonAccessibilitySupport'); + ToolDecorator = require('../ToolDecorator'); /** * {@link module:enyo/Button~Button} implements an HTML [button]{@glossary button}, with support @@ -38,11 +36,6 @@ module.exports = kind( */ kind: ToolDecorator, - /** - * @private - */ - mixins: options.accessibility ? [ButtonAccessibilitySupport] : null, - /** * @private */ @@ -109,5 +102,36 @@ module.exports = kind( } else { this.setActive(true); } - } + }, + + // Accessibility + + /** + * @default button + * @type {String} + * @see enyo/AccessibilitySupport~AccessibilitySupport#accessibilityRole + * @public + */ + accessibilityRole: 'button', + + /** + * When `true`, `aria-pressed` will reflect the state of + * {@link module:enyo/GroupItem~GroupItem#active} + * + * @type {Boolean} + * @default false + * @public + */ + accessibilityPressed: false, + + /** + * @private + */ + ariaObservers: [ + {from: 'disabled', to: 'aria-disabled'}, + {path: ['active', 'accessibilityPressed'], method: function () { + this.setAriaAttribute('aria-pressed', this.accessibilityPressed ? String(this.active) : null); + }}, + {from: 'accessibilityRole', to: 'role'} + ] }); diff --git a/src/Button/ButtonAccessibilitySupport.js b/src/Button/ButtonAccessibilitySupport.js deleted file mode 100644 index 5274ccfea..000000000 --- a/src/Button/ButtonAccessibilitySupport.js +++ /dev/null @@ -1,29 +0,0 @@ -var - kind = require('../kind'); - -/** -* @name ButtonAccessibilityMixin -* @mixin -*/ -module.exports = { - - /** - * @private - */ - observers: [ - {method: 'updateAccessibilityAttributes', path: 'disabled'} - ], - - /** - * @private - */ - updateAccessibilityAttributes: kind.inherit(function (sup) { - return function (was, is, prop) { - var enabled = !this.accessibilityDisabled; - sup.apply(this, arguments); - this.setAttribute('role', enabled ? 'button' : null); - this.setAttribute('tabindex', enabled ? 0 : null); - this.setAttribute('aria-disabled', enabled && this.disabled ? 'true' : null); - }; - }) -}; \ No newline at end of file diff --git a/src/Checkbox/Checkbox.js b/src/Checkbox/Checkbox.js index a46b9da9b..d58885aa8 100644 --- a/src/Checkbox/Checkbox.js +++ b/src/Checkbox/Checkbox.js @@ -7,12 +7,10 @@ require('enyo'); var kind = require('../kind'), - options = require('../options'), utils = require('../utils'), platform = require('../platform'); var - Input = require('../Input'), - CheckboxAccessibilitySupport = require('./CheckboxAccessibilitySupport'); + Input = require('../Input'); /** * Fires when checkbox is tapped. @@ -47,11 +45,6 @@ module.exports = kind( */ kind: Input, - /** - * @private - */ - mixins: options.accessibility ? [CheckboxAccessibilitySupport] : null, - /** * @private */ @@ -198,5 +191,22 @@ module.exports = kind( if (platform.ie <= 8) { this.bubble('onchange', e); } - } + }, + + // Accessibility + + /** + * @default checkbox + * @type {String} + * @see enyo/AccessibilitySupport~AccessibilitySupport#accessibilityRole + * @public + */ + accessibilityRole: 'checkbox', + + /** + * @private + */ + ariaObservers: [ + {from: 'checked', to: 'aria-checked'} + ] }); diff --git a/src/Checkbox/CheckboxAccessibilitySupport.js b/src/Checkbox/CheckboxAccessibilitySupport.js deleted file mode 100644 index bf830665e..000000000 --- a/src/Checkbox/CheckboxAccessibilitySupport.js +++ /dev/null @@ -1,29 +0,0 @@ -var - kind = require('../kind'); - -/** -* @name CheckboxAccessibilityMixin -* @mixin -*/ -module.exports = { - - /** - * @private - */ - observers: [ - {method: 'updateAccessibilityAttributes', path: 'checked'} - ], - - /** - * @private - */ - updateAccessibilityAttributes: kind.inherit(function (sup) { - return function (was, is, prop) { - var enabled = !this.accessibilityDisabled; - sup.apply(this, arguments); - this.setAttribute('role', enabled ? 'checkbox' : null); - this.setAttribute('tabindex', enabled ? 0 : null); - this.setAttribute('aria-checked', enabled ? String(this.checked) : null); - }; - }) -}; \ No newline at end of file diff --git a/src/DataGridList/DataGridList.js b/src/DataGridList/DataGridList.js index 0fd24593e..ba2c9e5ad 100644 --- a/src/DataGridList/DataGridList.js +++ b/src/DataGridList/DataGridList.js @@ -143,7 +143,17 @@ var DataGridList = module.exports = kind( * * @private */ - classes: 'enyo-data-grid-list' + classes: 'enyo-data-grid-list', + + // Accessibility + + /** + * @default grid + * @type {String} + * @see enyo/AccessibilitySupport~AccessibilitySupport#accessibilityRole + * @public + */ + accessibilityRole: 'grid' }); DataGridList.delegates.verticalGrid = VerticalGridDelegate; diff --git a/src/DataRepeater.js b/src/DataRepeater.js index a88973588..fc31a9595 100644 --- a/src/DataRepeater.js +++ b/src/DataRepeater.js @@ -774,7 +774,19 @@ var DataRepeater = module.exports = kind( /** * @private */ - _selection: null + _selection: null, + + // Accessibility + + /** + * @private + */ + ariaObservers: [ + {path: ['selection', 'multipleSelection'], method: function () { + this.setAriaAttribute('role', this.selection ? 'listbox' : 'list'); + this.setAriaAttribute('aria-multiselectable', this.selection && this.multipleSelection ? true : null); + }} + ] }); /** diff --git a/src/DataTable.js b/src/DataTable.js index c3e553f34..1f6ecfe49 100644 --- a/src/DataTable.js +++ b/src/DataTable.js @@ -51,5 +51,14 @@ module.exports = kind( kind: Table, name: 'container', style: 'width: 100%;' - } + }, + + // Accessibility + + /** + * @private + */ + ariaObservers: [ + {to: 'role', value: 'grid'} + ] }); diff --git a/src/Group/Group.js b/src/Group/Group.js index c0ad9ddba..eabd555e3 100644 --- a/src/Group/Group.js +++ b/src/Group/Group.js @@ -6,11 +6,9 @@ require('enyo'); */ var - kind = require('../kind'), - options = require('../options'); + kind = require('../kind'); var - Control = require('../Control'), - GroupAccessibilitySupport = require('./GroupAccessibilitySupport'); + Control = require('../Control'); /** * The extended {@glossary event} [object]{@glossary Object} that is provided when the @@ -55,11 +53,6 @@ module.exports = kind( * @private */ kind: Control, - - /** - * @private - */ - mixins: options.accessibility ? [GroupAccessibilitySupport] : null, /** * @private @@ -167,5 +160,24 @@ module.exports = kind( this.active.addClass('active'); } this.doActiveChanged({active: this.active}); - } + }, + + // Accessibility + + /** + * @default grid + * @type {String} + * @see enyo/AccessibilitySupport~AccessibilitySupport#accessibilityRole + * @public + */ + accessibilityRole: 'group', + + /** + * @private + */ + ariaObservers: [ + {path: 'active', method: function () { + this.setAriaAttribute('aria-activedescendant', this.active ? this.active.getId() : null); + }} + ] }); diff --git a/src/Group/GroupAccessibilitySupport.js b/src/Group/GroupAccessibilitySupport.js deleted file mode 100644 index 56512b514..000000000 --- a/src/Group/GroupAccessibilitySupport.js +++ /dev/null @@ -1,29 +0,0 @@ -var - kind = require('../kind'); - -/** -* @name GroupAccessibilityMixin -* @mixin -*/ -module.exports = { - - /** - * @private - */ - observers: [ - {method: 'updateAccessibilityAttributes', path: 'active'} - ], - - /** - * @private - */ - updateAccessibilityAttributes: kind.inherit(function (sup) { - return function (was, is, prop) { - var enabled = !this.accessibilityDisabled; - sup.apply(this, arguments); - this.setAttribute('role', enabled ? 'group' : null); - this.setAttribute('tabindex', enabled ? 0 : null); - this.setAttribute('aria-activedescendant', this.active && enabled ? this.active.getId() : null); - }; - }) -}; \ No newline at end of file diff --git a/src/Image/Image.js b/src/Image/Image.js index 0ef63e8c0..0094d33e1 100644 --- a/src/Image/Image.js +++ b/src/Image/Image.js @@ -346,5 +346,15 @@ module.exports = kind( 'E7IiAvPjxsaW5lIHgxPSIwIiB5MT0iMCIgeDI9IjEwMCUiIHkyPSIxMDAlIiBzdHlsZT0ic3Ryb2tlOiAjNDQ0' + 'OyBzdHJva2Utd2lkdGg6IDE7IiAvPjxsaW5lIHgxPSIxMDAlIiB5MT0iMCIgeDI9IjAiIHkyPSIxMDAlIiBzdH' + 'lsZT0ic3Ryb2tlOiAjNDQ0OyBzdHJva2Utd2lkdGg6IDE7IiAvPjwvc3ZnPg==' - } + }, + + // Accessibility + + /** + * @default img + * @type {String} + * @see enyo/AccessibilitySupport~AccessibilitySupport#accessibilityRole + * @public + */ + accessibilityRole: 'img' }); diff --git a/src/Input/Input.js b/src/Input/Input.js index f06d27259..a450ca098 100644 --- a/src/Input/Input.js +++ b/src/Input/Input.js @@ -9,11 +9,9 @@ var kind = require('../kind'), utils = require('../utils'), dispatcher = require('../dispatcher'), - options = require('../options'), platform = require('../platform'); var - Control = require('../Control'), - InputAccessibilitySupport = require('./InputAccessibilitySupport'); + Control = require('../Control'); /** * Fires immediately when the text changes. @@ -79,11 +77,6 @@ module.exports = kind( */ kind: Control, - /** - * @private - */ - mixins: options.accessibility ? [InputAccessibilitySupport] : null, - /** * @private */ @@ -333,5 +326,22 @@ module.exports = kind( input: function () { var val = this.getNodeProperty('value'); this.setValue(val); - } + }, + + // Accessibility + + /** + * @default textbox + * @type {String} + * @see enyo/AccessibilitySupport~AccessibilitySupport#accessibilityRole + * @public + */ + accessibilityRole: 'textbox', + + /** + * @private + */ + ariaObservers: [ + {path: 'disabled', to: 'aria-disabled'} + ] }); diff --git a/src/Input/InputAccessibilitySupport.js b/src/Input/InputAccessibilitySupport.js deleted file mode 100644 index d2ad7aaa7..000000000 --- a/src/Input/InputAccessibilitySupport.js +++ /dev/null @@ -1,29 +0,0 @@ -var - kind = require('../kind'); - -/** -* @name InputAccessibilityMixin -* @mixin -*/ -module.exports = { - - /** - * @private - */ - observers: [ - {method: 'updateAccessibilityAttributes', path: 'disabled'} - ], - - /** - * @private - */ - updateAccessibilityAttributes: kind.inherit(function (sup) { - return function (was, is, prop) { - var enabled = !this.accessibilityDisabled; - sup.apply(this, arguments); - this.setAttribute('role', enabled ? 'textbox' : null); - this.setAttribute('tabindex', enabled ? 0 : null); - this.setAttribute('aria-disabled', enabled && this.disabled ? 'true' : null); - }; - }) -}; \ No newline at end of file diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index 384989b6c..021197f9d 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -768,7 +768,6 @@ module.exports = kind( this.removeTask(this.getViewId(viewProps[idx])); } } - }); module.exports.Panel = LightPanel; diff --git a/src/Popup/Popup.js b/src/Popup/Popup.js index 4e03fbefd..bb850b277 100644 --- a/src/Popup/Popup.js +++ b/src/Popup/Popup.js @@ -9,15 +9,13 @@ require('enyo'); var kind = require('../kind'), - options = require('../options'), utils = require('../utils'), dispatcher = require('../dispatcher'); var Control = require('../Control'), Signals = require('../Signals'), Scrim = require('../Scrim'), - Dom = require('../dom'), - PopupAccessibilitySupport = require('./PopupAccessibilitySupport'); + Dom = require('../dom'); /** * Fires after the [popup]{@link module:enyo/Popup~Popup} is shown. @@ -88,16 +86,6 @@ var Popup = module.exports = kind( */ kind: Control, - /** - * @private - */ - mixins: options.accessibility ? [PopupAccessibilitySupport] : null, - - /** - * @private - */ - - /** * @private */ @@ -747,7 +735,17 @@ var Popup = module.exports = kind( } this._zIndex = z; return this._zIndex; - } + }, + + // Accessibility + + /** + * @default dialog + * @type {String} + * @see enyo/AccessibilitySupport~AccessibilitySupport#accessibilityRole + * @public + */ + accessibilityRole: 'dialog' }); /** diff --git a/src/Popup/PopupAccessibilitySupport.js b/src/Popup/PopupAccessibilitySupport.js deleted file mode 100644 index 6c1ce3279..000000000 --- a/src/Popup/PopupAccessibilitySupport.js +++ /dev/null @@ -1,26 +0,0 @@ -var - kind = require('../kind'); - -/** -* @name PopupAccessibilityMixin -* @mixin -*/ -module.exports = { - - /** - * @private - */ - observers: [ - {method: 'updateAccessibilityAttributes', path: 'showing'} - ], - - /** - * @private - */ - updateAccessibilityAttributes: kind.inherit(function (sup) { - return function (was, is, prop) { - sup.apply(this, arguments); - this.set('accessibilityAlert', this.accessibilityDisabled ? null : this.showing); - }; - }) -}; \ No newline at end of file diff --git a/src/Repeater.js b/src/Repeater.js index 4d43f8e4b..371e9e553 100644 --- a/src/Repeater.js +++ b/src/Repeater.js @@ -233,5 +233,15 @@ module.exports = kind( */ setCount: function (count) { this.set('count', count, {force: true}); - } + }, + + // Accessibility + + /** + * @default list + * @type {String} + * @see enyo/AccessibilitySupport~AccessibilitySupport#accessibilityRole + * @public + */ + accessibilityRole: 'list' }); diff --git a/src/RichText/RichText.js b/src/RichText/RichText.js index c1e57fa4c..dae00b0de 100644 --- a/src/RichText/RichText.js +++ b/src/RichText/RichText.js @@ -9,14 +9,12 @@ require('enyo'); var kind = require('../kind'), - options = require('../options'), utils = require('../utils'), platform = require('../platform'); var Control = require('../Control'), - Input = require('../Input'), - RichTextAccessibilitySupport = require('./RichTextAccessibilitySupport'); + Input = require('../Input'); /** * The type of change to apply. Possible values are `'move'` and `'extend'`. @@ -68,11 +66,6 @@ var RichText = module.exports = kind( */ kind: Input, - /** - * @private - */ - mixins: options.accessibility ? [RichTextAccessibilitySupport] : null, - /** * @private */ @@ -345,5 +338,14 @@ var RichText = module.exports = kind( var v = this.allowHtml ? val : Control.escapeHtml(val).replace(/\n/g, '
'); document.execCommand('insertHTML', false, v); } - } + }, + + // Accessibility + + /** + * @private + */ + ariaObservers: [ + {to: 'aria-multiline', value: true} + ] }); diff --git a/src/RichText/RichTextAccessibilitySupport.js b/src/RichText/RichTextAccessibilitySupport.js deleted file mode 100644 index f42daae6a..000000000 --- a/src/RichText/RichTextAccessibilitySupport.js +++ /dev/null @@ -1,22 +0,0 @@ -var - kind = require('../kind'); - -/** -* @name RichTextAccessibilityMixin -* @mixin -*/ -module.exports = { - - /** - * @private - */ - updateAccessibilityAttributes: kind.inherit(function (sup) { - return function (was, is, prop) { - var enabled = !this.accessibilityDisabled; - sup.apply(this, arguments); - this.setAttribute('role', enabled ? 'textbox' : null); - this.setAttribute('tabindex', enabled ? 0 : null); - this.setAttribute('aria-multiline', enabled ? true : null); - }; - }) -}; \ No newline at end of file diff --git a/src/SpriteAnimation/SpriteAnimation.js b/src/SpriteAnimation/SpriteAnimation.js index ad32a32ad..82fea364d 100644 --- a/src/SpriteAnimation/SpriteAnimation.js +++ b/src/SpriteAnimation/SpriteAnimation.js @@ -515,5 +515,15 @@ module.exports = kind( } } return this._positionList; - } + }, + + // Accessiility + + /** + * @default img + * @type {String} + * @see enyo/AccessibilitySupport~AccessibilitySupport#accessibilityRole + * @public + */ + accessibilityRole: 'img' }); diff --git a/src/Table/Table.js b/src/Table/Table.js index c1d5c8abd..5770be3ff 100644 --- a/src/Table/Table.js +++ b/src/Table/Table.js @@ -6,12 +6,10 @@ require('enyo'); */ var - kind = require('../kind'), - options = require('../options'); + kind = require('../kind'); var Control = require('../Control'), - TableRow = require('../TableRow'), - TableAccessibilitySupport = require('./TableAccessibilitySupport'); + TableRow = require('../TableRow'); /* * TODO: Won't work in IE8 because we can't set innerHTML on table elements. We'll need to fall @@ -42,11 +40,6 @@ module.exports = kind( */ kind: Control, - /** - * @private - */ - mixins: options.accessibility ? [TableAccessibilitySupport] : null, - /** * @private */ @@ -63,5 +56,15 @@ module.exports = kind( /** * @private */ - defaultKind: TableRow + defaultKind: TableRow, + + // Accessibility + + /** + * @default grid + * @type {String} + * @see enyo/AccessibilitySupport~AccessibilitySupport#accessibilityRole + * @public + */ + accessibilityRole: 'grid' }); diff --git a/src/Table/TableAccessibilitySupport.js b/src/Table/TableAccessibilitySupport.js deleted file mode 100644 index 0e0f636a5..000000000 --- a/src/Table/TableAccessibilitySupport.js +++ /dev/null @@ -1,21 +0,0 @@ -var - kind = require('../kind'); - -/** -* @name TableAccessibilityMixin -* @mixin -*/ -module.exports = { - - /** - * @private - */ - updateAccessibilityAttributes: kind.inherit(function (sup) { - return function (was, is, prop) { - var enabled = !this.accessibilityDisabled; - sup.apply(this, arguments); - this.setAttribute('role', enabled ? 'grid' : null); - this.setAttribute('tabindex', enabled ? 0 : null); - }; - }) -}; \ No newline at end of file diff --git a/src/TableCell/TableCell.js b/src/TableCell/TableCell.js index 85153ea60..fce02cf73 100644 --- a/src/TableCell/TableCell.js +++ b/src/TableCell/TableCell.js @@ -6,11 +6,9 @@ require('enyo'); */ var - kind = require('../kind'), - options = require('../options'); + kind = require('../kind'); var - Control = require('../Control'), - TableCellAccessibilitySupport = require('./TableCellAccessibilitySupport'); + Control = require('../Control'); /** * {@link module:enyo/TableCell~TableCell} implements an HTML [<td>]{@glossary td} element. @@ -36,10 +34,15 @@ module.exports = kind( /** * @private */ - mixins: options.accessibility ? [TableCellAccessibilitySupport] : null, + tag: 'td', + + // Accessibility /** - * @private + * @default gridcell + * @type {String} + * @see enyo/AccessibilitySupport~AccessibilitySupport#accessibilityRole + * @public */ - tag: 'td' + accessibilityRole: 'gridcell' }); diff --git a/src/TableCell/TableCellAccessibilitySupport.js b/src/TableCell/TableCellAccessibilitySupport.js deleted file mode 100644 index 6ebfeeddf..000000000 --- a/src/TableCell/TableCellAccessibilitySupport.js +++ /dev/null @@ -1,21 +0,0 @@ -var - kind = require('../kind'); - -/** -* @name TableCellAccessibilityMixin -* @mixin -*/ -module.exports = { - - /** - * @private - */ - updateAccessibilityAttributes: kind.inherit(function (sup) { - return function (was, is, prop) { - var enabled = !this.accessibilityDisabled; - sup.apply(this, arguments); - this.setAttribute('role', enabled ? 'gridcell' : null); - this.setAttribute('tabindex', enabled ? 0 : null); - }; - }) -}; \ No newline at end of file diff --git a/src/TableRow/TableRow.js b/src/TableRow/TableRow.js index e73912df3..42f72d6b5 100644 --- a/src/TableRow/TableRow.js +++ b/src/TableRow/TableRow.js @@ -6,12 +6,10 @@ require('enyo'); */ var - kind = require('../kind'), - options = require('../options'); + kind = require('../kind'); var Control = require('../Control'), - TableCell = require('../TableCell'), - TableRowAccessibilitySupport = require('./TableRowAccessibilitySupport'); + TableCell = require('../TableCell'); /** * {@link module:enyo/TableRow~TableRow} implements an HTML [<tr>]{@glossary tr} element. @@ -37,15 +35,20 @@ module.exports = kind( /** * @private */ - mixins: options.accessibility ? [TableRowAccessibilitySupport] : null, + tag: 'tr', /** * @private */ - tag: 'tr', + defaultKind: TableCell, + + // Accessibility /** - * @private + * @default row + * @type {String} + * @see enyo/AccessibilitySupport~AccessibilitySupport#accessibilityRole + * @public */ - defaultKind: TableCell + accessibilityRole: 'row' }); diff --git a/src/TableRow/TableRowAccessibilitySupport.js b/src/TableRow/TableRowAccessibilitySupport.js deleted file mode 100644 index 0a1503fff..000000000 --- a/src/TableRow/TableRowAccessibilitySupport.js +++ /dev/null @@ -1,21 +0,0 @@ -var - kind = require('../kind'); - -/** -* @name TableRowAccessibilityMixin -* @mixin -*/ -module.exports = { - - /** - * @private - */ - updateAccessibilityAttributes: kind.inherit(function (sup) { - return function (was, is, prop) { - var enabled = !this.accessibilityDisabled; - sup.apply(this, arguments); - this.setAttribute('role', enabled ? 'row' : null); - this.setAttribute('tabindex', enabled ? 0 : null); - }; - }) -}; \ No newline at end of file diff --git a/src/TextArea/TextArea.js b/src/TextArea/TextArea.js index ebe96e260..22400ee3d 100644 --- a/src/TextArea/TextArea.js +++ b/src/TextArea/TextArea.js @@ -6,11 +6,9 @@ require('enyo'); */ var - kind = require('../kind'), - options = require('../options'); + kind = require('../kind'); var - Input = require('../Input'), - TextAreaAccessibilitySupport = require('./TextAreaAccessibilitySupport'); + Input = require('../Input'); /** * {@link module:enyo/TextArea~TextArea} implements an HTML [<textarea>]{@glossary textarea} @@ -38,11 +36,6 @@ module.exports = kind( */ kind: Input, - /** - * @private - */ - mixins: options.accessibility ? [TextAreaAccessibilitySupport] : null, - /** * @private */ @@ -65,5 +58,15 @@ module.exports = kind( sup.apply(this, arguments); this.valueChanged(); }; - }) + }), + + // Accessibility + + /** + * @private + */ + ariaObservers: [ + {to: 'aria-multiline', value: true}, + {from: 'disabled', to: 'aria-disabled'} + ] }); diff --git a/src/TextArea/TextAreaAccessibilitySupport.js b/src/TextArea/TextAreaAccessibilitySupport.js deleted file mode 100644 index fbcbb5c42..000000000 --- a/src/TextArea/TextAreaAccessibilitySupport.js +++ /dev/null @@ -1,30 +0,0 @@ -var - kind = require('../kind'); - -/** -* @name TextAreaAccessibilityMixin -* @mixin -*/ -module.exports = { - - /** - * @private - */ - observers: [ - {method: 'updateAccessibilityAttributes', path: 'disabled'} - ], - - /** - * @private - */ - updateAccessibilityAttributes: kind.inherit(function (sup) { - return function (was, is, prop) { - var enabled = !this.accessibilityDisabled; - sup.apply(this, arguments); - this.setAttribute('role', enabled ? 'textbox' : null); - this.setAttribute('tabindex', enabled ? 0 : null); - this.setAttribute('aria-multiline', enabled ? 'true' : null); - this.setAttribute('aria-disabled', enabled && this.disabled ? 'true' : null); - }; - }) -}; \ No newline at end of file From 681f14aa5bd1ec496967b4a9e952e3d6a42a1d83 Mon Sep 17 00:00:00 2001 From: Ryan Duffy Date: Fri, 28 Aug 2015 10:54:28 -0500 Subject: [PATCH 035/195] fix path to js in gulpfile Issue: ENYO-2414 Enyo-DCO-1.1-Signed-off-by: Ryan Duffy (ryan.duffy@lge.com) --- gulpfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index 88d607bf2..1c227f69e 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -14,7 +14,7 @@ gulp.task('jshint', lint); function lint () { return gulp - .src('./lib/**/*.js') + .src('./src/**/*.js') .pipe(jshint()) .pipe(jshint.reporter(stylish, {verbose: true})) .pipe(jshint.reporter('fail')); From 3bb388bf69cdf13b0eb46c53887e102db600ada9 Mon Sep 17 00:00:00 2001 From: "suhyung.lee" Date: Mon, 8 Jun 2015 11:29:29 +0900 Subject: [PATCH 036/195] ENYO-1725: enyo.Video: setPlaybackRate should be called ahead of play in a certain case ## Issue If user selects REW/FF when content is paused, setPlaybackRate should be called ahead of play. ## Fix added flag, "isNeedPlay", to make node play after setPlaybackRate . Enyo-DCO-1.1-Signed-off-by: Suhyung Lee (suhyung2.lee@lge.com) --- src/Video.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Video.js b/src/Video.js index 4ebbea0ca..720f00ca6 100644 --- a/src/Video.js +++ b/src/Video.js @@ -499,7 +499,8 @@ module.exports = kind( * @public */ fastForward: function () { - var node = this.hasNode(); + var node = this.hasNode(), + isNeedPlay = false; if (!node) { return; @@ -520,7 +521,7 @@ module.exports = kind( this.selectPlaybackRateArray('slowForward'); this._speedIndex = 0; if (this.isPaused()) { - node.play(); + isNeedPlay = true; } this._prevCommand = 'slowForward'; break; @@ -548,6 +549,7 @@ module.exports = kind( this.setPlaybackRate(this.selectPlaybackRate(this._speedIndex)); + isNeedPlay && node.play(); }, /** @@ -556,8 +558,8 @@ module.exports = kind( * @public */ rewind: function () { - var node = this.hasNode(); - + var node = this.hasNode(), + isNeedPlay = false; if (!node) { return; } @@ -577,7 +579,7 @@ module.exports = kind( this.selectPlaybackRateArray('slowRewind'); this._speedIndex = 0; if (this.isPaused() && this.node.duration > this.node.currentTime) { - node.play(); + isNeedPlay = true; } this._prevCommand = 'slowRewind'; break; @@ -594,6 +596,8 @@ module.exports = kind( this.setPlaybackRate(this.selectPlaybackRate(this._speedIndex)); + + isNeedPlay && node.play(); }, /** From 2c6f4f067e4e513b9b52da4d49f947804894fa35 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Wed, 26 Aug 2015 23:16:19 -0700 Subject: [PATCH 037/195] ENYO-2384: Use panel container wrapper for panels. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/LightPanels/LightPanel.js | 20 +-- src/LightPanels/LightPanels.css | 57 ++++++--- src/LightPanels/LightPanels.js | 217 ++++++++++++++++---------------- src/ViewPreloadSupport.js | 6 + 4 files changed, 160 insertions(+), 140 deletions(-) diff --git a/src/LightPanels/LightPanel.js b/src/LightPanels/LightPanel.js index b85fc9318..cb556af96 100644 --- a/src/LightPanels/LightPanel.js +++ b/src/LightPanels/LightPanel.js @@ -40,13 +40,6 @@ module.exports = kind( */ kind: Control, - /** - * @private - */ - handlers: { - ontransitionend: 'transitionEnd' - }, - /** * The current [state]{@link module:enyo/LightPanel~LightPanel#States}. * @@ -68,18 +61,7 @@ module.exports = kind( * * @public */ - postTransition: function () { - // TODO: this is added for backwards-compatibility and is deprecated functionality - if (this.state == States.ACTIVE && this.activated) this.activated(); - else if (this.state == States.INACTIVE && this.deactivated) this.deactivated(); - }, - - /** - * @private - */ - transitionEnd: function (sender, ev) { - if (ev.originator === this) this.set('state', this.state == States.ACTIVATING ? States.ACTIVE : States.INACTIVE); - } + postTransition: function () {} }); diff --git a/src/LightPanels/LightPanels.css b/src/LightPanels/LightPanels.css index 73043e4ad..e4a400bb2 100644 --- a/src/LightPanels/LightPanels.css +++ b/src/LightPanels/LightPanels.css @@ -7,7 +7,16 @@ overflow: hidden; } -.enyo-light-panels > * { +/* Panels container styles */ + +.enyo-light-panels.transitioning .panels-container { + -webkit-transform: translateZ(0); + transform: translateZ(0); +} + +.enyo-light-panels .panels-container > * { + display: inline-block; + vertical-align: top; position: absolute; top: 0; left: 0; @@ -16,22 +25,42 @@ box-sizing: border-box; } -.enyo-light-panels.horizontal.forwards > * { - transform: translateX(100%); - -webkit-transform: translateX(100%); +.enyo-light-panels.horizontal .panels-container { + width: 200%; + height: 100%; } -.enyo-light-panels.horizontal.backwards > * { - transform: translateX(-100%); - -webkit-transform: translateX(-100%); +.enyo-light-panels.vertical .panels-container { + width: 100%; + height: 200%; } -.enyo-light-panels.vertical.forwards > * { - transform: translateY(100%); - -webkit-transform: translateY(100%); +.enyo-light-panels.horizontal .panels-container > * { + width: 50%; } -.enyo-light-panels.vertical.backwards > * { - transform: translateY(-100%); - -webkit-transform: translateY(-100%); -} \ No newline at end of file +.enyo-light-panels.vertical .panels-container > * { + height: 50%; +} + +/* Individual panel styles */ + +.enyo-light-panels.horizontal.forwards .panels-container > .next, +.enyo-light-panels.horizontal.backwards .panels-container > .previous { + left: 50%; +} + +.enyo-light-panels.horizontal.backwards .panels-container > .next, +.enyo-light-panels.horizontal.forwards .panels-container > .previous { + right: 50%; +} + +.enyo-light-panels.vertical.forwards .panels-container > .next, +.enyo-light-panels.vertical.backwards .panels-container > .previous { + top: 50%; +} + +.enyo-light-panels.vertical.backwards .panels-container > .next, +.enyo-light-panels.vertical.forwards .panels-container > .previous { + bottom: 50%; +} diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index 384989b6c..8c26b7484 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -19,6 +19,9 @@ var LightPanel = require('./LightPanel'), States = LightPanel.States; +var + trans, wTrans; + /** * @enum {Number} * @memberof module:enyo/LightPanels~LightPanels @@ -172,6 +175,13 @@ module.exports = kind( */ direction: Direction.FORWARDS, + /** + * @private + */ + components: [ + {kind: Control, name: 'client', classes: 'panels-container', ontransitionend: 'transitionFinished'} + ], + /** * @method * @private @@ -179,7 +189,7 @@ module.exports = kind( create: kind.inherit(function (sup) { return function () { sup.apply(this, arguments); - this._handleStateChange = this.bindSafely('handleStateChange'); + this.updateTransforms(); this.orientationChanged(); this.directionChanged(); this.indexChanged(); @@ -218,6 +228,20 @@ module.exports = kind( } }, + /** + * @private + */ + durationChanged: function () { + this.updateTransforms(); + }, + + /** + * @private + */ + timingFunctionChanged: function () { + this.updateTransforms(); + }, + /** * @private @@ -377,7 +401,7 @@ module.exports = kind( newPanel.render(); } else { compareIdx = opts && opts.targetIndex != null ? opts.targetIndex : lastIndex + info.length; - this.shiftPanel(newPanel, compareIdx - this.index); + this.shiftContainer(compareIdx - this.index); } if (opts && opts.forcePostTransition && newPanel.postTransition) { newPanel.postTransition(); @@ -459,17 +483,6 @@ module.exports = kind( return view.panelId; }, - /** - * Reset the state of the given panel. Currently resets the translated position of the panel. - * - * @param {Object} view - The panel whose state we wish to reset. - * @private - */ - resetView: function (view) { - // reset position - dom.transformValue(view, 'translate' + this.orientation, 100 * this.direction + '%'); - }, - /* @@ -531,73 +544,74 @@ module.exports = kind( return this.generated && this.getPanels().length > 1 && this.animate; }, - /** - * @private - */ - addChild: kind.inherit(function (sup) { - return function (control) { - control.observe('state', this._handleStateChange); - sup.apply(this, arguments); - }; - }), - /** - * @private - */ - removeChild: kind.inherit(function (sup) { - return function (control) { - sup.apply(this, arguments); - control.unobserve('state', this._handleStateChange); - }; - }), /* - ============== - Event handlers - ============== + ======================= + Private support methods + ======================= */ /** + * Updates the transform style strings we will apply to the panel container. + * * @private */ - handleStateChange: function (was, is) { - var panel; - if (was == States.ACTIVATING || was == States.DEACTIVATING) { - panel = was == States.ACTIVATING ? this._currentPanel : this._previousPanel; - panel.removeClass('transitioning'); - - // async'ing this as it seems to improve ending transition performance on the TV. Requires - // further investigation into its behavior. - asyncMethod(function () { - if (panel.postTransition) panel.postTransition(); - }); - - if ((this._currentPanel.state == States.ACTIVE) && - (!this._previousPanel || this._previousPanel.state == States.INACTIVE)) - this.finishTransition(); - } + updateTransforms: function () { + trans = 'transform ' + this.duration + 'ms ' + this.timingFunction; + wTrans = '-webkit-' + trans; }, - - - /* - ======================= - Private support methods - ======================= + /** + * Cleans-up the given panel, usually after the transition has completed. + * + * @param {Object} panel - The panel we wish to clean-up. + * @private */ + cleanUpPanel: function (panel) { + if (panel) { + panel.set('state', panel === this._currentPanel ? States.ACTIVE : States.INACTIVE); + if (panel.postTransition) { + // Async'ing this as it seems to improve ending transition performance on the TV. + // Requires further investigation into its behavior. + asyncMethod(this, function () { + panel.postTransition(); + }); + } + } + }, /** - * When all transitions (i.e. next/previous panel) have completed, we perform some clean-up work. + * When the transition has completed, we perform some clean-up work. * + * @param {Object} sender - The event sender. + * @param {Object} ev - The event object. + * @param {Boolean} [direct] - If `true`, this was a non-animated (direct) transition. * @private */ - finishTransition: function () { - if ((this._indexDirection < 0 && (this.popOnBack || this.cacheViews) && this.index < this.getPanels().length - 1) || - (this._indexDirection > 0 && (this.popOnForward || this.cacheViews) && this.index > 0)) { - this.popPanels(this.index, this._indexDirection); + transitionFinished: function (sender, ev, direct) { + var prevPanel, currPanel; + + if (ev && ev.originator === this.$.client || direct) { + prevPanel = this._previousPanel; + currPanel = this._currentPanel; + + if ((this._indexDirection < 0 && (this.popOnBack || this.cacheViews) && this.index < this.getPanels().length - 1) || + (this._indexDirection > 0 && (this.popOnForward || this.cacheViews) && this.index > 0)) { + this.popPanels(this.index, this._indexDirection); + } + if (this.popQueue && this.popQueue.length) this.finalizePurge(); + + this.cleanUpPanel(prevPanel); + this.cleanUpPanel(currPanel); + + // Async'ing this as it seems to improve ending transition performance on the TV. + // Requires further investigation into its behavior. + asyncMethod(this, function () { + this.removeClass('transitioning'); + this.transitioning = false; + }); } - if (this.popQueue && this.popQueue.length) this.finalizePurge(); - this.transitioning = false; }, /** @@ -631,13 +645,14 @@ module.exports = kind( setupTransitions: function (previousIndex, animate) { var panels = this.getPanels(), nextPanel = panels[this.index], - currPanel = this._currentPanel, - trans, wTrans; + currPanel = this._currentPanel; - this._indexDirection = this.index - previousIndex; + if (previousIndex != -1) this._indexDirection = this.index - previousIndex; + else this._indexDirection = 0; if (nextPanel) { this.transitioning = true; + this.addClass('transitioning'); if (currPanel) { currPanel.set('state', States.INACTIVE); @@ -651,25 +666,27 @@ module.exports = kind( nextPanel.set('state', States.ACTIVE); if (nextPanel.preTransition) nextPanel.preTransition(); + // ensure our panel container is in the correct, pre-transition position + this.shiftContainer(-1 * this._indexDirection); + // only animate transition if there is more than one panel and/or we're animating if (animate) { - trans = 'transform ' + this.duration + 'ms ' + this.timingFunction; - wTrans = '-webkit-' + trans; - nextPanel.applyStyle('-webkit-transition', wTrans); - nextPanel.applyStyle('transition', trans); - nextPanel.addClass('transitioning'); - - if (currPanel) { - currPanel.applyStyle('-webkit-transition', wTrans); - currPanel.applyStyle('transition', trans); - currPanel.addClass('transitioning'); - } + + // set the correct state for the next panel + nextPanel.set('state', States.ACTIVATING); + nextPanel.addRemoveClass('next', this._indexDirection > 0); + nextPanel.addRemoveClass('previous', this._indexDirection < 0); + + // set the correct state for the previous panel + this._currentPanel.set('state', States.DEACTIVATING); + currPanel.addRemoveClass('previous', this._indexDirection > 0); + currPanel.addRemoveClass('next', this._indexDirection < 0); setTimeout(this.bindSafely(function () { this.applyTransitions(nextPanel); }), 16); } else { - this.transitionDirect(nextPanel); + this.applyTransitions(nextPanel, true); } } }, @@ -682,35 +699,33 @@ module.exports = kind( * @private */ applyTransitions: function (nextPanel, direct) { - var previousPanel = this._previousPanel = this._currentPanel; - - // apply the transition for the next panel - nextPanel.set('state', States.ACTIVATING); - dom.transformValue(nextPanel, 'translate' + this.orientation, '0%'); - if (this._currentPanel) { // apply the transition for the current panel - this._currentPanel.set('state', States.DEACTIVATING); - this.shiftPanel(this._currentPanel, this._indexDirection); - } + // move the panel container to its intended post-transition position + if (this._currentPanel) this.shiftContainer(this._indexDirection, true); + // update our panel references + this._previousPanel = this._currentPanel; this._currentPanel = nextPanel; - if (!this.shouldAnimate() || direct) { // ensure that `transitionFinished is called, regardless of animation - nextPanel.set('state', States.ACTIVE); - if (previousPanel) previousPanel.set('state', States.INACTIVE); - } + // ensure that `transitionFinished` is called in the case where we are not animating + if (!this.shouldAnimate() || direct) this.transitionFinished(null, null, true); }, /** * Shifts the given panel into its post-transition position. * - * @param {Object} panel - The panel to be shifted to its final position. * @param {Number} indexDirection - The direction (positive indicates forward, negative * backwards) in which we are changing the index. + * @param {Boolean} [animate] - Whether or not we want this shift to be animated. * @private */ - shiftPanel: function (panel, indexDirection) { - var value = (indexDirection > 0 ? -100 : 100) * this.direction + '%'; - dom.transformValue(panel, 'translate' + this.orientation, value); + shiftContainer: function (indexDirection, animate) { + var container = this.$.client, + value = (indexDirection > 0 ? -50 : 0) * this.direction + '%'; + + container.applyStyle('-webkit-transition', animate ? wTrans : null); + container.applyStyle('transition', animate ? trans: null); + + dom.transformValue(container, 'translate' + this.orientation, value); }, /** @@ -744,18 +759,6 @@ module.exports = kind( } }, - /** - * Transition to a given panel directly, without any animation. - * - * @param {Object} panel - The panel we are transitioning to. - * @private - */ - transitionDirect: function (panel) { - panel.applyStyle('-webkit-transition-duration', '0s'); - panel.applyStyle('transition-duration', '0s'); - this.applyTransitions(panel, true); - }, - /** * Prunes the queue of to-be-cached panels in the event that any panels in the queue have * already been instanced. diff --git a/src/ViewPreloadSupport.js b/src/ViewPreloadSupport.js index 34f55c3db..8403c2555 100644 --- a/src/ViewPreloadSupport.js +++ b/src/ViewPreloadSupport.js @@ -45,9 +45,15 @@ var ViewPreloadSupport = { sup.apply(this, arguments); if (this.cacheViews) { + // we don't want viewCache to be added to the client area, so we cache the original + // controlParent and restore it afterward + var cp = this.controlParent; + this.controlParent = null; this.createChrome([ {name: 'viewCache', kind: Control, canGenerate: false} ]); + this.controlParent = cp; + this.removeChild(this.$.viewCache); this._cachedViews = {}; } From 6ff4d6b3d5f8167c188e3cc6b284c09c8f6d5eb4 Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Fri, 28 Aug 2015 14:25:17 -0700 Subject: [PATCH 038/195] JSDoc cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/Jsonp.js | 2 +- src/WebService.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Jsonp.js b/src/Jsonp.js index 321b270f7..83d0f1840 100644 --- a/src/Jsonp.js +++ b/src/Jsonp.js @@ -154,7 +154,7 @@ var JsonpRequest = module.exports = kind( /** * Initiates the asynchronous routine and will supply the given value if it completes - * successfully. Overloaded from [enyo.Async.go()]{@link module:enyo/Async~Async#go}. + * successfully. Overloaded from {@link module:enyo/Async~Async#go}. * * @param {*} value - The value to pass to responders. * @returns {this} The callee for chaining. diff --git a/src/WebService.js b/src/WebService.js index 5813eb8a6..5a3677c21 100644 --- a/src/WebService.js +++ b/src/WebService.js @@ -65,10 +65,10 @@ var AjaxComponent = kind({ * requests. Internally, it relies on the [Async]{@link module:enyo/Async~Async} subkinds * ({@link module:enyo/Ajax~Ajax} and {@link module:enyo/Jsonp~JsonpRequest}) to manage transactions. * -* By default, {@link module:enyo/WebService~WebService} uses `enyo.Ajax` and publishes all of its +* By default, {@link module:enyo/WebService~WebService} uses `enyo/Ajax` and publishes all of its * properties via the [enyo/AjaxProperties]{@link module:enyo/AjaxProperties} module. * -* To use `enyo.JsonpRequest` instead of `enyo.Ajax`, set +* To use `enyo/Jsonp/JsonpRequest` instead of `enyo/Ajax`, set * [jsonp]{@link module:enyo/WebService~WebService#jsonp} to `true` (defaults to `false`). * * For more information, see the documentation on [Consuming Web From edb32352aae4088009808c33d463df9e36424157 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Fri, 28 Aug 2015 17:08:28 -0700 Subject: [PATCH 039/195] ENYO-2421: Prevent animating a direct transition. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/LightPanels/LightPanels.js | 47 ++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index 3dc6bd022..a04994c65 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -581,6 +581,15 @@ module.exports = kind( } }, + /** + * Determines whether or not we are removing inactive panels from DOM. + * @returns {Boolean} If `true`, inactive panels are being removed; `false` otherwise. + * @private + */ + removesPanels: function () { + return this.cacheViews || this.popOnBack || this.popOnForward; + }, + /** * When the transition has completed, we perform some clean-up work. * @@ -592,14 +601,17 @@ module.exports = kind( transitionFinished: function (sender, ev, direct) { var prevPanel, currPanel; - if (ev && ev.originator === this.$.client || direct) { + if (this.transitioning && ((ev && ev.originator === this.$.client) || direct)) { prevPanel = this._previousPanel; currPanel = this._currentPanel; if ((this._indexDirection < 0 && (this.popOnBack || this.cacheViews) && this.index < this.getPanels().length - 1) || (this._indexDirection > 0 && (this.popOnForward || this.cacheViews) && this.index > 0)) { this.popPanels(this.index, this._indexDirection); + } else if (prevPanel && !this.removesPanels()) { + prevPanel.set('showing', false); } + if (this.popQueue && this.popQueue.length) this.finalizePurge(); this.cleanUpPanel(prevPanel); @@ -659,9 +671,9 @@ module.exports = kind( if (currPanel.preTransition) currPanel.preTransition(); } - if (!nextPanel.generated) { - nextPanel.render(); - } + // prepare the panel that will be transitioned into view + if (!this.removesPanels()) nextPanel.set('showing', true); + if (!nextPanel.generated) nextPanel.render(); nextPanel.set('state', States.ACTIVE); if (nextPanel.preTransition) nextPanel.preTransition(); @@ -669,24 +681,25 @@ module.exports = kind( // ensure our panel container is in the correct, pre-transition position this.shiftContainer(-1 * this._indexDirection); - // only animate transition if there is more than one panel and/or we're animating - if (animate) { - - // set the correct state for the next panel - nextPanel.set('state', States.ACTIVATING); - nextPanel.addRemoveClass('next', this._indexDirection > 0); - nextPanel.addRemoveClass('previous', this._indexDirection < 0); + // set the correct state for the next panel + nextPanel.set('state', States.ACTIVATING); + nextPanel.addRemoveClass('next', this._indexDirection > 0); + nextPanel.addRemoveClass('previous', this._indexDirection < 0); + if (currPanel) { // set the correct state for the previous panel this._currentPanel.set('state', States.DEACTIVATING); currPanel.addRemoveClass('previous', this._indexDirection > 0); currPanel.addRemoveClass('next', this._indexDirection < 0); + } + // only animate transition if there is more than one panel and/or we're animating + if (animate) { setTimeout(this.bindSafely(function () { - this.applyTransitions(nextPanel); + this.applyTransitions(nextPanel, true); }), 16); } else { - this.applyTransitions(nextPanel, true); + this.applyTransitions(nextPanel); } } }, @@ -695,19 +708,19 @@ module.exports = kind( * Applies the transitions for moving between the current and next panel. * * @param {Object} nextPanel - The panel we are transitioning to. - * @param {Boolean} direct - If `true`, signifies that this is a direct transition. + * @param {Boolean} animate - If `true`, signifies that this is an animated transition. * @private */ - applyTransitions: function (nextPanel, direct) { + applyTransitions: function (nextPanel, animate) { // move the panel container to its intended post-transition position - if (this._currentPanel) this.shiftContainer(this._indexDirection, true); + if (this._currentPanel) this.shiftContainer(this._indexDirection, animate); // update our panel references this._previousPanel = this._currentPanel; this._currentPanel = nextPanel; // ensure that `transitionFinished` is called in the case where we are not animating - if (!this.shouldAnimate() || direct) this.transitionFinished(null, null, true); + if (!this.shouldAnimate() || !animate) this.transitionFinished(null, null, true); }, /** From e7d912d2a1704e760b5ca96b043b44a18df18747 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Mon, 31 Aug 2015 15:12:58 -0700 Subject: [PATCH 040/195] ENYO-2421: Apply proper style when animated. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/LightPanels/LightPanels.css | 5 ----- src/LightPanels/LightPanels.js | 2 ++ 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/LightPanels/LightPanels.css b/src/LightPanels/LightPanels.css index e4a400bb2..28c9a1198 100644 --- a/src/LightPanels/LightPanels.css +++ b/src/LightPanels/LightPanels.css @@ -9,11 +9,6 @@ /* Panels container styles */ -.enyo-light-panels.transitioning .panels-container { - -webkit-transform: translateZ(0); - transform: translateZ(0); -} - .enyo-light-panels .panels-container > * { display: inline-block; vertical-align: top; diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index a04994c65..b8227c267 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -622,6 +622,7 @@ module.exports = kind( asyncMethod(this, function () { this.removeClass('transitioning'); this.transitioning = false; + dom.transform(this.$.client, {translateZ: null}); }); } }, @@ -665,6 +666,7 @@ module.exports = kind( if (nextPanel) { this.transitioning = true; this.addClass('transitioning'); + dom.transform(this.$.client, {translateZ: this.animate ? 0 : null}); if (currPanel) { currPanel.set('state', States.INACTIVE); From 9002631df08bdfb3d1fc15c43a427ffd700164f4 Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Wed, 2 Sep 2015 11:51:48 -0700 Subject: [PATCH 041/195] JSDoc cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/Popup/Popup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Popup/Popup.js b/src/Popup/Popup.js index bb850b277..4b2063d35 100644 --- a/src/Popup/Popup.js +++ b/src/Popup/Popup.js @@ -120,7 +120,7 @@ var Popup = module.exports = kind( /** * Set to `true` to render the [popup]{@link module:enyo/Popup~Popup} in a - * [floating layer]{@link module:enyo/FloatingLayer~FloatingLayer} outside of other + * [floating layer]{@link module:enyo/Control/floatingLayer~FloatingLayer} outside of other * [controls]{@link module:enyo/Control~Control}. This may be used to guarantee that the * popup will be shown on top of other controls. * From fa2410ae9b770df8305d2398c547aeae11509a95 Mon Sep 17 00:00:00 2001 From: Roy Sutton Date: Fri, 7 Aug 2015 18:01:01 -0400 Subject: [PATCH 042/195] ENYO-2257 DataList/DataRepeater does not update selection with POJOs Enyo-DCO-1.1-Signed-off-by: Roy Sutton --- src/DataRepeater.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/DataRepeater.js b/src/DataRepeater.js index fc31a9595..39e5eaffc 100644 --- a/src/DataRepeater.js +++ b/src/DataRepeater.js @@ -630,6 +630,7 @@ var DataRepeater = module.exports = kind( } else { model[p] = select; + if(c) c.syncBindings({force: true, all: true}); } } this.notifyObservers('selected'); From cf37ecdad2dfbae3ff83bd76ee03ba44cb31c533 Mon Sep 17 00:00:00 2001 From: Cole Davis Date: Tue, 1 Sep 2015 17:21:40 -0700 Subject: [PATCH 043/195] allow single gulp command to correctly run jshint and mocha tests --- .gitignore | 3 +- gulpfile.js | 58 ++++++++++++++++++++++--- package.json | 28 +++++------- test/.gitignore | 1 - test/gulpfile.js | 50 --------------------- test/index.html | 37 ++++++++-------- test/tests/AccessibilitySupport.js | 6 +-- test/tests/Ajax.js | 2 +- test/tests/Animator.js | 4 +- test/tests/Application.js | 6 +-- test/tests/Async.js | 2 +- test/tests/Binding.js | 10 ++--- test/tests/BindingSupport.js | 14 +++--- test/tests/BucketFilter.js | 4 +- test/tests/Collection.js | 10 ++--- test/tests/Component.js | 12 ++--- test/tests/ComponentDispatch.js | 6 +-- test/tests/ComponentHandlers.js | 4 +- test/tests/ComputedSupport.js | 4 +- test/tests/Control.js | 6 +-- test/tests/Controller.js | 4 +- test/tests/DataGridList.js | 10 ++--- test/tests/DataList.js | 10 ++--- test/tests/DataRepeater.js | 10 ++--- test/tests/EventEmitter.js | 4 +- test/tests/Inheritence.js | 8 ++-- test/tests/Job.js | 2 +- test/tests/Jobs.js | 4 +- test/tests/Json.js | 2 +- test/tests/Kind.js | 4 +- test/tests/Model.js | 10 ++--- test/tests/ModelController.js | 10 ++--- test/tests/MultipleDispatchComponent.js | 8 ++-- test/tests/ObserverSupport.js | 8 ++-- test/tests/Option.js | 4 +- test/tests/PriorityQueue.js | 2 +- test/tests/ProgressiveFilter.js | 6 +-- test/tests/RelationalModel.js | 8 ++-- test/tests/Select.js | 4 +- test/tests/Store.js | 6 +-- test/tests/Util.js | 2 +- test/tests/ViewController.js | 8 ++-- test/tests/ViewportPositioning.js | 6 +-- test/tests/lang.js | 4 +- test/tests/ready.js | 2 +- 45 files changed, 203 insertions(+), 210 deletions(-) delete mode 100644 test/.gitignore delete mode 100644 test/gulpfile.js diff --git a/.gitignore b/.gitignore index 84a5e94b7..a7eb13116 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ lcov-report lcov.info npm-* dist -.enyocache \ No newline at end of file +.enyocache +test/dist \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index 1c227f69e..1912b83ed 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,16 +1,31 @@ 'use strict'; +var + util = require('util'); + var gulp = require('gulp'), jshint = require('gulp-jshint'), - stylish = require('jshint-stylish'); - + stylish = require('jshint-stylish'), + concat = require('gulp-concat'), + enyo = require('enyo-dev'), + through = require('through2'), + mochaPhantomJs = require('gulp-mocha-phantomjs'); +var + opts = { + package: '.', + outdir: './test/dist', + sourceMaps: false, + clean: true, + cache: false + }; -gulp.task('default', ['jshint']); +gulp.task('default', ['jshint', 'test']); gulp.task('jshint', lint); - - +gulp.task('build-lib', buildLib); +gulp.task('build-tests', buildTests); +gulp.task('test', ['build-lib', 'build-tests'], test); function lint () { return gulp @@ -18,4 +33,35 @@ function lint () { .pipe(jshint()) .pipe(jshint.reporter(stylish, {verbose: true})) .pipe(jshint.reporter('fail')); -} \ No newline at end of file +} + +function buildLib (done) { + // returns a promise that gulp should be waiting for + return enyo.package(opts); +} + +function buildTests () { + return gulp + .src('./test/tests/**/*.js') + .pipe(wrap()) + .pipe(concat('tests.js')) + .pipe(gulp.dest('./test/dist')); +} + +// since the original tests were written to be modules, for now we will simply wrap +// their content to preserve the assumed safe-context +function wrap () { + return through.obj(function (file, nil, next) { + var body, wrapped; + body = file.contents.toString('utf8'); + wrapped = util.format('(function () {\n%s\n})();\n', body); + file.contents = new Buffer(wrapped); + next(null, file); + }); +} + +function test () { + return gulp + .src('./test/index.html') + .pipe(mochaPhantomJs({reporter: 'spec', phantomjs: {useColors: true}})); +} \ No newline at end of file diff --git a/package.json b/package.json index 17aa8d921..ecb6e5d35 100644 --- a/package.json +++ b/package.json @@ -40,21 +40,17 @@ "url": "http://github.com/enyojs/enyo" }, "devDependencies": { - "browserify": "^9.0.3", - "chai": "~1.9.2", - "gulp": "^3.8.11", - "gulp-jshint": "^1.10.0", - "gulp-mocha-phantomjs": "^0.6.1", - "gulp-sourcemaps": "^1.5.0", - "jshint": "~2.5.1", - "jshint-stylish": "^1.0.1", - "mocha": "~1.20.0", - "mocha-phantomjs": "~3.4.1", - "sinon": "~1.8.1", - "sinon-chai": "~2.5.0", - "through2": "^0.6.3", - "vinyl": "^0.4.6", - "vinyl-buffer": "^1.0.0", - "vinyl-source-stream": "^1.0.0" + "chai": "^3.2.0", + "enyo-dev": "git@github.com:enyojs/enyo-dev.git#master", + "gulp": "^3.9.0", + "gulp-concat": "^2.6.0", + "gulp-jshint": "^1.11.2", + "gulp-mocha-phantomjs": "^0.9.0", + "jshint": "^2.8.0", + "jshint-stylish": "^2.0.1", + "mocha": "^2.3.0", + "sinon": "^1.16.1", + "sinon-chai": "^2.8.0", + "through2": "^0.6.5" } } diff --git a/test/.gitignore b/test/.gitignore deleted file mode 100644 index c795b054e..000000000 --- a/test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -build \ No newline at end of file diff --git a/test/gulpfile.js b/test/gulpfile.js deleted file mode 100644 index cdc4764d6..000000000 --- a/test/gulpfile.js +++ /dev/null @@ -1,50 +0,0 @@ -'use strict'; - -var - fs = require('fs'), - path = require('path'); - -var - gulp = require('gulp'), - browserify = require('browserify'), - source = require('vinyl-source-stream'), - buffer = require('vinyl-buffer'), - mochaPhantomJs = require('gulp-mocha-phantomjs'); - - - -gulp.task('build', buildTests); -gulp.task('run', ['build'], runTests); -gulp.task('default', ['build', 'run']); - - - - - -function buildTests () { - var - bundle = browserify(), - tests = getTests(); - // add each of the tests we found to the bundle as an entry without having one hard-coded - tests.forEach(function (file) { bundle.add(file); }); - // the files in the library all require enyo but that is for building applications and - // is only useful because of style ordering... - bundle.ignore('enyo'); - return bundle - .bundle() - .pipe(source('tests.js')) - .pipe(buffer()) - .pipe(gulp.dest('./build')); -} - -function getTests () { - return fs.readdirSync(path.join(__dirname, './tests')) - .filter(function (file) { return path.extname(file) == '.js'; }) - .map(function (file) { return path.join(__dirname, './tests/', file); }); -} - -function runTests () { - return gulp - .src(path.join(__dirname, './index.html')) - .pipe(mochaPhantomJs()); -} \ No newline at end of file diff --git a/test/index.html b/test/index.html index 39da099c2..a7a7c15e0 100644 --- a/test/index.html +++ b/test/index.html @@ -7,6 +7,7 @@ +
@@ -15,27 +16,27 @@ - + + diff --git a/test/tests/AccessibilitySupport.js b/test/tests/AccessibilitySupport.js index 866ed5a40..163a3789a 100644 --- a/test/tests/AccessibilitySupport.js +++ b/test/tests/AccessibilitySupport.js @@ -1,9 +1,9 @@ var - kind = require('../../lib/kind'); + kind = require('enyo/kind'); var - AccessibilitySupport = require('../../lib/AccessibilitySupport') - Control = require('../../lib/Control'); + AccessibilitySupport = require('enyo/AccessibilitySupport') + Control = require('enyo/Control'); describe('AccessibilitySupport', function () { diff --git a/test/tests/Ajax.js b/test/tests/Ajax.js index a8564a42a..70608a0a3 100644 --- a/test/tests/Ajax.js +++ b/test/tests/Ajax.js @@ -1,5 +1,5 @@ var - Ajax = require('../../lib/Ajax'); + Ajax = require('enyo/Ajax'); describe('Ajax', function () { diff --git a/test/tests/Animator.js b/test/tests/Animator.js index 9ac4fc26a..8d0443344 100644 --- a/test/tests/Animator.js +++ b/test/tests/Animator.js @@ -1,6 +1,6 @@ var - Animator = require('../../lib/Animator'), - Component = require('../../lib/Component'); + Animator = require('enyo/Animator'), + Component = require('enyo/Component'); describe('Animator', function () { diff --git a/test/tests/Application.js b/test/tests/Application.js index 721c93362..1ac41e003 100644 --- a/test/tests/Application.js +++ b/test/tests/Application.js @@ -1,6 +1,6 @@ -var kind = require('../../lib/kind'), - Application = require('../../lib/Application'), - Router = require('../../lib/Router'); +var kind = require('enyo/kind'), + Application = require('enyo/Application'), + Router = require('enyo/Router'); describe('Application', function () { diff --git a/test/tests/Async.js b/test/tests/Async.js index 52be7f928..5714d9148 100644 --- a/test/tests/Async.js +++ b/test/tests/Async.js @@ -1,5 +1,5 @@ var - Async = require('../../lib/Async'); + Async = require('enyo/Async'); describe('Async', function () { diff --git a/test/tests/Binding.js b/test/tests/Binding.js index 0dd10522b..87d9e7cc1 100644 --- a/test/tests/Binding.js +++ b/test/tests/Binding.js @@ -1,12 +1,12 @@ var - kind = require('../../lib/kind'), - utils = require('../../lib/utils'); + kind = require('enyo/kind'), + utils = require('enyo/utils'); var - Binding = require('../../lib/Binding'), + Binding = require('enyo/Binding'), PassiveBinding = Binding.PassiveBinding, - CoreObject = require('../../lib/CoreObject'), - Component = require('../../lib/Component'); + CoreObject = require('enyo/CoreObject'), + Component = require('enyo/Component'); var proto; diff --git a/test/tests/BindingSupport.js b/test/tests/BindingSupport.js index 0c620c05c..63f03c9d8 100644 --- a/test/tests/BindingSupport.js +++ b/test/tests/BindingSupport.js @@ -1,13 +1,13 @@ var - kind = require('../../lib/kind'); + kind = require('enyo/kind'); var - BindingSupport = require('../../lib/BindingSupport'), - Binding = require('../../lib/Binding'), - CoreObject = require('../../lib/CoreObject'), - Component = require('../../lib/Component'), - Control = require('../../lib/Control'), - Model = require('../../lib/Model'); + BindingSupport = require('enyo/BindingSupport'), + Binding = require('enyo/Binding'), + CoreObject = require('enyo/CoreObject'), + Component = require('enyo/Component'), + Control = require('enyo/Control'), + Model = require('enyo/Model'); describe ("BindingSupport Mixin", function () { describe ("Methods", function () { diff --git a/test/tests/BucketFilter.js b/test/tests/BucketFilter.js index a11ad6424..9e10b61ed 100644 --- a/test/tests/BucketFilter.js +++ b/test/tests/BucketFilter.js @@ -1,6 +1,6 @@ var - BucketFilter = require('../../lib/BucketFilter'), - Collection = require('../../lib/Collection'); + BucketFilter = require('enyo/BucketFilter'), + Collection = require('enyo/Collection'); describe('BucketFilter', function () { diff --git a/test/tests/Collection.js b/test/tests/Collection.js index fb77ec6ee..ea685da56 100644 --- a/test/tests/Collection.js +++ b/test/tests/Collection.js @@ -1,9 +1,9 @@ var - Collection = require('../../lib/Collection'), - Model = require('../../lib/Model'), - States = require('../../lib/States'), - Source = require('../../lib/Source'), - Store = require('../../lib/Store'); + Collection = require('enyo/Collection'), + Model = require('enyo/Model'), + States = require('enyo/States'), + Source = require('enyo/Source'), + Store = require('enyo/Store'); describe('Collection', function () { diff --git a/test/tests/Component.js b/test/tests/Component.js index 4a231d80e..60975fbe7 100644 --- a/test/tests/Component.js +++ b/test/tests/Component.js @@ -1,9 +1,9 @@ -var kind = require('../../lib/kind'), - jobs = require('../../lib/jobs'), - Component = require('../../lib/Component'), - Control = require('../../lib/Control'), - Anchor = require('../../lib/Anchor'), - Button = require('../../lib/Button'); +var kind = require('enyo/kind'), + jobs = require('enyo/jobs'), + Component = require('enyo/Component'), + Control = require('enyo/Control'), + Anchor = require('enyo/Anchor'), + Button = require('enyo/Button'); describe('Component', function () { diff --git a/test/tests/ComponentDispatch.js b/test/tests/ComponentDispatch.js index d606d2417..664ecc5c9 100644 --- a/test/tests/ComponentDispatch.js +++ b/test/tests/ComponentDispatch.js @@ -1,7 +1,7 @@ var - kind = require('../../lib/kind'), - Component = require('../../lib/Component'), - Control = require('../../lib/Control'); + kind = require('enyo/kind'), + Component = require('enyo/Component'), + Control = require('enyo/Control'); describe('Component dispatch', function () { diff --git a/test/tests/ComponentHandlers.js b/test/tests/ComponentHandlers.js index ea2754dc6..1d24041f0 100644 --- a/test/tests/ComponentHandlers.js +++ b/test/tests/ComponentHandlers.js @@ -1,6 +1,6 @@ var - kind = require('../../lib/kind'), - Component = require('../../lib/Component'); + kind = require('enyo/kind'), + Component = require('enyo/Component'); describe('Component Handlers', function () { diff --git a/test/tests/ComputedSupport.js b/test/tests/ComputedSupport.js index 661a70d10..8ee6cf7f1 100644 --- a/test/tests/ComputedSupport.js +++ b/test/tests/ComputedSupport.js @@ -1,8 +1,8 @@ var - kind = require('../../lib/kind'); + kind = require('enyo/kind'); var - ComputedSupport = require('../../lib/ComputedSupport'); + ComputedSupport = require('enyo/ComputedSupport'); describe ('ComputedSupport Mixin', function () { diff --git a/test/tests/Control.js b/test/tests/Control.js index 27ce01682..1d2f80405 100644 --- a/test/tests/Control.js +++ b/test/tests/Control.js @@ -1,9 +1,9 @@ var - kind = require('../../lib/kind'); + kind = require('enyo/kind'); var - Control = require('../../lib/Control'), - UiComponent = require('../../lib/UiComponent'); + Control = require('enyo/Control'), + UiComponent = require('enyo/UiComponent'); describe('Control', function () { diff --git a/test/tests/Controller.js b/test/tests/Controller.js index ad68d7ec9..620a96b6b 100644 --- a/test/tests/Controller.js +++ b/test/tests/Controller.js @@ -1,5 +1,5 @@ -var kind = require('../../lib/kind'), - Controller = require('../../lib/Controller'); +var kind = require('enyo/kind'), + Controller = require('enyo/Controller'); describe('Controller', function () { diff --git a/test/tests/DataGridList.js b/test/tests/DataGridList.js index 4a4519dc5..f21adc429 100644 --- a/test/tests/DataGridList.js +++ b/test/tests/DataGridList.js @@ -1,11 +1,11 @@ var - kind = require('../../lib/kind'); + kind = require('enyo/kind'); var - Collection = require('../../lib/Collection'), - Model = require('../../lib/Model'), - DataGridList = require('../../lib/DataGridList'), - CoreObject = require('../../lib/CoreObject'); + Collection = require('enyo/Collection'), + Model = require('enyo/Model'), + DataGridList = require('enyo/DataGridList'), + CoreObject = require('enyo/CoreObject'); describe('DataGridList', function () { diff --git a/test/tests/DataList.js b/test/tests/DataList.js index 965e43fb3..e1cd7b82f 100644 --- a/test/tests/DataList.js +++ b/test/tests/DataList.js @@ -1,11 +1,11 @@ var - kind = require('../../lib/kind'); + kind = require('enyo/kind'); var - DataList = require('../../lib/DataList'), - Collection = require('../../lib/Collection'), - CoreObject = require('../../lib/CoreObject'), - Model = require('../../lib/Model'); + DataList = require('enyo/DataList'), + Collection = require('enyo/Collection'), + CoreObject = require('enyo/CoreObject'), + Model = require('enyo/Model'); var proto = DataList.prototype; diff --git a/test/tests/DataRepeater.js b/test/tests/DataRepeater.js index 5580f626b..56c7edf51 100644 --- a/test/tests/DataRepeater.js +++ b/test/tests/DataRepeater.js @@ -1,11 +1,11 @@ var - kind = require('../../lib/kind'); + kind = require('enyo/kind'); var - Collection = require('../../lib/Collection'), - Model = require('../../lib/Model'), - DataRepeater = require('../../lib/DataRepeater'), - CoreObject = require('../../lib/CoreObject'); + Collection = require('enyo/Collection'), + Model = require('enyo/Model'), + DataRepeater = require('enyo/DataRepeater'), + CoreObject = require('enyo/CoreObject'); describe('DataRepeater', function () { diff --git a/test/tests/EventEmitter.js b/test/tests/EventEmitter.js index 16446c555..ef0bc2532 100644 --- a/test/tests/EventEmitter.js +++ b/test/tests/EventEmitter.js @@ -1,6 +1,6 @@ var - EventEmitter = require('../../lib/EventEmitter'), - CoreObject = require('../../lib/CoreObject'); + EventEmitter = require('enyo/EventEmitter'), + CoreObject = require('enyo/CoreObject'); // TODO: Someday add tests that test with default context and emit with a specified object to emit to describe('EventEmitter', function () { diff --git a/test/tests/Inheritence.js b/test/tests/Inheritence.js index bbad3fde2..bf519425b 100644 --- a/test/tests/Inheritence.js +++ b/test/tests/Inheritence.js @@ -1,7 +1,7 @@ -var CoreObject = require('../../lib/CoreObject'), - Component = require('../../lib/Component'), - UiComponent = require('../../lib/UiComponent'), - Control = require('../../lib/Control'); +var CoreObject = require('enyo/CoreObject'), + Component = require('enyo/Component'), + UiComponent = require('enyo/UiComponent'), + Control = require('enyo/Control'); describe('Inherentence Sanity Tests', function () { diff --git a/test/tests/Job.js b/test/tests/Job.js index 8d0e8caa5..ab43469f4 100644 --- a/test/tests/Job.js +++ b/test/tests/Job.js @@ -1,5 +1,5 @@ var - job = require('../../lib/job'); + job = require('enyo/job'); describe('Job', function () { diff --git a/test/tests/Jobs.js b/test/tests/Jobs.js index e3d1ef837..78c14a3b4 100644 --- a/test/tests/Jobs.js +++ b/test/tests/Jobs.js @@ -1,6 +1,6 @@ var - utils = require('../../lib/utils'), - jobs = require('../../lib/jobs'); + utils = require('enyo/utils'), + jobs = require('enyo/jobs'); describe('Jobs', function () { diff --git a/test/tests/Json.js b/test/tests/Json.js index 9d17eb879..9088e6758 100644 --- a/test/tests/Json.js +++ b/test/tests/Json.js @@ -1,5 +1,5 @@ var - json = require('../../lib/json'); + json = require('enyo/json'); describe('JSON', function () { diff --git a/test/tests/Kind.js b/test/tests/Kind.js index cf6d989b1..49e66e5d4 100644 --- a/test/tests/Kind.js +++ b/test/tests/Kind.js @@ -1,6 +1,6 @@ var - kind = require('../../lib/kind'), - utils = require('../../lib/utils'); + kind = require('enyo/kind'), + utils = require('enyo/utils'); describe('Kind', function () { diff --git a/test/tests/Model.js b/test/tests/Model.js index 0dd479e9e..7bba5432e 100644 --- a/test/tests/Model.js +++ b/test/tests/Model.js @@ -1,11 +1,11 @@ var - kind = require('../../lib/kind'); + kind = require('enyo/kind'); var - Model = require('../../lib/Model'), - States = require('../../lib/States'), - Source = require('../../lib/Source'), - Store = require('../../lib/Store'); + Model = require('enyo/Model'), + States = require('enyo/States'), + Source = require('enyo/Source'), + Store = require('enyo/Store'); describe('Model', function () { diff --git a/test/tests/ModelController.js b/test/tests/ModelController.js index 1de2825dc..74ea8e02f 100644 --- a/test/tests/ModelController.js +++ b/test/tests/ModelController.js @@ -1,11 +1,11 @@ var - kind = require('../../lib/kind'); + kind = require('enyo/kind'); var - ModelController = require('../../lib/ModelController'), - Model = require('../../lib/Model'), - RelationalModel = require('../../lib/RelationalModel'), - CoreObject = require('../../lib/CoreObject'); + ModelController = require('enyo/ModelController'), + Model = require('enyo/Model'), + RelationalModel = require('enyo/RelationalModel'), + CoreObject = require('enyo/CoreObject'); describe('ModelController', function () { diff --git a/test/tests/MultipleDispatchComponent.js b/test/tests/MultipleDispatchComponent.js index 3f3432f81..50a8005ed 100644 --- a/test/tests/MultipleDispatchComponent.js +++ b/test/tests/MultipleDispatchComponent.js @@ -1,10 +1,10 @@ var - kind = require('../../lib/kind'), - utils = require('../../lib/utils'); + kind = require('enyo/kind'), + utils = require('enyo/utils'); var - Component = require('../../lib/Component'), - MultipleDispatchComponent = require('../../lib/MultipleDispatchComponent'); + Component = require('enyo/Component'), + MultipleDispatchComponent = require('enyo/MultipleDispatchComponent'); describe('MultipleDispatch', function () { diff --git a/test/tests/ObserverSupport.js b/test/tests/ObserverSupport.js index f221125fc..0888034aa 100644 --- a/test/tests/ObserverSupport.js +++ b/test/tests/ObserverSupport.js @@ -1,10 +1,10 @@ var - kind = require('../../lib/kind'), - utils = require('../../lib/utils'); + kind = require('enyo/kind'), + utils = require('enyo/utils'); var - CoreObject = require('../../lib/CoreObject'), - Control = require('../../lib/Control'); + CoreObject = require('enyo/CoreObject'), + Control = require('enyo/Control'); describe ("ObserverSupport Mixin", function () { describe ("methods", function () { diff --git a/test/tests/Option.js b/test/tests/Option.js index 86d812b94..8aa8a0dcb 100644 --- a/test/tests/Option.js +++ b/test/tests/Option.js @@ -1,8 +1,8 @@ var - kind = require('../../lib/kind'); + kind = require('enyo/kind'); var - Select = require('../../lib/Select'); + Select = require('enyo/Select'); describe('Option', function () { diff --git a/test/tests/PriorityQueue.js b/test/tests/PriorityQueue.js index 5685b86c8..0d6cdfd6a 100644 --- a/test/tests/PriorityQueue.js +++ b/test/tests/PriorityQueue.js @@ -1,5 +1,5 @@ var - PriorityQueue = require('../../lib/PriorityQueue'); + PriorityQueue = require('enyo/PriorityQueue'); describe('PriorityQueue', function () { diff --git a/test/tests/ProgressiveFilter.js b/test/tests/ProgressiveFilter.js index b36dacce2..5cf282f95 100644 --- a/test/tests/ProgressiveFilter.js +++ b/test/tests/ProgressiveFilter.js @@ -1,8 +1,8 @@ var - kind = require('../../lib/kind'); + kind = require('enyo/kind'); var - ProgressiveFilter = require('../../lib/ProgressiveFilter'), - Collection = require('../../lib/Collection'); + ProgressiveFilter = require('enyo/ProgressiveFilter'), + Collection = require('enyo/Collection'); describe('ProgressiveFilter', function () { diff --git a/test/tests/RelationalModel.js b/test/tests/RelationalModel.js index 3efabce87..2e29920b8 100644 --- a/test/tests/RelationalModel.js +++ b/test/tests/RelationalModel.js @@ -1,9 +1,9 @@ var - kind = require('../../lib/kind'); + kind = require('enyo/kind'); var - RelationalModel = require('../../lib/RelationalModel'), - Collection = require('../../lib/Collection'), - CoreObject = require('../../lib/CoreObject'), + RelationalModel = require('enyo/RelationalModel'), + Collection = require('enyo/Collection'), + CoreObject = require('enyo/CoreObject'), toOne = RelationalModel.toOne, toMany = RelationalModel.toMany, manyToMany = RelationalModel.manyToMany; diff --git a/test/tests/Select.js b/test/tests/Select.js index 051666a97..10bbe51fe 100644 --- a/test/tests/Select.js +++ b/test/tests/Select.js @@ -1,8 +1,8 @@ var - kind = require('../../lib/kind'); + kind = require('enyo/kind'); var - Select = require('../../lib/Select'); + Select = require('enyo/Select'); describe('Select', function () { describe('properties', function () { diff --git a/test/tests/Store.js b/test/tests/Store.js index d0f39a2f3..d2ffed39f 100644 --- a/test/tests/Store.js +++ b/test/tests/Store.js @@ -1,9 +1,9 @@ var - kind = require('../../lib/kind'); + kind = require('enyo/kind'); var - Store = require('../../lib/Store'), - Model = require('../../lib/Model'); + Store = require('enyo/Store'), + Model = require('enyo/Model'); describe('Store', function () { diff --git a/test/tests/Util.js b/test/tests/Util.js index e62429d42..5e9011496 100644 --- a/test/tests/Util.js +++ b/test/tests/Util.js @@ -1,5 +1,5 @@ var - utils = require('../../lib/utils'); + utils = require('enyo/utils'); describe('Utilities', function () { diff --git a/test/tests/ViewController.js b/test/tests/ViewController.js index db7105cd0..1633d3900 100644 --- a/test/tests/ViewController.js +++ b/test/tests/ViewController.js @@ -1,10 +1,10 @@ var - kind = require('../../lib/kind'), - utils = require('../../lib/utils'); + kind = require('enyo/kind'), + utils = require('enyo/utils'); var - Control = require('../../lib/Control'); - ViewController = require('../../lib/ViewController'); + Control = require('enyo/Control'); + ViewController = require('enyo/ViewController'); describe('ViewController', function () { diff --git a/test/tests/ViewportPositioning.js b/test/tests/ViewportPositioning.js index 736585c6b..6f1c9f932 100644 --- a/test/tests/ViewportPositioning.js +++ b/test/tests/ViewportPositioning.js @@ -1,6 +1,6 @@ -var kind = require('../../lib/kind'); -var Control = require('../../lib/Control'); -var dom = require('../../lib/dom'); +var kind = require('enyo/kind'); +var Control = require('enyo/Control'); +var dom = require('enyo/dom'); describe('Meauring Viewport Positioning', function () { diff --git a/test/tests/lang.js b/test/tests/lang.js index 6149fb0c2..687452dfd 100644 --- a/test/tests/lang.js +++ b/test/tests/lang.js @@ -1,6 +1,6 @@ var - utils = require('../../lib/utils'), - kind = require('../../lib/kind'); + utils = require('enyo/utils'), + kind = require('enyo/kind'); describe('language', function () { diff --git a/test/tests/ready.js b/test/tests/ready.js index 011377c84..5c0f59d8e 100644 --- a/test/tests/ready.js +++ b/test/tests/ready.js @@ -1,5 +1,5 @@ var - ready = require('../../lib/ready'); + ready = require('enyo/ready'); describe('ready', function () { From cb3c812eb386315c4cb7cc7a960a4256fce52665 Mon Sep 17 00:00:00 2001 From: Cole Davis Date: Tue, 1 Sep 2015 17:28:10 -0700 Subject: [PATCH 044/195] allow poor travis to find enyo-dev... --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ecb6e5d35..437993b92 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ }, "devDependencies": { "chai": "^3.2.0", - "enyo-dev": "git@github.com:enyojs/enyo-dev.git#master", + "enyo-dev": "https://github.com/enyojs/enyo-dev.git#master", "gulp": "^3.9.0", "gulp-concat": "^2.6.0", "gulp-jshint": "^1.11.2", From 385018688fd6051452577ba55d2aaa11db79c6ee Mon Sep 17 00:00:00 2001 From: Cole Davis Date: Tue, 1 Sep 2015 17:39:01 -0700 Subject: [PATCH 045/195] since beta of enyo-dev@0.5.1 is available now, use that instead to see if travis is happier --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 437993b92..a899b0142 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ }, "devDependencies": { "chai": "^3.2.0", - "enyo-dev": "https://github.com/enyojs/enyo-dev.git#master", + "enyo-dev": "^0.5.1", "gulp": "^3.9.0", "gulp-concat": "^2.6.0", "gulp-jshint": "^1.11.2", From 72e8f50b6c6adb28695135322c23fe05a7fdff36 Mon Sep 17 00:00:00 2001 From: Cole Davis Date: Tue, 1 Sep 2015 17:41:07 -0700 Subject: [PATCH 046/195] update node version --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b4d1573b7..baef169a7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - "0.10" + - "0.12" cache: directories: - $HOME/.npm \ No newline at end of file From 1a632248d4c1cc679b6a732e265a82908db0dd3c Mon Sep 17 00:00:00 2001 From: Cole Davis Date: Tue, 1 Sep 2015 17:42:40 -0700 Subject: [PATCH 047/195] update to use travis container --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index baef169a7..065e17d82 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: node_js +sudo: false node_js: - "0.12" cache: From 68c74b50c15473bcc4d67c768bcd44fdd2c26138 Mon Sep 17 00:00:00 2001 From: Cole Davis Date: Wed, 2 Sep 2015 13:31:10 -0700 Subject: [PATCH 048/195] temporarily remove unit tests from travis (but allow manual running) until fixes have been made for issues --- gulpfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index 1912b83ed..d9147f2de 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -21,7 +21,7 @@ var cache: false }; -gulp.task('default', ['jshint', 'test']); +gulp.task('default', ['jshint']); gulp.task('jshint', lint); gulp.task('build-lib', buildLib); gulp.task('build-tests', buildTests); From 43c900b8b7ee01969907893f3476a37657f3b8ef Mon Sep 17 00:00:00 2001 From: Ryan Duffy Date: Wed, 2 Sep 2015 15:26:50 -0500 Subject: [PATCH 049/195] add tabIndex support Rather than forcing a tabindex of 0 for any control with content or aria-label, add the tabIndex property which maps to the tabindex attribute. This will allow saner keyboard navigation rather than virtually everything being navigable via tab. Issue: ENYO-2430 Enyo-DCO-1.1-Signed-off-by: Ryan Duffy (ryan.duffy@lge.com) --- src/AccessibilitySupport/AccessibilitySupport.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/AccessibilitySupport/AccessibilitySupport.js b/src/AccessibilitySupport/AccessibilitySupport.js index bf43e1103..61e653be2 100644 --- a/src/AccessibilitySupport/AccessibilitySupport.js +++ b/src/AccessibilitySupport/AccessibilitySupport.js @@ -10,6 +10,7 @@ var utils = require('../utils'); var defaultObservers = [ + {from: 'tabIndex', to: 'tabindex'}, {from: 'accessibilityDisabled', method: function () { this.setAriaAttribute('aria-hidden', this.accessibilityDisabled ? 'true' : null); }}, @@ -21,15 +22,13 @@ var defaultObservers = [ var role = this.accessibilityAlert && 'alert' || this.accessibilityRole || null; this.setAriaAttribute('role', role); }}, - {path: ['ariaContent', 'accessibilityHint', 'accessibilityLabel'], method: function () { - var focusable = this.accessibilityLabel || this.content || this.accessibilityHint || null, - prefix = this.accessibilityLabel || this.content || null, + {path: ['content', 'accessibilityHint', 'accessibilityLabel'], method: function () { + var prefix = this.accessibilityLabel || this.content || null, label = this.accessibilityHint && prefix && (prefix + ' ' + this.accessibilityHint) || this.accessibilityHint || this.accessibilityLabel || null; - this.setAriaAttribute('tabindex', focusable ? 0 : null); this.setAriaAttribute('aria-label', label); }} ]; @@ -75,7 +74,8 @@ function registerAriaUpdate (obj) { } function toAriaAttribute (from, to) { - this.setAriaAttribute(to, this[from]); + var value = this[from]; + this.setAriaAttribute(to, value === undefined ? null : value); } function staticToAriaAttribute (to, value) { From eedbb7eb5b68b029a1de2be78ea22155346365d9 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Wed, 2 Sep 2015 16:46:23 -0700 Subject: [PATCH 050/195] ENYO-2442: Preserve panel animation in RTL mode. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/LightPanels/LightPanels.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LightPanels/LightPanels.css b/src/LightPanels/LightPanels.css index e4a400bb2..82ed3f088 100644 --- a/src/LightPanels/LightPanels.css +++ b/src/LightPanels/LightPanels.css @@ -5,6 +5,7 @@ bottom: 0; right: 0; overflow: hidden; + direction: ltr; } /* Panels container styles */ From c363f52bc53be17bbf2eab329ff353376e1d5c06 Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Thu, 3 Sep 2015 13:25:39 -0700 Subject: [PATCH 051/195] JSDoc cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/Model.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Model.js b/src/Model.js index da16979ac..f62b52a92 100644 --- a/src/Model.js +++ b/src/Model.js @@ -240,9 +240,9 @@ var Model = module.exports = kind( }, /** - * The current [state(s)]{@link module:enyo/States~States} possessed by the [model]{@link module:enyo/Model~Model}. + * The current [state(s)]{@link module:enyo/States} possessed by the [model]{@link module:enyo/Model~Model}. * There are limitations as to which state(s) the model may possess at any given time. - * By default, a model is [NEW]{@link module:enyo/States#NEW} and [CLEAN]{@link module:enyo/States~States.CLEAN}. + * By default, a model is [NEW]{@link module:enyo/States#NEW} and [CLEAN]{@link module:enyo/States#CLEAN}. * Note that this is **not** a [bindable]{@link module:enyo/BindingSupport~BindingSupport} property. * * @see module:enyo/States~States @@ -267,12 +267,12 @@ var Model = module.exports = kind( /** * Inspects and restructures incoming data prior to [setting]{@link module:enyo/Model~Model#set} it on * the [model]{@link module:enyo/Model~Model}. While this method may be called directly, it is most - * often used via the [parse]{@link module:enyo/Model~Model~Options.parse} option and executed + * often used via the [parse]{@link module:enyo/Model~Model~Options#parse} option and executed * automatically, either during initialization or when [fetched]{@link module:enyo/Model~Model#fetch} * (or, in some cases, both). This is a virtual method and must be provided to suit a * given implementation's needs. * - * @see module:enyo/Model~Model~Options.parse + * @see module:enyo/Model~Model~Options#parse * @param {*} data - The incoming data that may need to be restructured or reduced prior to * being [set]{@link module:enyo/Model~Model#set} on the [model]{@link module:enyo/Model~Model}. * @returns {Object} The [hash]{@glossary Object} to apply to the @@ -557,7 +557,7 @@ var Model = module.exports = kind( /** * Retrieves the value for the given property or path. If the property is a - * [computed property]{@link module:enyo/ComputedSupport~ComputedSupport.computed}, then it will return + * [computed property]{@link module:enyo/ComputedSupport}, then it will return * that value; otherwise, it will attempt to retrieve the value from the * [attributes hash]{@link module:enyo/Model~Model#attributes}. * From d85bc70ce9b569d195cdc59bfb4fa4e288c57eeb Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Thu, 3 Sep 2015 13:50:32 -0700 Subject: [PATCH 052/195] JSDoc and inline code sample cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/RelationalModel/RelationalModel.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/RelationalModel/RelationalModel.js b/src/RelationalModel/RelationalModel.js index a15ea2d1b..081f027c5 100644 --- a/src/RelationalModel/RelationalModel.js +++ b/src/RelationalModel/RelationalModel.js @@ -1,5 +1,5 @@ /** - * Contains the declaration for the {@link module:enyo/RelationalModel} kind. + * Contains the declaration for the {@link module:enyo/RelationalModel~RelationalModel} kind. * @module enyo/RelationalModel */ @@ -51,9 +51,13 @@ var RelationalModel = module.exports = kind( * local [attribute]{@link module:enyo/Model~Model#attributes}. For example: * * ```javascript - * enyo.kind({ + * var + * kind = require('enyo/kind'), + * RelationalModel = require('enyo/RelationalModel'); + * + * module.exports = kind({ * name: 'Person', - * kind: enyo.RelationalModel, + * kind: RelationalModel, * relations: [ * { * key: 'nicknames', From 0c0a700fd55e18c4d2eff9076f4c2676f705342b Mon Sep 17 00:00:00 2001 From: Ankur Mishra Date: Fri, 4 Sep 2015 20:40:25 +0530 Subject: [PATCH 053/195] User interaction based changes to core Enyo-DCO-1.1-Signed-off-by: Ankur Mishra --- .../AnimationInterfaceSupport.js | 298 ++++++++++++++++++ lib/AnimationSupport/AnimationSupport.js | 4 +- lib/AnimationSupport/Core.js | 29 ++ lib/AnimationSupport/Fadeable.js | 39 ++- lib/AnimationSupport/Flippable.js | 23 +- lib/AnimationSupport/Slideable.js | 14 +- 6 files changed, 393 insertions(+), 14 deletions(-) create mode 100644 lib/AnimationSupport/AnimationInterfaceSupport.js diff --git a/lib/AnimationSupport/AnimationInterfaceSupport.js b/lib/AnimationSupport/AnimationInterfaceSupport.js new file mode 100644 index 000000000..0512006be --- /dev/null +++ b/lib/AnimationSupport/AnimationInterfaceSupport.js @@ -0,0 +1,298 @@ +require('enyo'); + +var + kind = require('../kind'), + animator = require('./Core'), + frame = require('./Frame'), + utils = require('../utils'), + dispatcher = require('../dispatcher'); + +var extend = kind.statics.extend; + +kind.concatenated.push('animation'); + +var AnimationInterfaceSupport = { + + /** + * @private + */ + patterns: [], + + /** + * @private + */ + checkX: 0, + + /** + * @private + */ + checkY: 0, + + /** + * @private + */ + deltaX: 0, + + /** + * @private + */ + deltaY: 0, + + /** + * @private + */ + translateX: 0, + + /** + * @private + */ + translateY: 0, + + /** + * @private + */ + scrollValue: 0, + + /** + * @private + */ + deltaValueX: 0, + + /** + * @private + */ + deltaValueY: 0, + + /** + * @private + */ + checkDragStartX: 0, + + /** + * @private + */ + checkDragStartY: 0, + + /** + * @private + */ + deltaDragValueX: 0, + + /** + * @private + */ + deltaDragValueY: 0, + + /** + * @private + */ + eventArray: [ + "dragstart", + "dragend", + "drag", + "flick", + "down", + "move", + "scroll", + "mousewheel", + "touchstart", + "touchmove", + "touchend" + ], + + /** + * @public + */ + initialize: function () { + var i, eventArrayLength = this.eventArray.length; + for (i = 0; i < eventArrayLength; i++) { + dispatcher.listen(this.node, this.eventArray[i], this.bindSafely(this.detectTheEvent) ); + } + }, + + detectTheEvent: function (inSender, inEvent) { + var eventType = inSender.type; + switch (eventType) { + case "dragstart": + this.touchDragStart(inSender, inEvent, inSender.pageX, inSender.pageY); + break; + case "drag": + this.touchDragMove(inSender, inEvent, inSender.pageX, inSender.pageY); + break; + case "dragend": + this.touchDragEnd(inSender, inEvent); + break; + case "flick": + this.handleMyEvent(inSender, inEvent); + break; + case "down": + this.handleMyEvent(inSender, inEvent); + break; + case "move": + this.handleMyEvent(inSender, inEvent); + break; + case "scroll": + this.scrollEvent(inSender, inEvent); + break; + case "mousewheel": + this.mousewheelEvent(inSender, inEvent); + break; + case "touchstart": + this.touchDragStart(inSender, inEvent, inSender.targetTouches[0].pageX, inSender.targetTouches[0].pageY); + break; + case "touchmove": + this.touchDragMove(inSender, inEvent, inSender.targetTouches[0].pageX, inSender.targetTouches[0].pageY); + break; + case "touchend": + this.touchDragEnd(inSender, inEvent); + break; + default: + this.handleMyEvent(inSender, inEvent); + } + }, + + touchDragStart: function (inSender, inEvent, x, y) { + this.checkX = x; + this.checkY = y; + }, + + touchDragMove: function (inSender, inEvent, x, y) { + var currentX = x, + currentY = y; + + if(currentX != 0 || currentY != 0) { + this.deltaValueX = this.checkX - currentX; + this.deltaValueY = this.checkY - currentY; + + var finalX = -1 * this.deltaValueX, + finalY = -1 * this.deltaValueY; + + //call commonTasks function with delta values + this.commonTasks(this.deltaValueX, this.deltaValueX, this.deltaValueY); + } + }, + + touchDragEnd: function (inSender, inEvent) { + this.checkX = 0; + this.checkY = 0; + this.deltaValueX = 0; + this.deltaValueY = 0; + }, + + /** + * @public + */ + scrollEvent: function (inSender, inEvent) { + var delta = inSender.deltaY, + scrollTop = inSender.target.scrollTop, + scrollLeft = inSender.target.scrollLeft; + + if (this.scrollValue === 0) { + this.scrollValue = inSender.target.scrollTop; + } + + delta = inSender.target.scrollTop - this.scrollValue; + + this.deltaX = scrollLeft - this.deltaX; + this.deltaY = scrollTop - this.deltaY; + this.scrollValue = scrollTop; + + //call commonTasks function with delta values + this.commonTasks(delta, (-1 * this.deltaX), (-1 * this.deltaY)); + + this.deltaX = scrollLeft; + this.deltaY = scrollTop; + }, + + /** + * @public + */ + mousewheelEvent: function (inSender, inEvent) { + var delta = inSender.deltaY, + deltaX = inSender.wheelDeltaX, + deltaY = inSender.wheelDeltaY; + + //call commonTasks function with delta values + this.commonTasks(delta, (-1 * deltaX), (-1 * deltaY)); + }, + + /** + * @public + */ + commonTasks: function (delta, deltax, deltay) { + if (delta !== 0) { + delta = delta / Math.abs(delta); + } + + this.translateX = this.translateX - deltax; + this.translateY = this.translateY - deltay; + + //Call specific interface + if (patterns[0].name ==="Fadeable") { + patterns[0].fadeByDelta.call(this, delta); + } else if (patterns[0].name === "Flippable") { + patterns[0].doFlip.call(this, delta); + } else if (patterns[0].name === "Slideable") { + patterns[0].slide.call(this, {translate: this.translateX + ", " + this.translateY + ", 0"} ); + } + }, + + /** + * @public + */ + handleMyEvent: function (inSender, inEvent) { + /*TODO:*/ + }, + + /** + * @public + */ + commitAnimation: function () { + var i, len; + if (patterns && Object.prototype.toString.call(patterns) === "[object Array]") { + len = patterns.length; + for (i = 0; i < len; i++) { + if (typeof patterns[i].triggerEvent === 'function') { + //patterns[i].triggerEvent(); + } + + } + } + }, + + /** + * @private + */ + rendered: kind.inherit (function (sup) { + return function() { + sup.apply(this, arguments); + this.initialize(); + }; + }) +}; + +module.exports = AnimationInterfaceSupport; + +/** + Hijacking original behaviour as in other Enyo supports. +*/ +var sup = kind.concatHandler; + +/** + * @private + */ +kind.concatHandler = function (ctor, props, instance) { + sup.call(this, ctor, props, instance); + var aPattern = props.pattern; + if (aPattern && Object.prototype.toString.call(aPattern) === "[object Array]") { + var proto = ctor.prototype || ctor; + extend(AnimationInterfaceSupport, proto); + + patterns = aPattern; + var len = patterns.length; + for (var i = 0; i < len; i++) { + extend(patterns[i], proto); + } + animator.register(proto); + } +}; \ No newline at end of file diff --git a/lib/AnimationSupport/AnimationSupport.js b/lib/AnimationSupport/AnimationSupport.js index 89945dec7..3fba00386 100644 --- a/lib/AnimationSupport/AnimationSupport.js +++ b/lib/AnimationSupport/AnimationSupport.js @@ -62,7 +62,7 @@ var AnimationSupport = { * @public */ addAnimation: function (newProp) { - if (this._prop === undefined) { + if (this._prop === undefined || this._prop == true) { this._prop = newProp; } else { utils.mixin(this._prop, newProp); @@ -147,7 +147,7 @@ var sup = kind.concatHandler; */ kind.concatHandler = function (ctor, props, instance) { sup.call(this, ctor, props, instance); - if (props.animate || props.keyFrame) { + if (props.animate || props.keyFrame || props.pattern) { var proto = ctor.prototype || ctor; extend(AnimationSupport, proto); if (props.keyFrame && typeof props.keyFrame != 'function') { diff --git a/lib/AnimationSupport/Core.js b/lib/AnimationSupport/Core.js index 1a27930bf..0326a73bd 100644 --- a/lib/AnimationSupport/Core.js +++ b/lib/AnimationSupport/Core.js @@ -36,6 +36,11 @@ module.exports = kind.singleton({ */ chracs: [], + /** + * @private + */ + evnts: [], + /** * @private */ @@ -102,6 +107,23 @@ module.exports = kind.singleton({ this.chracs.splice(this.chracs.indexOf(curr), 1); }, + /** + * Animator public API to register character with event + * + * @parameter charc- Animation character + * + * @public + */ + register: function (charc) { + this.evnts.push(charc); + + /*if (!this.running) { + this.running = true; + this.start(); + }*/ + this.start(); + }, + /** * @private */ @@ -143,6 +165,13 @@ module.exports = kind.singleton({ } } } + + len = this.evnts.length; + for (i = 0; i < len; i++) { + if (typeof this.evnts[i].commitAnimation === 'function') { + this.evnts[i].commitAnimation(); + } + } this.start(); }, diff --git a/lib/AnimationSupport/Fadeable.js b/lib/AnimationSupport/Fadeable.js index 22176e941..4e6439c73 100644 --- a/lib/AnimationSupport/Fadeable.js +++ b/lib/AnimationSupport/Fadeable.js @@ -15,12 +15,17 @@ module.exports = { */ name: 'Fadeable', + /** + * @private + */ + fadableValue: 0, + /** * @public * Make the character invisible */ invisible: function () { - this.addAnimation({opacity: 0}); + this.setAnimation({opacity: 0}); doFade(this); }, @@ -31,7 +36,8 @@ module.exports = { * @parameter value - set transparency value */ transparent: function (value) { - this.addAnimation({opacity: value ? value : 0.5}); + value = value || 0.5; + this.setAnimation({opacity: value}); doFade(this); }, @@ -40,9 +46,35 @@ module.exports = { * Make the character visible */ opaque: function () { - this.addAnimation({opacity: 1}); + this.setAnimation({opacity: 1}); doFade(this); }, + + /** + * @public + * Fade element based on event trigger + */ + fadeByDelta: function (deltaValue) { + if (deltaValue !== 0) { + this.fadableValue = this.fadableValue + deltaValue * 0.1; + if (this.fadableValue <= 0) { + this.fadableValue=0; + } else if (this.fadableValue >=1) { + this.fadableValue=1; + } + } + this.setAnimation({opacity:this.fadableValue }); + doFade(this); + }, + + /** + * @public + * Bubble the fadeable event + */ + triggerEvent: function (e) { + console.log("TODO: Trigger the fadeable event"+e); + //this.doFadeStart(); + } }; /** @@ -50,4 +82,5 @@ module.exports = { */ function doFade (charc) { animation.trigger(charc); + charc.start(); } \ No newline at end of file diff --git a/lib/AnimationSupport/Flippable.js b/lib/AnimationSupport/Flippable.js index 72586d9c7..adbb2bf04 100644 --- a/lib/AnimationSupport/Flippable.js +++ b/lib/AnimationSupport/Flippable.js @@ -37,19 +37,32 @@ module.exports = { * @public * apply animation to the flippable DOM object */ - doFlip: function() { - this.setAxis(); + doFlip: function(deltaValue) { + this.setAxis(deltaValue); animation.trigger(this); - // this.start(); + this.start(); }, /** * @public * set axis of rotation of flippable DOM object */ - setAxis: function() { + setAxis: function(delta) { var css = {}; - css["rotate" + this.flipDirection] = this.flipAngle; + var dir = ""; + this.flipAngle = this.flipAngle + delta * 10; + switch(this.flipDirection) { + case "X": + dir = "1,0,0,"+ this.flipAngle; + break; + case "Y": + dir = "0,1,0,"+ this.flipAngle; + break; + case "Z": + dir = "0,0,1,"+ this.flipAngle; + break; + } + css["rotate"] = dir; this.addAnimation(css); } }; \ No newline at end of file diff --git a/lib/AnimationSupport/Slideable.js b/lib/AnimationSupport/Slideable.js index 856a46a59..2976c90a9 100644 --- a/lib/AnimationSupport/Slideable.js +++ b/lib/AnimationSupport/Slideable.js @@ -1,6 +1,7 @@ var kind = require('../kind'), animation = require('./Core'); + /** * Interface to achieve slide animation * @@ -20,7 +21,8 @@ module.exports = { * @parameter: slideDistance - distance in pixels to slide in left direction */ left: function (slideDistance) { - this.slide({translateX: -1 * slideDistance, translateY: 0}); + var value = (-1 * slideDistance) + ",0,0"; + this.slide({translate: value}); }, /** @@ -29,7 +31,8 @@ module.exports = { * @parameter: slideDistance - distance in pixels to slide in right direction */ right: function (slideDistance) { - this.slide({translateX: slideDistance, translateY: 0}); + var value = slideDistance + ",0,0"; + this.slide({translate: value}); }, /** @@ -38,7 +41,8 @@ module.exports = { * @parameter: slideDistance - distance in pixels to slide upward */ up: function (slideDistance) { - this.slide({translateX: 0, translateY: -1 * slideDistance}); + var value = "0," + (-1 * slideDistance) + ",0"; + this.slide({translate: value}); }, /** @@ -47,7 +51,8 @@ module.exports = { * @parameter: slideDistance - distance in pixels to slide downward */ down: function (slideDistance) { - this.slide({translateX: 0, translateY: slideDistance}); + var value = "0," + slideDistance + ",0"; + this.slide({translate: value}); }, /** @@ -58,5 +63,6 @@ module.exports = { slide: function (anim) { this.addAnimation(anim); animation.trigger(this); + this.start(); } }; \ No newline at end of file From 63188d818dc77c9de548ac4f0312e3941c4608b6 Mon Sep 17 00:00:00 2001 From: Ryan Duffy Date: Fri, 4 Sep 2015 10:29:15 -0500 Subject: [PATCH 054/195] notify observers of index when using LightPanels.animateTo Without the notification, the aria observer cannot update the role of the active LightPanel. Issue: ENYO-1994 Enyo-DCO-1.1-Signed-off-by: Ryan Duffy (ryan.duffy@lge.com) --- src/LightPanels/LightPanels.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index b8227c267..67e629e1c 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -192,7 +192,6 @@ module.exports = kind( this.updateTransforms(); this.orientationChanged(); this.directionChanged(); - this.indexChanged(); }; }), @@ -246,8 +245,12 @@ module.exports = kind( /** * @private */ - indexChanged: function (was) { - this.setupTransitions(was); + indexChanged: function (was, is) { + // when notifyObservers is called without arguments, we do not need to do any work here + // (since there's no transition required with the indices are the same) + if (was !== is) { + this.setupTransitions(was); + } }, @@ -296,6 +299,7 @@ module.exports = kind( animateTo: function (index) { var from = this.index; this.index = index; + this.notifyObservers('index'); this.setupTransitions(from, true); }, From 5175642cfaa06aa9cc5e53bc8a7cf6da0a291a44 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Fri, 4 Sep 2015 11:34:44 -0700 Subject: [PATCH 055/195] ENYO-2442: Fix backwards direction. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/LightPanels/LightPanels.css | 36 ++++++++++++++++++++++----------- src/LightPanels/LightPanels.js | 26 ++++++++++++++++-------- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/LightPanels/LightPanels.css b/src/LightPanels/LightPanels.css index 7bd95163a..c4890aec7 100644 --- a/src/LightPanels/LightPanels.css +++ b/src/LightPanels/LightPanels.css @@ -39,24 +39,36 @@ height: 50%; } +.enyo-light-panels.horizontal.backwards .panels-container { + transform: translateX(-50%); +} + +.enyo-light-panels.vertical.backwards .panels-container { + transform: translateY(-50%); +} + /* Individual panel styles */ -.enyo-light-panels.horizontal.forwards .panels-container > .next, -.enyo-light-panels.horizontal.backwards .panels-container > .previous { - left: 50%; +.enyo-light-panels.horizontal.backwards .panels-container > * { + transform: translateX(100%); } -.enyo-light-panels.horizontal.backwards .panels-container > .next, -.enyo-light-panels.horizontal.forwards .panels-container > .previous { - right: 50%; +.enyo-light-panels.vertical.backwards .panels-container > * { + transform: translateY(100%); } -.enyo-light-panels.vertical.forwards .panels-container > .next, -.enyo-light-panels.vertical.backwards .panels-container > .previous { - top: 50%; +.enyo-light-panels.horizontal.forwards .panels-container > .next { + transform: translateX(100%); } -.enyo-light-panels.vertical.backwards .panels-container > .next, -.enyo-light-panels.vertical.forwards .panels-container > .previous { - bottom: 50%; +.enyo-light-panels.horizontal.backwards .panels-container > .previous { + transform: translateX(0%); +} + +.enyo-light-panels.vertical.forwards .panels-container > .next { + transform: translateY(100%); +} + +.enyo-light-panels.vertical.backwards .panels-container > .previous { + transform: translateY(0%); } diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index b8227c267..2f3e3da29 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -658,7 +658,8 @@ module.exports = kind( setupTransitions: function (previousIndex, animate) { var panels = this.getPanels(), nextPanel = panels[this.index], - currPanel = this._currentPanel; + currPanel = this._currentPanel, + panelShift, panelReset; if (previousIndex != -1) this._indexDirection = this.index - previousIndex; else this._indexDirection = 0; @@ -683,16 +684,22 @@ module.exports = kind( // ensure our panel container is in the correct, pre-transition position this.shiftContainer(-1 * this._indexDirection); + // determine what are final panel states should be + panelShift = (this.direction == Direction.FORWARDS && this._indexDirection > 0) + || (this.direction == Direction.BACKWARDS && this._indexDirection < 0); + panelReset = (this.direction == Direction.FORWARDS && this._indexDirection < 0) + || (this.direction == Direction.BACKWARDS && this._indexDirection > 0); + // set the correct state for the next panel nextPanel.set('state', States.ACTIVATING); - nextPanel.addRemoveClass('next', this._indexDirection > 0); - nextPanel.addRemoveClass('previous', this._indexDirection < 0); + nextPanel.addRemoveClass('next', panelShift); + nextPanel.addRemoveClass('previous', panelReset); if (currPanel) { // set the correct state for the previous panel - this._currentPanel.set('state', States.DEACTIVATING); - currPanel.addRemoveClass('previous', this._indexDirection > 0); - currPanel.addRemoveClass('next', this._indexDirection < 0); + currPanel.set('state', States.DEACTIVATING); + currPanel.addRemoveClass('previous', panelShift); + currPanel.addRemoveClass('next', panelReset); } // only animate transition if there is more than one panel and/or we're animating @@ -735,12 +742,15 @@ module.exports = kind( */ shiftContainer: function (indexDirection, animate) { var container = this.$.client, - value = (indexDirection > 0 ? -50 : 0) * this.direction + '%'; + value; + + if (this.direction == Direction.FORWARDS) value = indexDirection > 0 ? -50 : 0; + else value = indexDirection > 0 ? 0 : -50; container.applyStyle('-webkit-transition', animate ? wTrans : null); container.applyStyle('transition', animate ? trans: null); - dom.transformValue(container, 'translate' + this.orientation, value); + dom.transformValue(container, 'translate' + this.orientation, value + '%'); }, /** From fa899ad40bbdd7851d1f4e39c17fe16c505700e3 Mon Sep 17 00:00:00 2001 From: Ankur Mishra Date: Mon, 7 Sep 2015 11:52:28 +0530 Subject: [PATCH 056/195] Update Animation Interface for scroll and mousewheel events --- .../AnimationInterfaceSupport.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/AnimationSupport/AnimationInterfaceSupport.js b/lib/AnimationSupport/AnimationInterfaceSupport.js index 0512006be..6f8ce5eb7 100644 --- a/lib/AnimationSupport/AnimationInterfaceSupport.js +++ b/lib/AnimationSupport/AnimationInterfaceSupport.js @@ -197,8 +197,11 @@ var AnimationInterfaceSupport = { this.deltaY = scrollTop - this.deltaY; this.scrollValue = scrollTop; + this.translateX = this.translateX + this.deltaX; + this.translateY = this.translateY + this.deltaY; + //call commonTasks function with delta values - this.commonTasks(delta, (-1 * this.deltaX), (-1 * this.deltaY)); + this.commonTasks(delta, (this.translateX), (this.translateY)); this.deltaX = scrollLeft; this.deltaY = scrollTop; @@ -212,8 +215,11 @@ var AnimationInterfaceSupport = { deltaX = inSender.wheelDeltaX, deltaY = inSender.wheelDeltaY; + this.translateX = this.translateX + deltaX; + this.translateY = this.translateY + deltaY; + //call commonTasks function with delta values - this.commonTasks(delta, (-1 * deltaX), (-1 * deltaY)); + this.commonTasks(delta, (-1 * this.translateX), (-1 * this.translateY)); }, /** @@ -223,17 +229,14 @@ var AnimationInterfaceSupport = { if (delta !== 0) { delta = delta / Math.abs(delta); } - - this.translateX = this.translateX - deltax; - this.translateY = this.translateY - deltay; - + //Call specific interface if (patterns[0].name ==="Fadeable") { patterns[0].fadeByDelta.call(this, delta); } else if (patterns[0].name === "Flippable") { patterns[0].doFlip.call(this, delta); } else if (patterns[0].name === "Slideable") { - patterns[0].slide.call(this, {translate: this.translateX + ", " + this.translateY + ", 0"} ); + patterns[0].slide.call(this, {translate: (-1 * deltax)+ ", " + (-1 * deltay) + ", 0"} ); } }, From 376da074dd4358534701a584e9ee001bc310aa3a Mon Sep 17 00:00:00 2001 From: Ankur Mishra Date: Mon, 7 Sep 2015 18:45:06 +0530 Subject: [PATCH 057/195] Remove animation trigger from interfaces --- .../AnimationInterfaceSupport.js | 9 ++-- lib/AnimationSupport/Fadeable.js | 19 +++------ lib/AnimationSupport/Flippable.js | 7 +++- lib/AnimationSupport/Slideable.js | 41 ++++++++++++------- 4 files changed, 44 insertions(+), 32 deletions(-) diff --git a/lib/AnimationSupport/AnimationInterfaceSupport.js b/lib/AnimationSupport/AnimationInterfaceSupport.js index 6f8ce5eb7..d3484c0db 100644 --- a/lib/AnimationSupport/AnimationInterfaceSupport.js +++ b/lib/AnimationSupport/AnimationInterfaceSupport.js @@ -217,11 +217,10 @@ var AnimationInterfaceSupport = { this.translateX = this.translateX + deltaX; this.translateY = this.translateY + deltaY; - //call commonTasks function with delta values this.commonTasks(delta, (-1 * this.translateX), (-1 * this.translateY)); }, - + /** * @public */ @@ -236,8 +235,12 @@ var AnimationInterfaceSupport = { } else if (patterns[0].name === "Flippable") { patterns[0].doFlip.call(this, delta); } else if (patterns[0].name === "Slideable") { - patterns[0].slide.call(this, {translate: (-1 * deltax)+ ", " + (-1 * deltay) + ", 0"} ); + if (event.type === "mousewheel") { + deltax = deltay; + } + patterns[0].slide.call(this, (-1 * deltax) , (-1 * deltay) , 0 ); } + this.start(true); }, /** diff --git a/lib/AnimationSupport/Fadeable.js b/lib/AnimationSupport/Fadeable.js index 4e6439c73..a190af238 100644 --- a/lib/AnimationSupport/Fadeable.js +++ b/lib/AnimationSupport/Fadeable.js @@ -15,6 +15,11 @@ module.exports = { */ name: 'Fadeable', + /** + * To start animation + */ + animate: true, + /** * @private */ @@ -26,7 +31,6 @@ module.exports = { */ invisible: function () { this.setAnimation({opacity: 0}); - doFade(this); }, /** @@ -38,7 +42,6 @@ module.exports = { transparent: function (value) { value = value || 0.5; this.setAnimation({opacity: value}); - doFade(this); }, /** @@ -47,7 +50,6 @@ module.exports = { */ opaque: function () { this.setAnimation({opacity: 1}); - doFade(this); }, /** @@ -64,7 +66,6 @@ module.exports = { } } this.setAnimation({opacity:this.fadableValue }); - doFade(this); }, /** @@ -75,12 +76,4 @@ module.exports = { console.log("TODO: Trigger the fadeable event"+e); //this.doFadeStart(); } -}; - -/** -* @private -*/ -function doFade (charc) { - animation.trigger(charc); - charc.start(); -} \ No newline at end of file +}; \ No newline at end of file diff --git a/lib/AnimationSupport/Flippable.js b/lib/AnimationSupport/Flippable.js index adbb2bf04..f346c22ad 100644 --- a/lib/AnimationSupport/Flippable.js +++ b/lib/AnimationSupport/Flippable.js @@ -15,6 +15,11 @@ module.exports = { */ name: 'Flippable', + /** + * To start animation + */ + animate: true, + /** * Specifies the direction of flip. Accepted value are 'X', 'Y', 'Z' * @@ -39,8 +44,6 @@ module.exports = { */ doFlip: function(deltaValue) { this.setAxis(deltaValue); - animation.trigger(this); - this.start(); }, /** diff --git a/lib/AnimationSupport/Slideable.js b/lib/AnimationSupport/Slideable.js index 2976c90a9..d33b31e6c 100644 --- a/lib/AnimationSupport/Slideable.js +++ b/lib/AnimationSupport/Slideable.js @@ -15,14 +15,18 @@ module.exports = { */ name: 'Slideable', + /** + * To start animation + */ + animate: true, + /** * @public * slide animation in left direction * @parameter: slideDistance - distance in pixels to slide in left direction */ left: function (slideDistance) { - var value = (-1 * slideDistance) + ",0,0"; - this.slide({translate: value}); + this.slide((-1 * slideDistance), 0, 0); }, /** @@ -31,8 +35,7 @@ module.exports = { * @parameter: slideDistance - distance in pixels to slide in right direction */ right: function (slideDistance) { - var value = slideDistance + ",0,0"; - this.slide({translate: value}); + this.slide(slideDistance, 0, 0); }, /** @@ -41,8 +44,7 @@ module.exports = { * @parameter: slideDistance - distance in pixels to slide upward */ up: function (slideDistance) { - var value = "0," + (-1 * slideDistance) + ",0"; - this.slide({translate: value}); + this.slide(0, (-1 * slideDistance), 0); }, /** @@ -51,18 +53,29 @@ module.exports = { * @parameter: slideDistance - distance in pixels to slide downward */ down: function (slideDistance) { - var value = "0," + slideDistance + ",0"; - this.slide({translate: value}); + this.slide(0, slideDistance, 0); }, - + /** * @public * slide animation in custom direction - * @parameter: anim - css property to slide in any direction + * @parameter: x - css property to slide in x-axis direction + * @parameter: y - css property to slide in y-axis direction + * @parameter: z - css property to slide in z-axis direction */ - slide: function (anim) { - this.addAnimation(anim); - animation.trigger(this); - this.start(); + slide: function (x, y, z) { + x = x || 0; + y = y || 0; + z = z || 0; + switch (this.direction) { + case "horizontal" : + this.addAnimation({translate: x + "," + 0 + "," + 0}); + break; + case "vertical" : + this.addAnimation({translate: 0 + "," + y + "," + 0}); + break; + default: + this.addAnimation({translate: x + "," + y + "," + z}); + } } }; \ No newline at end of file From d07f1593aa61508770353efd98b6be4a8f0f9df6 Mon Sep 17 00:00:00 2001 From: Ankur Mishra Date: Mon, 7 Sep 2015 19:11:14 +0530 Subject: [PATCH 058/195] Changes made in UiComponents for animation support interface --- lib/UiComponent.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/UiComponent.js b/lib/UiComponent.js index 433f3706e..d541229a9 100644 --- a/lib/UiComponent.js +++ b/lib/UiComponent.js @@ -9,7 +9,8 @@ var kind = require('./kind'), utils = require('./utils'), master = require('./master'), - AnimationSupport = require('./AnimationSupport/AnimationSupport'); + AnimationSupport = require('./AnimationSupport/AnimationSupport'), + AnimationInterfaceSupport = require('./AnimationSupport/AnimationInterfaceSupport'); var Component = require('./Component'); @@ -133,7 +134,7 @@ var UiComponent = module.exports = kind( * Adding animation support for controls * @private */ - mixins: [AnimationSupport], + mixins: [AnimationSupport, AnimationInterfaceSupport], /** * When set, provides a [control]{@link module:enyo/Control~Control} reference used to indicate where a From 5a3b5ffa516ffa3257d774bf29a3f1da9b414d69 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Sat, 5 Sep 2015 21:04:08 -0700 Subject: [PATCH 059/195] ENYO-2465: Allow for declared component block and initial index. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/LightPanels/LightPanels.css | 20 +++++++-- src/LightPanels/LightPanels.js | 75 +++++++++++++++++---------------- 2 files changed, 55 insertions(+), 40 deletions(-) diff --git a/src/LightPanels/LightPanels.css b/src/LightPanels/LightPanels.css index c4890aec7..df503615e 100644 --- a/src/LightPanels/LightPanels.css +++ b/src/LightPanels/LightPanels.css @@ -21,6 +21,10 @@ box-sizing: border-box; } +.enyo-light-panels .panels-container { + position: relative; +} + .enyo-light-panels.horizontal .panels-container { width: 200%; height: 100%; @@ -57,18 +61,26 @@ transform: translateY(100%); } -.enyo-light-panels.horizontal.forwards .panels-container > .next { +.enyo-light-panels.horizontal.forwards .panels-container > .shifted { transform: translateX(100%); } -.enyo-light-panels.horizontal.backwards .panels-container > .previous { +.enyo-light-panels.horizontal.backwards .panels-container > .shifted { transform: translateX(0%); } -.enyo-light-panels.vertical.forwards .panels-container > .next { +.enyo-light-panels.vertical.forwards .panels-container > .shifted { transform: translateY(100%); } -.enyo-light-panels.vertical.backwards .panels-container > .previous { +.enyo-light-panels.vertical.backwards .panels-container > .shifted { transform: translateY(0%); } + +.enyo-light-panels.horizontal .panels-container > .offscreen { + transform: translateY(100%); +} + +.enyo-light-panels.vertical .panels-container > .offscreen { + transform: translateX(100%); +} diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index 632d61ea7..6e85bc5ce 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -95,10 +95,10 @@ module.exports = kind( * The index of the active panel. * * @type {Number} - * @default -1 + * @default 0 * @public */ - index: -1, + index: 0, /** * Indicates whether the panels animate when transitioning. @@ -178,22 +178,37 @@ module.exports = kind( /** * @private */ - components: [ + tools: [ {kind: Control, name: 'client', classes: 'panels-container', ontransitionend: 'transitionFinished'} ], /** - * @method * @private */ - create: kind.inherit(function (sup) { - return function () { - sup.apply(this, arguments); - this.updateTransforms(); - this.orientationChanged(); - this.directionChanged(); - }; - }), + create: function () { + Control.prototype.create.apply(this, arguments); + this.updateTransforms(); + this.orientationChanged(); + this.directionChanged(); + if (!this.getPanels().length) this.index = -1; + this.setupTransitions(); + }, + + /** + * @private + */ + initComponents: function() { + this.createChrome(this.tools); + Control.prototype.initComponents.apply(this, arguments); + }, + + /** + * @private + */ + addChild: function (control) { + if (!control.isChrome) control.addClass('offscreen'); + Control.prototype.addChild.apply(this, arguments); + }, @@ -585,15 +600,6 @@ module.exports = kind( } }, - /** - * Determines whether or not we are removing inactive panels from DOM. - * @returns {Boolean} If `true`, inactive panels are being removed; `false` otherwise. - * @private - */ - removesPanels: function () { - return this.cacheViews || this.popOnBack || this.popOnForward; - }, - /** * When the transition has completed, we perform some clean-up work. * @@ -612,8 +618,11 @@ module.exports = kind( if ((this._indexDirection < 0 && (this.popOnBack || this.cacheViews) && this.index < this.getPanels().length - 1) || (this._indexDirection > 0 && (this.popOnForward || this.cacheViews) && this.index > 0)) { this.popPanels(this.index, this._indexDirection); - } else if (prevPanel && !this.removesPanels()) { - prevPanel.set('showing', false); + } + + if (prevPanel) { + prevPanel.removeClass('shifted'); + prevPanel.addClass('offscreen'); } if (this.popQueue && this.popQueue.length) this.finalizePurge(); @@ -654,8 +663,8 @@ module.exports = kind( /** * Sets up the transitions between the current and next panel. * - * @param {Number} previousIndex - The index of the panel we are transitioning from. - * @param {Boolean} animate - Whether or not there should be a visible animation when + * @param {Number} [previousIndex] - The index of the panel we are transitioning from. + * @param {Boolean} [animate] - Whether or not there should be a visible animation when * transitioning between the current and next panel. * @private */ @@ -663,7 +672,7 @@ module.exports = kind( var panels = this.getPanels(), nextPanel = panels[this.index], currPanel = this._currentPanel, - panelShift, panelReset; + shiftCurrent; if (previousIndex != -1) this._indexDirection = this.index - previousIndex; else this._indexDirection = 0; @@ -679,7 +688,7 @@ module.exports = kind( } // prepare the panel that will be transitioned into view - if (!this.removesPanels()) nextPanel.set('showing', true); + nextPanel.removeClass('offscreen'); if (!nextPanel.generated) nextPanel.render(); nextPanel.set('state', States.ACTIVE); @@ -688,22 +697,16 @@ module.exports = kind( // ensure our panel container is in the correct, pre-transition position this.shiftContainer(-1 * this._indexDirection); - // determine what are final panel states should be - panelShift = (this.direction == Direction.FORWARDS && this._indexDirection > 0) - || (this.direction == Direction.BACKWARDS && this._indexDirection < 0); - panelReset = (this.direction == Direction.FORWARDS && this._indexDirection < 0) - || (this.direction == Direction.BACKWARDS && this._indexDirection > 0); + shiftCurrent = this._indexDirection > 0; // set the correct state for the next panel nextPanel.set('state', States.ACTIVATING); - nextPanel.addRemoveClass('next', panelShift); - nextPanel.addRemoveClass('previous', panelReset); + nextPanel.addRemoveClass('shifted', shiftCurrent); if (currPanel) { // set the correct state for the previous panel currPanel.set('state', States.DEACTIVATING); - currPanel.addRemoveClass('previous', panelShift); - currPanel.addRemoveClass('next', panelReset); + currPanel.addRemoveClass('shifted', !shiftCurrent); } // only animate transition if there is more than one panel and/or we're animating From f02fa90f3396b558351c5184ba91149cd57b7eda Mon Sep 17 00:00:00 2001 From: Cole Davis Date: Tue, 8 Sep 2015 15:53:12 -0700 Subject: [PATCH 060/195] fix failing test and update guard code in controller when creating global controller --- src/Controller.js | 5 +++-- test/tests/Controller.js | 5 ++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Controller.js b/src/Controller.js index ab19643dd..cb4fa3955 100644 --- a/src/Controller.js +++ b/src/Controller.js @@ -63,8 +63,9 @@ module.exports = kind( constructor: kind.inherit(function (sup) { return function () { sup.apply(this, arguments); - if (this.global) { - utils.setPath(this.name, this); + // don't attempt to set a controller globally without a name + if (this.global && this.name) { + utils.setPath.call(global, this.name, this); } }; }), diff --git a/test/tests/Controller.js b/test/tests/Controller.js index 620a96b6b..9fd31c629 100644 --- a/test/tests/Controller.js +++ b/test/tests/Controller.js @@ -9,11 +9,10 @@ describe('Controller', function () { var c; before(function () { - c = kind.singleton({ - name: 'test.global.controller', + c = new (kind({ kind: Controller, global: true - }); + }))({name: 'test.global.controller'}); }); after(function () { From 9b96945c8d2f578104af4f91a3c850df26ee7980 Mon Sep 17 00:00:00 2001 From: Cole Davis Date: Tue, 8 Sep 2015 16:29:51 -0700 Subject: [PATCH 061/195] when bubbleTarget is explicitly set (other cases would need to arbitrarily execute this method but no cases currently exist) ensure that any cached bubble targets are cleaned up --- src/UiComponent.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/UiComponent.js b/src/UiComponent.js index 1a393b47c..5cc3a6dde 100644 --- a/src/UiComponent.js +++ b/src/UiComponent.js @@ -701,5 +701,17 @@ var UiComponent = module.exports = kind( || this.owner ); } + }, + + /** + * @method + * @private + */ + bubbleTargetChanged: function (was) { + if (was && this.cachedBubble && this.cachedBubbleTarget) { + for (var n in this.cachedBubbleTarget) { + if (this.cachedBubbleTarget[n] === was) delete this.cachedBubbleTarget[n]; + } + } } }); From 47518209c7db4c61803e0e1ebe16d22c3826707b Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Tue, 8 Sep 2015 16:30:22 -0700 Subject: [PATCH 062/195] ENYO-2465: Handle the wrapping case, guard against new transition while transitioning, tighten up selectors. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/LightPanels/LightPanels.css | 32 ++++++++++++------------ src/LightPanels/LightPanels.js | 44 ++++++++++++++++++++------------- 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/src/LightPanels/LightPanels.css b/src/LightPanels/LightPanels.css index df503615e..e08750895 100644 --- a/src/LightPanels/LightPanels.css +++ b/src/LightPanels/LightPanels.css @@ -10,7 +10,7 @@ /* Panels container styles */ -.enyo-light-panels .panels-container > * { +.enyo-light-panels > .panels-container > * { display: inline-block; vertical-align: top; position: absolute; @@ -21,66 +21,66 @@ box-sizing: border-box; } -.enyo-light-panels .panels-container { +.enyo-light-panels > .panels-container { position: relative; } -.enyo-light-panels.horizontal .panels-container { +.enyo-light-panels.horizontal > .panels-container { width: 200%; height: 100%; } -.enyo-light-panels.vertical .panels-container { +.enyo-light-panels.vertical > .panels-container { width: 100%; height: 200%; } -.enyo-light-panels.horizontal .panels-container > * { +.enyo-light-panels.horizontal > .panels-container > * { width: 50%; } -.enyo-light-panels.vertical .panels-container > * { +.enyo-light-panels.vertical > .panels-container > * { height: 50%; } -.enyo-light-panels.horizontal.backwards .panels-container { +.enyo-light-panels.horizontal.backwards > .panels-container { transform: translateX(-50%); } -.enyo-light-panels.vertical.backwards .panels-container { +.enyo-light-panels.vertical.backwards > .panels-container { transform: translateY(-50%); } /* Individual panel styles */ -.enyo-light-panels.horizontal.backwards .panels-container > * { +.enyo-light-panels.horizontal.backwards > .panels-container > * { transform: translateX(100%); } -.enyo-light-panels.vertical.backwards .panels-container > * { +.enyo-light-panels.vertical.backwards > .panels-container > * { transform: translateY(100%); } -.enyo-light-panels.horizontal.forwards .panels-container > .shifted { +.enyo-light-panels.horizontal.forwards > .panels-container > .shifted { transform: translateX(100%); } -.enyo-light-panels.horizontal.backwards .panels-container > .shifted { +.enyo-light-panels.horizontal.backwards > .panels-container > .shifted { transform: translateX(0%); } -.enyo-light-panels.vertical.forwards .panels-container > .shifted { +.enyo-light-panels.vertical.forwards > .panels-container > .shifted { transform: translateY(100%); } -.enyo-light-panels.vertical.backwards .panels-container > .shifted { +.enyo-light-panels.vertical.backwards > .panels-container > .shifted { transform: translateY(0%); } -.enyo-light-panels.horizontal .panels-container > .offscreen { +.enyo-light-panels.horizontal > .panels-container > .offscreen { transform: translateY(100%); } -.enyo-light-panels.vertical .panels-container > .offscreen { +.enyo-light-panels.vertical > .panels-container > .offscreen { transform: translateX(100%); } diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index 6e85bc5ce..0c025a7c4 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -206,8 +206,8 @@ module.exports = kind( * @private */ addChild: function (control) { - if (!control.isChrome) control.addClass('offscreen'); Control.prototype.addChild.apply(this, arguments); + if (control.parent === this.$.client) control.addClass('offscreen'); }, @@ -325,13 +325,15 @@ module.exports = kind( * @public */ previous: function () { - var prevIndex = this.index - 1; - if (this.wrap && prevIndex < 0) { - prevIndex = this.getPanels().length - 1; - } - if (prevIndex >= 0) { - if (this.animate) this.animateTo(prevIndex); - else this.set('index', prevIndex); + if (!this.transitioning) { + var prevIndex = this.index - 1; + if (this.wrap && prevIndex < 0) { + prevIndex = this.getPanels().length - 1; + } + if (prevIndex >= 0) { + if (this.animate) this.animateTo(prevIndex); + else this.set('index', prevIndex); + } } }, @@ -342,13 +344,15 @@ module.exports = kind( * @public */ next: function () { - var nextIndex = this.index + 1; - if (this.wrap && nextIndex >= this.getPanels().length) { - nextIndex = 0; - } - if (nextIndex < this.getPanels().length) { - if (this.animate) this.animateTo(nextIndex); - else this.set('index', nextIndex); + if (!this.transitioning) { + var nextIndex = this.index + 1; + if (this.wrap && nextIndex >= this.getPanels().length) { + nextIndex = 0; + } + if (nextIndex < this.getPanels().length) { + if (this.animate) this.animateTo(nextIndex); + else this.set('index', nextIndex); + } } }, @@ -674,8 +678,14 @@ module.exports = kind( currPanel = this._currentPanel, shiftCurrent; - if (previousIndex != -1) this._indexDirection = this.index - previousIndex; - else this._indexDirection = 0; + this._indexDirection = 0; + + // handle the wrapping case + if (this.wrap) { + if (this.index === 0 && previousIndex == panels.length - 1) this._indexDirection = 1; + else if (this.index === panels.length - 1 && previousIndex === 0) this._indexDirection = -1; + } + if (this._indexDirection === 0 && previousIndex != -1) this._indexDirection = this.index - previousIndex; if (nextPanel) { this.transitioning = true; From 65cc8ccbd60c99cf990de4ed971cadbde57f4553 Mon Sep 17 00:00:00 2001 From: Ankur Mishra Date: Wed, 9 Sep 2015 15:01:15 +0530 Subject: [PATCH 063/195] Need copy of initial state in framework --- lib/AnimationSupport/AnimationInterfaceSupport.js | 7 ++----- lib/AnimationSupport/AnimationSupport.js | 2 +- lib/AnimationSupport/Fadeable.js | 8 ++++---- lib/AnimationSupport/Frame.js | 2 +- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/lib/AnimationSupport/AnimationInterfaceSupport.js b/lib/AnimationSupport/AnimationInterfaceSupport.js index d3484c0db..1631de924 100644 --- a/lib/AnimationSupport/AnimationInterfaceSupport.js +++ b/lib/AnimationSupport/AnimationInterfaceSupport.js @@ -115,7 +115,7 @@ var AnimationInterfaceSupport = { switch (eventType) { case "dragstart": this.touchDragStart(inSender, inEvent, inSender.pageX, inSender.pageY); - break; + break; case "drag": this.touchDragMove(inSender, inEvent, inSender.pageX, inSender.pageY); break; @@ -160,13 +160,10 @@ var AnimationInterfaceSupport = { var currentX = x, currentY = y; - if(currentX != 0 || currentY != 0) { + if(currentX !== 0 || currentY !== 0) { this.deltaValueX = this.checkX - currentX; this.deltaValueY = this.checkY - currentY; - var finalX = -1 * this.deltaValueX, - finalY = -1 * this.deltaValueY; - //call commonTasks function with delta values this.commonTasks(this.deltaValueX, this.deltaValueX, this.deltaValueY); } diff --git a/lib/AnimationSupport/AnimationSupport.js b/lib/AnimationSupport/AnimationSupport.js index 3fba00386..f6a5aa196 100644 --- a/lib/AnimationSupport/AnimationSupport.js +++ b/lib/AnimationSupport/AnimationSupport.js @@ -33,7 +33,7 @@ var AnimationSupport = { * @public */ setInitial: function (initial) { - this._startAnim = initial; + this._startAnim = frame.copy(initial); }, /** diff --git a/lib/AnimationSupport/Fadeable.js b/lib/AnimationSupport/Fadeable.js index a190af238..9ef58df4f 100644 --- a/lib/AnimationSupport/Fadeable.js +++ b/lib/AnimationSupport/Fadeable.js @@ -30,7 +30,7 @@ module.exports = { * Make the character invisible */ invisible: function () { - this.setAnimation({opacity: 0}); + this.addAnimation({opacity: 0}); }, /** @@ -41,7 +41,7 @@ module.exports = { */ transparent: function (value) { value = value || 0.5; - this.setAnimation({opacity: value}); + this.addAnimation({opacity: value}); }, /** @@ -49,7 +49,7 @@ module.exports = { * Make the character visible */ opaque: function () { - this.setAnimation({opacity: 1}); + this.addAnimation({opacity: 1}); }, /** @@ -65,7 +65,7 @@ module.exports = { this.fadableValue=1; } } - this.setAnimation({opacity:this.fadableValue }); + this.addAnimation({opacity:this.fadableValue }); }, /** diff --git a/lib/AnimationSupport/Frame.js b/lib/AnimationSupport/Frame.js index 44d42bffa..fe16bcf1d 100644 --- a/lib/AnimationSupport/Frame.js +++ b/lib/AnimationSupport/Frame.js @@ -306,7 +306,7 @@ var frame = module.exports = { if(!node || !props) return; var eP = {}, - sP = {}, + sP = initial ? this.copy(initial) : {}, tP = {}, dP = {}, m, k, v, From 62394e28d325f43156a7c3cf23547307b77c5d1e Mon Sep 17 00:00:00 2001 From: Roy Sutton Date: Wed, 9 Sep 2015 14:37:43 -0700 Subject: [PATCH 064/195] Revert "Revert "Merge pull request #1256 from enyojs/ENYO-2313-graynorton"" This reverts commit f94e6c257833b13ba4ed3659b1c80dbfab0967e4. --- src/Image/Image.css | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Image/Image.css b/src/Image/Image.css index a795b7638..e8e51c5b7 100644 --- a/src/Image/Image.css +++ b/src/Image/Image.css @@ -1,7 +1,10 @@ -.enyo-image.sized { - display: inline-block; +.enyo-image { background-position: center; background-repeat: no-repeat; + background-size: cover; +} +.enyo-image.sized { + display: inline-block; } .enyo-image.contain { background-size: contain; From 7771158ea7b1dea6f322cdd1053b58abdadc9253 Mon Sep 17 00:00:00 2001 From: Roy Sutton Date: Wed, 9 Sep 2015 14:38:47 -0700 Subject: [PATCH 065/195] Make image placeholder static visible in docs Enyo-DCO-1.1-Signed-off-by: Roy Sutton --- src/Image/Image.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Image/Image.js b/src/Image/Image.js index 0094d33e1..9ce18fe57 100644 --- a/src/Image/Image.js +++ b/src/Image/Image.js @@ -326,7 +326,7 @@ module.exports = kind( }), /** - * @lends enyo.Image + * @lends module:enyo/Image~Image * @private */ statics: { From 2579519a6d25242b029b603ab0de60e3bf445801 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Wed, 9 Sep 2015 14:55:15 -0700 Subject: [PATCH 066/195] ENYO-2444: Complete animation when tearing down a cached view. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/Drawer.js | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/Drawer.js b/src/Drawer.js index 8200b59bb..2ab3d4e25 100644 --- a/src/Drawer.js +++ b/src/Drawer.js @@ -13,7 +13,7 @@ var Control = require('./Control'); /** -* Fires when the [drawer]{@link module:enyo/Drawer~Drawer} has been opened or closed. The handler can +* Fires when the [drawer]{@link module:enyo/Drawer~Drawer} has been opened or closed. The handler can * determine whether the drawer was just opened or just closed based on the * [open]{@link module:enyo/Drawer~Drawer#open} property. If `this.getOpen()` returns `true`, * the drawer was opened; if not, it was closed. @@ -34,7 +34,7 @@ var * * @event module:enyo/Drawer~Drawer#onDrawerAnimationEnd * @type {Object} -* @property {Object} sender - The [component]{@link module:enyo/Component~Component} that most recently +* @property {Object} sender - The [component]{@link module:enyo/Component~Component} that most recently * propagated the {@glossary event}. * @property {Object} event - An [object]{@glossary Object} containing event information. * @public @@ -71,12 +71,12 @@ module.exports = kind( /** * @private */ - published: + published: /** @lends module:enyo/Drawer~Drawer.prototype */ { - + /** * The visibility state of the [drawer's]{@link module:enyo/Drawer~Drawer} associated control. - * + * * @type {Boolean} * @default true * @public @@ -86,7 +86,7 @@ module.exports = kind( /** * The direction of the opening/closing animation; will be either `'v'` for vertical * or `'h'` for horizontal. - * + * * @type {String} * @default 'v' * @public @@ -95,7 +95,7 @@ module.exports = kind( /** * If `true`, the opening/closing transition will be animated. - * + * * @type {Boolean} * @default true * @public @@ -106,7 +106,7 @@ module.exports = kind( * If `true`, the [drawer]{@link module:enyo/Drawer~Drawer} will resize its container as it is * animating, which is useful when the drawer is placed inside a * [FittableLayout]{@link module:layout/FittableLayout~FittableLayout}. - * + * * @type {Boolean} * @default true * @public @@ -121,7 +121,7 @@ module.exports = kind( onDrawerAnimationStep: '', onDrawerAnimationEnd: '' }, - + /** * @private */ @@ -158,6 +158,16 @@ module.exports = kind( }; }), + /** + * @private + */ + teardownRender: kind.inherit(function (sup) { + return function (caching) { + if (caching) this.$.animator.complete(); + sup.apply(this, arguments); + }; + }), + /** * @private */ From 00960bb9e9d1f8863b9ba55f08fdc60611142665 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Wed, 9 Sep 2015 15:49:12 -0700 Subject: [PATCH 067/195] ENYO-2465: Convert styles to LESS syntax. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/LightPanels/LightPanels.css | 86 ----------------------------- src/LightPanels/LightPanels.less | 92 ++++++++++++++++++++++++++++++++ src/LightPanels/package.json | 2 +- 3 files changed, 93 insertions(+), 87 deletions(-) delete mode 100644 src/LightPanels/LightPanels.css create mode 100644 src/LightPanels/LightPanels.less diff --git a/src/LightPanels/LightPanels.css b/src/LightPanels/LightPanels.css deleted file mode 100644 index e08750895..000000000 --- a/src/LightPanels/LightPanels.css +++ /dev/null @@ -1,86 +0,0 @@ -.enyo-light-panels { - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - overflow: hidden; - direction: ltr; -} - -/* Panels container styles */ - -.enyo-light-panels > .panels-container > * { - display: inline-block; - vertical-align: top; - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - box-sizing: border-box; -} - -.enyo-light-panels > .panels-container { - position: relative; -} - -.enyo-light-panels.horizontal > .panels-container { - width: 200%; - height: 100%; -} - -.enyo-light-panels.vertical > .panels-container { - width: 100%; - height: 200%; -} - -.enyo-light-panels.horizontal > .panels-container > * { - width: 50%; -} - -.enyo-light-panels.vertical > .panels-container > * { - height: 50%; -} - -.enyo-light-panels.horizontal.backwards > .panels-container { - transform: translateX(-50%); -} - -.enyo-light-panels.vertical.backwards > .panels-container { - transform: translateY(-50%); -} - -/* Individual panel styles */ - -.enyo-light-panels.horizontal.backwards > .panels-container > * { - transform: translateX(100%); -} - -.enyo-light-panels.vertical.backwards > .panels-container > * { - transform: translateY(100%); -} - -.enyo-light-panels.horizontal.forwards > .panels-container > .shifted { - transform: translateX(100%); -} - -.enyo-light-panels.horizontal.backwards > .panels-container > .shifted { - transform: translateX(0%); -} - -.enyo-light-panels.vertical.forwards > .panels-container > .shifted { - transform: translateY(100%); -} - -.enyo-light-panels.vertical.backwards > .panels-container > .shifted { - transform: translateY(0%); -} - -.enyo-light-panels.horizontal > .panels-container > .offscreen { - transform: translateY(100%); -} - -.enyo-light-panels.vertical > .panels-container > .offscreen { - transform: translateX(100%); -} diff --git a/src/LightPanels/LightPanels.less b/src/LightPanels/LightPanels.less new file mode 100644 index 000000000..86695deef --- /dev/null +++ b/src/LightPanels/LightPanels.less @@ -0,0 +1,92 @@ +.enyo-light-panels { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + overflow: hidden; + direction: ltr; + + &.horizontal { + &.forwards { + > .panels-container { + > .shifted { + transform: translateX(100%); + } + } + } + &.backwards { + > .panels-container { + transform: translateX(-50%); + + > * { + transform: translateX(100%); + } + + > .shifted { + transform: translateX(0%); + } + } + } + + > .panels-container { + width: 200%; + height: 100%; + + > * { + width: 50%; + } + } + } + + &.vertical { + &.fowards { + > .panels-container { + > .shifted { + transform: translateY(100%); + } + } + } + &.backwards { + > .panels-container { + transform: translateY(-50%); + + > * { + transform: translateY(100%); + } + + > .shifted { + transform: translateY(0%); + } + } + } + + > .panels-container { + width: 100%; + height: 200%; + + > * { + height: 50%; + } + } + } + + > .panels-container { + position: relative; + + > * { + display: inline-block; + vertical-align: top; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + box-sizing: border-box; + } + + > .offscreen { + display: none; + } + } +} diff --git a/src/LightPanels/package.json b/src/LightPanels/package.json index 1fd5137b3..2484c901d 100644 --- a/src/LightPanels/package.json +++ b/src/LightPanels/package.json @@ -1,6 +1,6 @@ { "main": "LightPanels.js", "styles": [ - "LightPanels.css" + "LightPanels.less" ] } From 61ab35a6085584590f59b4e0fce57c4ef7e45639 Mon Sep 17 00:00:00 2001 From: Ryan Duffy Date: Thu, 10 Sep 2015 11:20:38 -0500 Subject: [PATCH 068/195] add default tabindex support for webOS Issue: ENYO-2430 Enyo-DCO-1.1-Signed-off-by: Ryan Duffy (ryan.duffy@lge.com) --- .../AccessibilitySupport.js | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/AccessibilitySupport/AccessibilitySupport.js b/src/AccessibilitySupport/AccessibilitySupport.js index 61e653be2..1acbe760d 100644 --- a/src/AccessibilitySupport/AccessibilitySupport.js +++ b/src/AccessibilitySupport/AccessibilitySupport.js @@ -7,10 +7,10 @@ var dispatcher = require('../dispatcher'), kind = require('../kind'), + platform = require('../platform'), utils = require('../utils'); var defaultObservers = [ - {from: 'tabIndex', to: 'tabindex'}, {from: 'accessibilityDisabled', method: function () { this.setAriaAttribute('aria-hidden', this.accessibilityDisabled ? 'true' : null); }}, @@ -22,14 +22,29 @@ var defaultObservers = [ var role = this.accessibilityAlert && 'alert' || this.accessibilityRole || null; this.setAriaAttribute('role', role); }}, - {path: ['content', 'accessibilityHint', 'accessibilityLabel'], method: function () { - var prefix = this.accessibilityLabel || this.content || null, + {path: ['content', 'accessibilityHint', 'accessibilityLabel', 'tabIndex'], method: function () { + var focusable = this.accessibilityLabel || this.content || this.accessibilityHint || false, + prefix = this.accessibilityLabel || this.content || null, label = this.accessibilityHint && prefix && (prefix + ' ' + this.accessibilityHint) || this.accessibilityHint || this.accessibilityLabel || null; this.setAriaAttribute('aria-label', label); + + // A truthy or zero tabindex will be set directly + if (this.tabIndex || this.tabIndex === 0) { + this.setAriaAttribute('tabindex', this.tabIndex); + } + // The webOS browser will only read nodes with a non-null tabindex so if the node has + // readable content, make it programmably focusable. + else if (focusable && this.tabIndex === undefined && platform.webos) { + this.setAriaAttribute('tabindex', -1); + } + // Otherwise, remove it + else { + this.setAriaAttribute('tabindex', null); + } }} ]; @@ -237,6 +252,16 @@ var AccessibilitySupport = { */ accessibilityPreventScroll: false, + /** + * Sets the `tabindex` of the control. When `undefined` on webOS, it will be set to -1 to enable + * screen reading. A value of `null` (or `undefined` on non-webOS) ensures that no `tabindex` is + * set. + * + * @type {Number} + * @default undefined + * @public + */ + /** * @method * @private From 0eae15c0e8846f36c9da0d8fc0b2770aeb549445 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Thu, 10 Sep 2015 10:39:17 -0700 Subject: [PATCH 069/195] ENYO-2465: Fixing typo. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/LightPanels/LightPanels.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LightPanels/LightPanels.less b/src/LightPanels/LightPanels.less index 86695deef..6ce7c1113 100644 --- a/src/LightPanels/LightPanels.less +++ b/src/LightPanels/LightPanels.less @@ -40,7 +40,7 @@ } &.vertical { - &.fowards { + &.forwards { > .panels-container { > .shifted { transform: translateY(100%); From 15f70b0c92f9da456860e91c6b1b4c5c613b574a Mon Sep 17 00:00:00 2001 From: Jason Robitaille Date: Fri, 11 Sep 2015 12:17:03 -0700 Subject: [PATCH 070/195] ENYO-2504: TouchScrollStrategy should require touch.js --- src/TouchScrollStrategy.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/TouchScrollStrategy.js b/src/TouchScrollStrategy.js index cf20c8bd3..90f63b1cb 100644 --- a/src/TouchScrollStrategy.js +++ b/src/TouchScrollStrategy.js @@ -1,4 +1,5 @@ require('enyo'); +require('./touch'); /** * Contains the declaration for the {@link module:enyo/TouchScrollStrategy~TouchScrollStrategy} kind. From a00ff18312e28d6ba5478be9c28719a0a98ba0bd Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Fri, 11 Sep 2015 10:16:06 -0700 Subject: [PATCH 071/195] ENYO-2491: Ensure that panel transition runs. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/LightPanels/LightPanels.js | 59 +++++++++++++++++----------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index 0c025a7c4..640807195 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -190,6 +190,7 @@ module.exports = kind( this.updateTransforms(); this.orientationChanged(); this.directionChanged(); + this.animateChanged(); if (!this.getPanels().length) this.index = -1; this.setupTransitions(); }, @@ -256,6 +257,12 @@ module.exports = kind( this.updateTransforms(); }, + /** + * @private + */ + animateChanged: function () { + dom.transform(this.$.client, {translateZ: this.animate ? 0 : null}); + }, /** * @private @@ -634,13 +641,8 @@ module.exports = kind( this.cleanUpPanel(prevPanel); this.cleanUpPanel(currPanel); - // Async'ing this as it seems to improve ending transition performance on the TV. - // Requires further investigation into its behavior. - asyncMethod(this, function () { - this.removeClass('transitioning'); - this.transitioning = false; - dom.transform(this.$.client, {translateZ: null}); - }); + this.removeClass('transitioning'); + this.transitioning = false; } }, @@ -676,7 +678,7 @@ module.exports = kind( var panels = this.getPanels(), nextPanel = panels[this.index], currPanel = this._currentPanel, - shiftCurrent; + shiftCurrent, fnSetupClasses, fnTransition; this._indexDirection = 0; @@ -689,43 +691,40 @@ module.exports = kind( if (nextPanel) { this.transitioning = true; - this.addClass('transitioning'); - dom.transform(this.$.client, {translateZ: this.animate ? 0 : null}); + // prepare the panel that will be deactivated if (currPanel) { - currPanel.set('state', States.INACTIVE); + currPanel.set('state', States.DEACTIVATING); if (currPanel.preTransition) currPanel.preTransition(); } - // prepare the panel that will be transitioned into view - nextPanel.removeClass('offscreen'); + // prepare the panel that will be activated + nextPanel.set('state', States.ACTIVATING); if (!nextPanel.generated) nextPanel.render(); - - nextPanel.set('state', States.ACTIVE); if (nextPanel.preTransition) nextPanel.preTransition(); // ensure our panel container is in the correct, pre-transition position this.shiftContainer(-1 * this._indexDirection); - shiftCurrent = this._indexDirection > 0; - // set the correct state for the next panel - nextPanel.set('state', States.ACTIVATING); - nextPanel.addRemoveClass('shifted', shiftCurrent); + fnSetupClasses = this.bindSafely(function () { + this.addClass('transitioning'); + nextPanel.removeClass('offscreen'); + nextPanel.addRemoveClass('shifted', shiftCurrent); + if (currPanel) currPanel.addRemoveClass('shifted', !shiftCurrent); + }); - if (currPanel) { - // set the correct state for the previous panel - currPanel.set('state', States.DEACTIVATING); - currPanel.addRemoveClass('shifted', !shiftCurrent); - } + fnTransition = this.bindSafely(function () { + if (animate) setTimeout(this.bindSafely('applyTransitions', nextPanel, true), 16); + else this.applyTransitions(nextPanel); + }); - // only animate transition if there is more than one panel and/or we're animating - if (animate) { - setTimeout(this.bindSafely(function () { - this.applyTransitions(nextPanel, true); - }), 16); + if (this.generated) { + global.requestAnimationFrame(fnSetupClasses); + global.requestAnimationFrame(fnTransition); } else { - this.applyTransitions(nextPanel); + fnSetupClasses(); + fnTransition(); } } }, From 3c42f285dceb35bc33fbbda9c5ea99e856c1d0af Mon Sep 17 00:00:00 2001 From: Cole Davis Date: Mon, 14 Sep 2015 14:08:47 -0700 Subject: [PATCH 072/195] ensure that the next time the tag renders it will do so properly Enyo-DCO-1.1-Signed-Off-By: Cole Davis (cole.davis@lge.com) --- src/Control/Control.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Control/Control.js b/src/Control/Control.js index 92f4fa62a..63365d2c9 100644 --- a/src/Control/Control.js +++ b/src/Control/Control.js @@ -649,9 +649,6 @@ var Control = module.exports = kind( // sync with the node in styleChanged this.style = this.cssText = node.style.cssText; - // we need to invalidate the style for the delegate - delegate.invalidate(this, 'style'); - // otherwise we have to try and prepare it for the next time it is rendered we // will need to update it because it will not be synchronized } else this.set('style', style + (' ' + prop + ':' + value + ';')); @@ -681,6 +678,10 @@ var Control = module.exports = kind( this.set('style', style); } } + // we need to invalidate the style for the delegate -- regardless of whether or + // not the node exists to ensure that the tag is updated properly the next time + // it is rendered + delegate.invalidate(this, 'style'); return this; }, From 8d78de3c26f100cf86805e9bdbc40ea54f2ad8bc Mon Sep 17 00:00:00 2001 From: Gray Norton Date: Mon, 14 Sep 2015 14:58:56 -0700 Subject: [PATCH 073/195] ENYO-2514: VDR: Always refresh immediately VirtualDataRepeater has, until now, routinely made refresh() an async operation. This behavior was inherited (via copy/paste) from DataRepeater when VDR was first created. It's not apparent that making refresh() async is necessary or useful for VDR, and the async operation is causing problems in at least one instance -- specifically, when the data underlying a NewDataList is modified while the list is hidden, the list isn't refreshed until the list is reshown, and at that time the "old" list contents flash momentarily before being refreshed. We address this issue by making refresh() synchronous for VDR and its subkinds. Enyo-DCO-1.1-Signed-Off-By: Gray Norton (gray.norton@lge.com) --- src/VirtualDataRepeater.js | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/VirtualDataRepeater.js b/src/VirtualDataRepeater.js index de4c61353..309d29b0b 100644 --- a/src/VirtualDataRepeater.js +++ b/src/VirtualDataRepeater.js @@ -70,18 +70,7 @@ module.exports = kind({ this.stabilizeExtent(); - var refresh = this.bindSafely(function() { - this.doIt(); - }); - - // refresh is used as the event handler for - // collection resets so checking for truthy isn't - // enough. it must be true. - if(immediate === true) { - refresh(); - } else { - this.startJob('refreshing', refresh, 16); - } + this.doIt(); }, childForIndex: function(idx) { From 24fc939ce15dec46a3d8715d12b0369798cbc862 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Mon, 14 Sep 2015 15:27:32 -0700 Subject: [PATCH 074/195] ENYO-2491: Remove unnecessary function creation. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/LightPanels/LightPanels.js | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index 640807195..1199df6b4 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -678,7 +678,7 @@ module.exports = kind( var panels = this.getPanels(), nextPanel = panels[this.index], currPanel = this._currentPanel, - shiftCurrent, fnSetupClasses, fnTransition; + shiftCurrent, fnSetupClasses; this._indexDirection = 0; @@ -712,20 +712,12 @@ module.exports = kind( nextPanel.removeClass('offscreen'); nextPanel.addRemoveClass('shifted', shiftCurrent); if (currPanel) currPanel.addRemoveClass('shifted', !shiftCurrent); - }); - - fnTransition = this.bindSafely(function () { if (animate) setTimeout(this.bindSafely('applyTransitions', nextPanel, true), 16); else this.applyTransitions(nextPanel); }); - if (this.generated) { - global.requestAnimationFrame(fnSetupClasses); - global.requestAnimationFrame(fnTransition); - } else { - fnSetupClasses(); - fnTransition(); - } + if (this.generated) global.requestAnimationFrame(fnSetupClasses); + else fnSetupClasses(); } }, From 33cd9138d841685416ebca8f005fa691bedda1b0 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Mon, 14 Sep 2015 16:28:06 -0700 Subject: [PATCH 075/195] PLAT-6855: Allow overriding of cacheViews property. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/ViewPreloadSupport.js | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/ViewPreloadSupport.js b/src/ViewPreloadSupport.js index 8403c2555..ee2eb46ef 100644 --- a/src/ViewPreloadSupport.js +++ b/src/ViewPreloadSupport.js @@ -3,7 +3,8 @@ * @module enyo/ViewPreloadSupport */ var - kind = require('./kind'); + kind = require('./kind'), + utils = require('./utils'); var Control = require('./Control'); @@ -22,19 +23,13 @@ var ViewPreloadSupport = { name: 'enyo.ViewPreloadSupport', /** - * @private + * When `true`, existing views are cached for reuse; otherwise, they are destroyed. + * + * @name cacheViews + * @type {Boolean} + * @default undefined + * @public */ - published: { - - /** - * When `true`, existing views are cached for reuse; otherwise, they are destroyed. - * - * @type {Boolean} - * @default true - * @public - */ - cacheViews: true - }, /** * @method @@ -44,6 +39,9 @@ var ViewPreloadSupport = { return function () { sup.apply(this, arguments); + // initialize properties + this.cacheViews = utils.exists(this.cacheViews) ? this.cacheViews : true; + if (this.cacheViews) { // we don't want viewCache to be added to the client area, so we cache the original // controlParent and restore it afterward From 56a9cf61db643371fab90b157968e351bfe592af Mon Sep 17 00:00:00 2001 From: Gray Norton Date: Mon, 14 Sep 2015 19:55:15 -0700 Subject: [PATCH 076/195] ENYO-2258: Defer reflow when hidden, do it when shown Reflowing a Control when it's hidden generally doesn't make sense, since a) it's wasteful to spend cycles laying out something that can't be seen; and b) the layout may depend on measuring the Control's children, and they can't be accurately measured if they're not showing. We therefore defer reflow in the case where we're hidden, and wait until we're shown. In the case where we are explicitly hidden (i.e., `showing` is `false`), we defer immediately. We can't inexpensively detect the case where we're showing but an ancestor is hidden, so to handle that case we rely on the layout to do its measurements and let us know (by returning `true`) if we need to reflow again the next time we are shown. Enyo-DCO-1.1-Signed-Off-By: Gray Norton (gray.norton@lge.com) --- src/Control/Control.js | 18 ++++++++++++++++++ src/Layout.js | 6 ++++++ 2 files changed, 24 insertions(+) diff --git a/src/Control/Control.js b/src/Control/Control.js index 92f4fa62a..a993e808c 100644 --- a/src/Control/Control.js +++ b/src/Control/Control.js @@ -882,9 +882,27 @@ var Control = module.exports = kind( * @private */ showingChangedHandler: function (sender, event) { + // If we have deferred a reflow, do it now... + if (this.showing && this._needsReflow) { + this.reflow(); + } + + // Then propagate `onShowingChanged` if appropriate return sender === this ? false : !this.showing; }, + /** + * Overriding reflow() so that we can take `showing` into + * account and defer reflowing accordingly. + * + * @private + */ + reflow: function () { + if (this.layout) { + this._needsReflow = this.showing ? this.layout.reflow() : true; + } + }, + /** * @private */ diff --git a/src/Layout.js b/src/Layout.js index 214846b22..a3e7e78cf 100644 --- a/src/Layout.js +++ b/src/Layout.js @@ -74,6 +74,12 @@ module.exports = kind( /** * Called during dynamic measuring layout (i.e., during a resize). * + * May short-circuit and return `true` if the layout needs to be + * redone when the associated Control is next shown. This is useful + * for cases where the Control itself has `showing` set to `true` + * but an ancestor is hidden, and the layout is therefore unable to + * get accurate measurements of the Control or its children. + * * @public */ reflow: function () { From 8c4f9ca07210e571d6646526e9a252a98b6c9d8d Mon Sep 17 00:00:00 2001 From: Gray Norton Date: Tue, 15 Sep 2015 14:55:32 -0700 Subject: [PATCH 077/195] ENYO-2533: Move deferred reset & refresh logic to VDR NewDataList has some logic to defer refreshing and resetting when the list is not showing. This logic would apply equally to VirtualDataRepeater, so we'll move it there. Enyo-DCO-1.1-Signed-Off-By: Gray Norton (gray.norton@lge.com) --- src/NewDataList.js | 61 ++++++++++---------------------------- src/VirtualDataRepeater.js | 45 +++++++++++++++++++++++----- 2 files changed, 52 insertions(+), 54 deletions(-) diff --git a/src/NewDataList.js b/src/NewDataList.js index 44e47ce67..252cc647a 100644 --- a/src/NewDataList.js +++ b/src/NewDataList.js @@ -323,57 +323,26 @@ module.exports = kind({ /** * @private */ - showingChangedHandler: kind.inherit(function (sup) { - return function () { - if (this.needsReset) { - this.reset(); - } - else if (this.needsRefresh) { - this.refresh(); - } - return sup.apply(this, arguments); - }; - }), + collectionResetHandler: function () { + this.calcBoundaries(); + }, /** * @private */ - refresh: kind.inherit(function (sup) { - return function () { - if (this.getAbsoluteShowing()) { - if (arguments[1] === 'reset') { - this.calcBoundaries(); - } - this.needsRefresh = false; - sup.apply(this, arguments); - } - else { - this.needsRefresh = true; - } - }; - }), - - /** - * @private - */ - reset: kind.inherit(function (sup) { + init: kind.inherit(function (sup) { return function () { - var v; - if (this.getAbsoluteShowing()) { - v = (this.direction === 'vertical'); - this.set('scrollTop', 0); - this.set('scrollLeft', 0); - this.set('vertical', v ? 'auto' : 'hidden'); - this.set('horizontal', v ? 'hidden' : 'auto'); - this.calculateMetrics(); - this.calcBoundaries(); - this.first = 0; - this.needsReset = false; - sup.apply(this, arguments); - } - else { - this.needsReset = true; - } + var v = (this.direction === 'vertical'); + + this.set('scrollTop', 0); + this.set('scrollLeft', 0); + this.set('vertical', v ? 'auto' : 'hidden'); + this.set('horizontal', v ? 'hidden' : 'auto'); + this.calculateMetrics(); + this.calcBoundaries(); + this.first = 0; + + sup.apply(this, arguments); }; }) }); diff --git a/src/VirtualDataRepeater.js b/src/VirtualDataRepeater.js index 309d29b0b..d09b4f2db 100644 --- a/src/VirtualDataRepeater.js +++ b/src/VirtualDataRepeater.js @@ -25,9 +25,15 @@ module.exports = kind({ // reorderNodes: false, reset: function () { - this.init(); - this.destroyClientControls(); - this.setExtent(); + if (this.getAbsoluteShowing()) { + this.init(); + this.destroyClientControls(); + this.setExtent(); + this._needsReset = false; + } + else { + this._needsReset = true; + } }, init: function () { @@ -64,15 +70,38 @@ module.exports = kind({ this.notify('numItems', pn, this.numItems); } }, - - refresh: function (immediate) { - if (!this.hasInitialized) return this.reset(); - this.stabilizeExtent(); + refresh: function () { + if (!this.hasInitialized) return this.reset(); - this.doIt(); + if (this.getAbsoluteShowing()) { + if (arguments[1] === 'reset' && typeof this.collectionResetHandler === 'function') { + this.collectionResetHandler(); + } + this.stabilizeExtent(); + this.doIt(); + this._needsRefresh = false; + } + else { + this._needsRefresh = true; + } }, + /** + * @private + */ + showingChangedHandler: kind.inherit(function (sup) { + return function () { + if (this._needsReset) { + this.reset(); + } + else if (this._needsRefresh) { + this.refresh(); + } + return sup.apply(this, arguments); + }; + }), + childForIndex: function(idx) { return this.childrenByIndex[idx]; }, From c4333aa7fdb4f53dc2d3d23c46d67f2f7ca0c292 Mon Sep 17 00:00:00 2001 From: Madala Cholan Satyanarayana Date: Wed, 16 Sep 2015 16:24:31 +0530 Subject: [PATCH 078/195] Added changes related to parallax effect Enyo-DCO-1.1-Signed-off-by:Madala Satyanarayana --- .../AnimationInterfaceSupport.js | 626 ++++++++++-------- lib/AnimationSupport/Fadeable.js | 144 ++-- lib/AnimationSupport/Flippable.js | 122 ++-- lib/AnimationSupport/Slideable.js | 150 +++-- 4 files changed, 561 insertions(+), 481 deletions(-) diff --git a/lib/AnimationSupport/AnimationInterfaceSupport.js b/lib/AnimationSupport/AnimationInterfaceSupport.js index 1631de924..3237a17f1 100644 --- a/lib/AnimationSupport/AnimationInterfaceSupport.js +++ b/lib/AnimationSupport/AnimationInterfaceSupport.js @@ -1,11 +1,11 @@ require('enyo'); var - kind = require('../kind'), - animator = require('./Core'), - frame = require('./Frame'), - utils = require('../utils'), - dispatcher = require('../dispatcher'); + kind = require('../kind'), + animator = require('./Core'), + frame = require('./Frame'), + utils = require('../utils'), + dispatcher = require('../dispatcher'); var extend = kind.statics.extend; @@ -13,289 +13,355 @@ kind.concatenated.push('animation'); var AnimationInterfaceSupport = { - /** - * @private - */ - patterns: [], - - /** - * @private - */ - checkX: 0, - - /** - * @private - */ - checkY: 0, - - /** - * @private - */ - deltaX: 0, - - /** - * @private - */ - deltaY: 0, - - /** - * @private - */ - translateX: 0, - - /** - * @private - */ - translateY: 0, - - /** - * @private - */ - scrollValue: 0, - - /** - * @private - */ - deltaValueX: 0, - - /** - * @private - */ - deltaValueY: 0, - - /** - * @private - */ - checkDragStartX: 0, - - /** - * @private - */ - checkDragStartY: 0, - - /** - * @private - */ - deltaDragValueX: 0, - - /** - * @private - */ - deltaDragValueY: 0, - - /** - * @private - */ - eventArray: [ - "dragstart", - "dragend", - "drag", - "flick", - "down", - "move", - "scroll", - "mousewheel", - "touchstart", - "touchmove", - "touchend" - ], - - /** - * @public - */ - initialize: function () { - var i, eventArrayLength = this.eventArray.length; - for (i = 0; i < eventArrayLength; i++) { - dispatcher.listen(this.node, this.eventArray[i], this.bindSafely(this.detectTheEvent) ); - } - }, - - detectTheEvent: function (inSender, inEvent) { - var eventType = inSender.type; - switch (eventType) { - case "dragstart": - this.touchDragStart(inSender, inEvent, inSender.pageX, inSender.pageY); - break; - case "drag": - this.touchDragMove(inSender, inEvent, inSender.pageX, inSender.pageY); - break; - case "dragend": - this.touchDragEnd(inSender, inEvent); - break; - case "flick": - this.handleMyEvent(inSender, inEvent); - break; - case "down": - this.handleMyEvent(inSender, inEvent); - break; - case "move": - this.handleMyEvent(inSender, inEvent); - break; - case "scroll": - this.scrollEvent(inSender, inEvent); - break; - case "mousewheel": - this.mousewheelEvent(inSender, inEvent); - break; - case "touchstart": - this.touchDragStart(inSender, inEvent, inSender.targetTouches[0].pageX, inSender.targetTouches[0].pageY); - break; - case "touchmove": - this.touchDragMove(inSender, inEvent, inSender.targetTouches[0].pageX, inSender.targetTouches[0].pageY); - break; - case "touchend": - this.touchDragEnd(inSender, inEvent); - break; - default: - this.handleMyEvent(inSender, inEvent); - } - }, - - touchDragStart: function (inSender, inEvent, x, y) { - this.checkX = x; - this.checkY = y; - }, - - touchDragMove: function (inSender, inEvent, x, y) { - var currentX = x, - currentY = y; - - if(currentX !== 0 || currentY !== 0) { - this.deltaValueX = this.checkX - currentX; - this.deltaValueY = this.checkY - currentY; - - //call commonTasks function with delta values - this.commonTasks(this.deltaValueX, this.deltaValueX, this.deltaValueY); - } - }, - - touchDragEnd: function (inSender, inEvent) { - this.checkX = 0; - this.checkY = 0; - this.deltaValueX = 0; - this.deltaValueY = 0; - }, - - /** - * @public - */ - scrollEvent: function (inSender, inEvent) { - var delta = inSender.deltaY, - scrollTop = inSender.target.scrollTop, - scrollLeft = inSender.target.scrollLeft; - - if (this.scrollValue === 0) { - this.scrollValue = inSender.target.scrollTop; - } - - delta = inSender.target.scrollTop - this.scrollValue; - - this.deltaX = scrollLeft - this.deltaX; - this.deltaY = scrollTop - this.deltaY; - this.scrollValue = scrollTop; - - this.translateX = this.translateX + this.deltaX; - this.translateY = this.translateY + this.deltaY; - - //call commonTasks function with delta values - this.commonTasks(delta, (this.translateX), (this.translateY)); - - this.deltaX = scrollLeft; - this.deltaY = scrollTop; - }, - - /** - * @public - */ - mousewheelEvent: function (inSender, inEvent) { - var delta = inSender.deltaY, - deltaX = inSender.wheelDeltaX, - deltaY = inSender.wheelDeltaY; - - this.translateX = this.translateX + deltaX; - this.translateY = this.translateY + deltaY; - //call commonTasks function with delta values - this.commonTasks(delta, (-1 * this.translateX), (-1 * this.translateY)); - }, - - /** - * @public - */ - commonTasks: function (delta, deltax, deltay) { - if (delta !== 0) { - delta = delta / Math.abs(delta); - } - - //Call specific interface - if (patterns[0].name ==="Fadeable") { - patterns[0].fadeByDelta.call(this, delta); - } else if (patterns[0].name === "Flippable") { - patterns[0].doFlip.call(this, delta); - } else if (patterns[0].name === "Slideable") { - if (event.type === "mousewheel") { - deltax = deltay; - } - patterns[0].slide.call(this, (-1 * deltax) , (-1 * deltay) , 0 ); - } - this.start(true); - }, - - /** - * @public - */ - handleMyEvent: function (inSender, inEvent) { - /*TODO:*/ - }, - - /** - * @public - */ - commitAnimation: function () { - var i, len; - if (patterns && Object.prototype.toString.call(patterns) === "[object Array]") { - len = patterns.length; - for (i = 0; i < len; i++) { - if (typeof patterns[i].triggerEvent === 'function') { - //patterns[i].triggerEvent(); - } - - } - } - }, - - /** - * @private - */ - rendered: kind.inherit (function (sup) { - return function() { - sup.apply(this, arguments); - this.initialize(); - }; - }) + /** + * @private + */ + patterns: [], + + /** + * @private + */ + checkX: 0, + + /** + * @private + */ + checkY: 0, + + /** + * @private + */ + deltaX: 0, + + /** + * @private + */ + deltaY: 0, + + /** + * @private + */ + translateX: 0, + + /** + * @private + */ + translateY: 0, + + /** + * @private + */ + scrollValue: 0, + + /** + * @private + */ + deltaValueX: 0, + + /** + * @private + */ + deltaValueY: 0, + + /** + * @private + */ + checkDragStartX: 0, + + /** + * @private + */ + checkDragStartY: 0, + + /** + * @private + */ + deltaDragValueX: 0, + + /** + * @private + */ + deltaDragValueY: 0, + + /** + * @private + */ + setAnimateOne: 0, + + /** + * @private + */ + setAnimateTwo: 0, + + /** + * @private + */ + setAnimateThree: 0, + + /** + * @private + */ + eventArray: [ + "dragstart", + "dragend", + "drag", + "flick", + "down", + "move", + "scroll", + "mousewheel", + "touchstart", + "touchmove", + "touchend" + ], + + /** + * @public + */ + initialize: function() { + var i, eventArrayLength = this.eventArray.length; + for (i = 0; i < eventArrayLength; i++) { + dispatcher.listen(this.node, this.eventArray[i], this.bindSafely(this.detectTheEvent)); + } + }, + + /** + * @public + */ + detectTheEvent: function(inSender, inEvent) { + var eventType = inSender.type; + switch (eventType) { + case "dragstart": + this.touchDragStart(inSender, inEvent, inSender.pageX, inSender.pageY); + break; + case "drag": + this.touchDragMove(inSender, inEvent, inSender.pageX, inSender.pageY); + break; + case "dragend": + this.touchDragEnd(inSender, inEvent); + break; + case "flick": + this.handleMyEvent(inSender, inEvent); + break; + case "down": + this.handleMyEvent(inSender, inEvent); + break; + case "move": + this.handleMyEvent(inSender, inEvent); + break; + case "scroll": + this.scrollEvent(inSender, inEvent); + break; + case "mousewheel": + this.mousewheelEvent(inSender, inEvent); + break; + case "touchstart": + this.touchDragStart(inSender, inEvent, inSender.targetTouches[0].pageX, inSender.targetTouches[0].pageY); + break; + case "touchmove": + this.touchDragMove(inSender, inEvent, inSender.targetTouches[0].pageX, inSender.targetTouches[0].pageY); + break; + case "touchend": + this.touchDragEnd(inSender, inEvent); + break; + default: + this.handleMyEvent(inSender, inEvent); + } + }, + + /** + * @public + */ + touchDragStart: function(inSender, inEvent, x, y) { + this.checkX = x; + this.checkY = y; + }, + + /** + * @public + */ + touchDragMove: function(inSender, inEvent, x, y) { + var currentX = x, + currentY = y; + + if (currentX != 0 || currentY != 0) { + this.deltaValueX = this.checkX - currentX; + + this.checkX = currentX; // set the initial position to the current position while moving + + this.deltaValueY = this.checkY - currentY; + + this.checkY = currentY; // set the initial position to the current position while moving + + //call commonTasks function with delta values + this.translateX = this.translateX + this.deltaValueX; + this.translateY = this.translateY + this.deltaValueY; + + this.setAnimateOne = this.translateX; + this.setAnimateTwo = this.translateX; + this.setAnimateThree = this.translateY; + + } + + }, + + /** + * @public + */ + touchDragEnd: function(inSender, inEvent, x, y) { + this.checkX = 0; + this.checkY = 0; + this.deltaValueX = 0; + this.deltaValueY = 0; + }, + + /** + * @public + */ + scrollEvent: function(inSender, inEvent) { + var delta = inSender.deltaY, + scrollTop = inSender.target.scrollTop, + scrollLeft = inSender.target.scrollLeft; + + if (this.scrollValue === 0) { + this.scrollValue = inSender.target.scrollTop; + } + + delta = inSender.target.scrollTop - this.scrollValue; + + this.deltaX = scrollLeft - this.deltaX; + this.deltaY = scrollTop - this.deltaY; + this.scrollValue = scrollTop; + + this.translateX = this.translateX + this.deltaX; + this.translateY = this.translateY + this.deltaY; + + //call commonTasks function with delta values + this.setAnimateOne = delta; + this.setAnimateTwo = this.translateX; + this.setAnimateThree = this.translateY; + + + this.deltaX = scrollLeft; + this.deltaY = scrollTop; + }, + + /** + * @public + */ + mousewheelEvent: function(inSender, inEvent) { + var delta = inSender.deltaY, + deltaX = inSender.wheelDeltaX, + deltaY = inSender.wheelDeltaY; + + this.translateX = this.translateX + deltaX; + this.translateY = this.translateY + deltaY; + + //call commonTasks function with delta values + this.setAnimateOne = delta; + this.setAnimateTwo = (-1 * (this.translateX)); + this.setAnimateThree = (-1 * (this.translateY)); + if (patterns[0].name === "Slideable") { + this.setAnimateTwo = this.setAnimateThree; + } + + + }, + + /** + * @public + */ + commonTasks: function(delta, deltax, deltay) { + var patternsLength = patterns.length; + if (delta !== 0) { + delta = delta / Math.abs(delta); + } + //Call specific interface + for (var i = 0; i < patternsLength; i++) { + if (patterns[i].name === "Fadeable") { + patterns[i].fadeByDelta.call(this, delta); + } else if (patterns[i].name === "Flippable") { + patterns[i].doFlip.call(this, delta); + } else if (patterns[i].name === "Slideable") { + if (this.parallax === true) { + for (var j = 0; j < this.children.length; j++) { + var current = this.children[j]; + animator.trigger(current); + patterns[i].slide.call(current, (-1 * deltax / current.speed), (-1 * deltay / current.speed), 0); + current.start(true); + } + } else { + patterns[i].slide.call(this, (-1 * deltax), (-1 * deltay), 0); + } + } + if (patterns[i].name !== "Slideable") { + this.setAnimateOne = 0; + this.setAnimateTwo = 0; + this.setAnimateThree = 0; + } + } + this.start(true); + }, + + /** + * @public + */ + handleMyEvent: function(inSender, inEvent) { + /*TODO:*/ + }, + + /** + * @public + */ + commitAnimation: function(x, y, z) { + var i, len; + + if (patterns && Object.prototype.toString.call(patterns) === "[object Array]") { + len = patterns.length; + for (i = 0; i < len; i++) { + if (typeof patterns[i].triggerEvent === 'function') { + //patterns[i].triggerEvent(); + + } + this.commonTasks(this.setAnimateOne, this.setAnimateTwo, this.setAnimateThree); + + } + } + }, + + /** + * @private + */ + rendered: kind.inherit(function(sup) { + return function() { + sup.apply(this, arguments); + this.initialize(); + }; + }) }; module.exports = AnimationInterfaceSupport; /** - Hijacking original behaviour as in other Enyo supports. + Hijacking original behaviour as in other Enyo supports. */ var sup = kind.concatHandler; /** * @private */ -kind.concatHandler = function (ctor, props, instance) { - sup.call(this, ctor, props, instance); - var aPattern = props.pattern; - if (aPattern && Object.prototype.toString.call(aPattern) === "[object Array]") { - var proto = ctor.prototype || ctor; - extend(AnimationInterfaceSupport, proto); - - patterns = aPattern; - var len = patterns.length; - for (var i = 0; i < len; i++) { - extend(patterns[i], proto); - } - animator.register(proto); - } -}; \ No newline at end of file +kind.concatHandler = function(ctor, props, instance) { + sup.call(this, ctor, props, instance); + var aPattern = props.pattern; + if (aPattern && Object.prototype.toString.call(aPattern) === "[object Array]") { + var proto = ctor.prototype || ctor; + extend(AnimationInterfaceSupport, proto); + + patterns = aPattern; + var len = patterns.length; + for (var i = 0; i < len; i++) { + extend(patterns[i], proto); + } + animator.register(proto); + } +}; diff --git a/lib/AnimationSupport/Fadeable.js b/lib/AnimationSupport/Fadeable.js index 9ef58df4f..4e2d2dc82 100644 --- a/lib/AnimationSupport/Fadeable.js +++ b/lib/AnimationSupport/Fadeable.js @@ -1,79 +1,87 @@ var - kind = require('../kind'), - animation = require('./Core'); + kind = require('../kind'), + animation = require('./Core'); /** -* Interface to achieve fade animation -* -* @module enyo/AnimationSupport/Fadeable -* @public -*/ + * Interface to achieve fade animation + * + * @module enyo/AnimationSupport/Fadeable + * @public + */ module.exports = { - /** - * @private - */ - name: 'Fadeable', + /** + * @private + */ + name: 'Fadeable', - /** - * To start animation - */ - animate: true, + /** + * To start animation + */ + animate: true, - /** - * @private - */ - fadableValue: 0, + /** + * @private + */ + fadableValue: 0, - /** - * @public - * Make the character invisible - */ - invisible: function () { - this.addAnimation({opacity: 0}); - }, + /** + * @public + * Make the character invisible + */ + invisible: function() { + this.addAnimation({ + opacity: 0 + }); + }, - /** - * @public - * Make the character transparent - * @default 0.5 - * @parameter value - set transparency value - */ - transparent: function (value) { - value = value || 0.5; - this.addAnimation({opacity: value}); - }, + /** + * @public + * Make the character transparent + * @default 0.5 + * @parameter value - set transparency value + */ + transparent: function(value) { + value = value || 0.5; + this.addAnimation({ + opacity: value + }); + }, - /** - * @public - * Make the character visible - */ - opaque: function () { - this.addAnimation({opacity: 1}); - }, - - /** - * @public - * Fade element based on event trigger - */ - fadeByDelta: function (deltaValue) { - if (deltaValue !== 0) { - this.fadableValue = this.fadableValue + deltaValue * 0.1; - if (this.fadableValue <= 0) { - this.fadableValue=0; - } else if (this.fadableValue >=1) { - this.fadableValue=1; - } - } - this.addAnimation({opacity:this.fadableValue }); - }, + /** + * @public + * Make the character visible + */ + opaque: function() { + this.addAnimation({ + opacity: 1 + }); + }, - /** - * @public - * Bubble the fadeable event - */ - triggerEvent: function (e) { - console.log("TODO: Trigger the fadeable event"+e); - //this.doFadeStart(); - } -}; \ No newline at end of file + /** + * @public + * Fade element based on event trigger + */ + fadeByDelta: function(deltaValue) { + if (deltaValue !== 0) { + this.fadableValue = this.fadableValue + deltaValue * 0.1; + if (this.fadableValue <= 0) { + this.fadableValue = 0; + } else if (this.fadableValue >= 1) { + this.fadableValue = 1; + } + } + this.addAnimation({ + opacity: this.fadableValue + }); + }, + + /** + * @public + * Bubble the fadeable event + */ + triggerEvent: function(e) { + console.log("TODO: Trigger the fadeable event" + e); + //this.doFadeStart(); + } +}; diff --git a/lib/AnimationSupport/Flippable.js b/lib/AnimationSupport/Flippable.js index f346c22ad..aaa5c727c 100644 --- a/lib/AnimationSupport/Flippable.js +++ b/lib/AnimationSupport/Flippable.js @@ -1,71 +1,71 @@ var - kind = require('../kind'), - animation = require('./Core'); + kind = require('../kind'), + animation = require('./Core'); /** -* Interface to achieve flip animation -* -* @module enyo/AnimationSupport/Flippable -* @public -*/ + * Interface to achieve flip animation + * + * @module enyo/AnimationSupport/Flippable + * @public + */ module.exports = { - /** - * @private - */ - name: 'Flippable', + /** + * @private + */ + name: 'Flippable', - /** - * To start animation - */ - animate: true, + /** + * To start animation + */ + animate: true, - /** - * Specifies the direction of flip. Accepted value are 'X', 'Y', 'Z' - * - * @type {String} - * @default 'X' - * @public - */ - flipDirection: 'X', + /** + * Specifies the direction of flip. Accepted value are 'X', 'Y', 'Z' + * + * @type {String} + * @default 'X' + * @public + */ + flipDirection: 'X', - /** - * Specifies the flip up-to angle. Accepted value are in degree - * - * @type {Number} - * @default '0' - * @public - */ - flipAngle: 0, + /** + * Specifies the flip up-to angle. Accepted value are in degree + * + * @type {Number} + * @default '0' + * @public + */ + flipAngle: 0, - /** - * @public - * apply animation to the flippable DOM object - */ - doFlip: function(deltaValue) { - this.setAxis(deltaValue); - }, + /** + * @public + * apply animation to the flippable DOM object + */ + doFlip: function(deltaValue) { + this.setAxis(deltaValue); + }, - /** - * @public - * set axis of rotation of flippable DOM object - */ - setAxis: function(delta) { - var css = {}; - var dir = ""; - this.flipAngle = this.flipAngle + delta * 10; - switch(this.flipDirection) { - case "X": - dir = "1,0,0,"+ this.flipAngle; - break; - case "Y": - dir = "0,1,0,"+ this.flipAngle; - break; - case "Z": - dir = "0,0,1,"+ this.flipAngle; - break; - } - css["rotate"] = dir; - this.addAnimation(css); - } -}; \ No newline at end of file + /** + * @public + * set axis of rotation of flippable DOM object + */ + setAxis: function(delta) { + var css = {}; + var dir = ""; + this.flipAngle = this.flipAngle + delta * 10; + switch (this.flipDirection) { + case "X": + dir = "1,0,0," + this.flipAngle; + break; + case "Y": + dir = "0,1,0," + this.flipAngle; + break; + case "Z": + dir = "0,0,1," + this.flipAngle; + break; + } + css["rotate"] = dir; + this.addAnimation(css); + } +}; diff --git a/lib/AnimationSupport/Slideable.js b/lib/AnimationSupport/Slideable.js index d33b31e6c..09ed9acec 100644 --- a/lib/AnimationSupport/Slideable.js +++ b/lib/AnimationSupport/Slideable.js @@ -1,81 +1,87 @@ var - kind = require('../kind'), - animation = require('./Core'); + kind = require('../kind'), + animation = require('./Core'); /** -* Interface to achieve slide animation -* -* @module enyo/AnimationSupport/Slideable -* @public -*/ + * Interface to achieve slide animation + * + * @module enyo/AnimationSupport/Slideable + * @public + */ module.exports = { - - /** - * @private - */ - name: 'Slideable', - /** - * To start animation - */ - animate: true, + /** + * @private + */ + name: 'Slideable', - /** - * @public - * slide animation in left direction - * @parameter: slideDistance - distance in pixels to slide in left direction - */ - left: function (slideDistance) { - this.slide((-1 * slideDistance), 0, 0); - }, + /** + * To start animation + */ + animate: true, - /** - * @public - * slide animation in right direction - * @parameter: slideDistance - distance in pixels to slide in right direction - */ - right: function (slideDistance) { - this.slide(slideDistance, 0, 0); - }, + /** + * @public + * slide animation in left direction + * @parameter: slideDistance - distance in pixels to slide in left direction + */ + left: function(slideDistance) { + this.slide((-1 * slideDistance), 0, 0); + }, - /** - * @public - * slide animation upward - * @parameter: slideDistance - distance in pixels to slide upward - */ - up: function (slideDistance) { - this.slide(0, (-1 * slideDistance), 0); - }, + /** + * @public + * slide animation in right direction + * @parameter: slideDistance - distance in pixels to slide in right direction + */ + right: function(slideDistance) { + this.slide(slideDistance, 0, 0); + }, - /** - * @public - * slide animation downward - * @parameter: slideDistance - distance in pixels to slide downward - */ - down: function (slideDistance) { - this.slide(0, slideDistance, 0); - }, - - /** - * @public - * slide animation in custom direction - * @parameter: x - css property to slide in x-axis direction - * @parameter: y - css property to slide in y-axis direction - * @parameter: z - css property to slide in z-axis direction - */ - slide: function (x, y, z) { - x = x || 0; - y = y || 0; - z = z || 0; - switch (this.direction) { - case "horizontal" : - this.addAnimation({translate: x + "," + 0 + "," + 0}); - break; - case "vertical" : - this.addAnimation({translate: 0 + "," + y + "," + 0}); - break; - default: - this.addAnimation({translate: x + "," + y + "," + z}); - } - } -}; \ No newline at end of file + /** + * @public + * slide animation upward + * @parameter: slideDistance - distance in pixels to slide upward + */ + up: function(slideDistance) { + this.slide(0, (-1 * slideDistance), 0); + }, + + /** + * @public + * slide animation downward + * @parameter: slideDistance - distance in pixels to slide downward + */ + down: function(slideDistance) { + this.slide(0, slideDistance, 0); + }, + + /** + * @public + * slide animation in custom direction + * @parameter: x - css property to slide in x-axis direction + * @parameter: y - css property to slide in y-axis direction + * @parameter: z - css property to slide in z-axis direction + */ + slide: function(x, y, z) { + x = x || 0; + y = y || 0; + z = z || 0; + switch (this.direction) { + case "horizontal": + this.addAnimation({ + translate: x + "," + 0 + "," + 0 + }); + break; + case "vertical": + this.addAnimation({ + translate: 0 + "," + y + "," + 0 + }); + break; + default: + this.addAnimation({ + translate: x + "," + y + "," + z + }); + } + } +}; From 6e577add17bbb907ab05050d502990e01be6415e Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Wed, 16 Sep 2015 11:12:07 -0700 Subject: [PATCH 079/195] ENYO-2541: Remove popOnForward. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/LightPanels/LightPanels.js | 55 +++++++++++++++------------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index 1199df6b4..ba1bc827c 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -127,15 +127,6 @@ module.exports = kind( */ popOnBack: true, - /** - * When `true`, previous panels are automatically popped when moving forwards. - * - * @type {Boolean} - * @default false - * @public - */ - popOnForward: false, - /** * The amount of time, in milliseconds, to run the transition animation between panels. * @@ -450,41 +441,43 @@ module.exports = kind( }, /** - * Destroys panels whose index is either greater than, or less than, the specified value, + * Removes panels whose index is either greater than, or less than, the specified value, * depending on the direction. * - * @param {Number} index - Index at which to start destroying panels. - * @param {Number} direction - The direction in which we want to destroy panels. A negative - * number signifies popping backwards, otherwise we pop forwards. + * @param {Number} index - Index at which to start removing panels. + * @param {Number} [direction] - The direction in which we are changing indices. A negative + * value signifies that we are moving backwards, and want to remove panels whose indices are + * greater than the current index. Conversely, a positive value signifies that we are moving + * forwards, and panels whose indices are less than the current index should be removed. To + * maintain backwards-compatibility with the existing API, this parameter is optional and, if + * not specified, will default to the popOnBack behavior. * @public */ - popPanels: function (index, direction) { - var panels = this.getPanels(); + removePanels: function (index, direction) { + var panels = this.getPanels(), + i; if (direction < 0) { - while (panels.length > index + 1 && index >= 0) { - this.popPanel(panels.length - 1); + for (i = panels.length - 1; i > index; i--) { + this.removePanel(panels[i]); } } else { - for (var panelIndex = index - 1; panelIndex >= 0; panelIndex--) { - this.popPanel(panelIndex, true); + for (i = 0; i < index; i++) { + this.removePanel(panels[i], true); } } }, /** - * Destroys the specified panel. + * Removes the specified panel. * - * @param {Number} index - The index of the panel to destroy. - * @param {Boolean} [preserve] - If {@link module:enyo/LightPanels~LightPanels#cacheViews} is `true`, this - * value is used to determine whether or not to preserve the current panel's position in - * the component hierarchy and on the screen, when caching. - * @public + * @param {Object} panel - The panel to remove. + * @param {Boolean} [preserve] - If {@link module:enyo/LightPanels~LightPanels#cacheViews} is + * `true`, this value is used to determine whether or not to preserve the current panel's + * position in the component hierarchy and on the screen, when caching. + * @private */ - popPanel: function (index, preserve) { - var panels = this.getPanels(), - panel = panels[index]; - + removePanel: function (panel, preserve) { if (panel) { if (this.cacheViews) { this.cacheView(panel, preserve); @@ -627,8 +620,8 @@ module.exports = kind( currPanel = this._currentPanel; if ((this._indexDirection < 0 && (this.popOnBack || this.cacheViews) && this.index < this.getPanels().length - 1) || - (this._indexDirection > 0 && (this.popOnForward || this.cacheViews) && this.index > 0)) { - this.popPanels(this.index, this._indexDirection); + (this._indexDirection > 0 && this.cacheViews && this.index > 0)) { + this.removePanels(this.index, this._indexDirection); } if (prevPanel) { From 67d80819b22bd618d9805acfdb5e767569542eeb Mon Sep 17 00:00:00 2001 From: Jenkins Date: Wed, 16 Sep 2015 16:53:45 -0700 Subject: [PATCH 080/195] Update version string to 2.6.0-pre.16 --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 09d5e3a79..e176aaf94 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ 'use strict'; exports = module.exports = require('./src/options'); -exports.version = '2.6.0-pre.14.1'; +exports.version = '2.6.0-pre.16'; From a092db149da951309f081116c2bcbe2a6a9b375b Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Thu, 17 Sep 2015 13:59:40 -0700 Subject: [PATCH 081/195] JSDoc cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/LightPanels/LightPanels.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index 1199df6b4..f3039c519 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -157,20 +157,20 @@ module.exports = kind( /** * The orientation of the panels. Possible values from - * {@link module:moonstone/LightPanels~Orientation}. + * {@link module:enyo/LightPanels~LightPanels#Orientation}. * * @type {String} - * @default Orientation.HORIZONTAL + * @default {@link module:enyo/LightPanels~LightPanels#Orientation.HORIZONTAL} * @public */ orientation: Orientation.HORIZONTAL, /** * The direction of the panel movement. Possible values from - * {@link module:moonstone/LightPanels~Direction}. + * {@link module:enyo/LightPanels~LightPanels#Direction}. * * @type {String} - * @default Direction.FORWARDS + * @default {@link module:enyo/LightPanels~LightPanels#Direction.FORWARDS} * @public */ direction: Direction.FORWARDS, @@ -808,4 +808,4 @@ module.exports = kind( module.exports.Panel = LightPanel; module.exports.Direction = Direction; -module.exports.Orientation = Orientation; \ No newline at end of file +module.exports.Orientation = Orientation; From d70f64d7e7137a559c4e16d42b01bd9dedc464a7 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Wed, 16 Sep 2015 14:07:10 -0700 Subject: [PATCH 082/195] ENYO-2536: Properly execute animation style rAF after the setup rAF. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/LightPanels/LightPanels.js | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index 1199df6b4..1132a2a5c 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -678,7 +678,7 @@ module.exports = kind( var panels = this.getPanels(), nextPanel = panels[this.index], currPanel = this._currentPanel, - shiftCurrent, fnSetupClasses; + shiftCurrent, fnInitiateTransition; this._indexDirection = 0; @@ -703,21 +703,24 @@ module.exports = kind( if (!nextPanel.generated) nextPanel.render(); if (nextPanel.preTransition) nextPanel.preTransition(); - // ensure our panel container is in the correct, pre-transition position - this.shiftContainer(-1 * this._indexDirection); - shiftCurrent = this._indexDirection > 0; + fnInitiateTransition = this.bindSafely(function (timestamp) { + + // ensure our panel container is in the correct, pre-transition position + this.shiftContainer(-1 * this._indexDirection); + shiftCurrent = this._indexDirection > 0; - fnSetupClasses = this.bindSafely(function () { this.addClass('transitioning'); nextPanel.removeClass('offscreen'); nextPanel.addRemoveClass('shifted', shiftCurrent); if (currPanel) currPanel.addRemoveClass('shifted', !shiftCurrent); - if (animate) setTimeout(this.bindSafely('applyTransitions', nextPanel, true), 16); - else this.applyTransitions(nextPanel); + + // timestamp will be truthy if this is triggered from a rAF + if (timestamp) global.requestAnimationFrame(this.bindSafely('applyTransitions', nextPanel, animate)); + else this.applyTransitions(nextPanel, animate); }); - if (this.generated) global.requestAnimationFrame(fnSetupClasses); - else fnSetupClasses(); + if (!this.generated || !animate) fnInitiateTransition(); + else global.requestAnimationFrame(fnInitiateTransition); } }, From 89901bf53600be01c71f30bd0a59961ee4e83eb9 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Thu, 17 Sep 2015 16:39:11 -0700 Subject: [PATCH 083/195] ENYO-2541: Update documentation. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/LightPanels/LightPanels.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index ba1bc827c..2247b2524 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -445,12 +445,10 @@ module.exports = kind( * depending on the direction. * * @param {Number} index - Index at which to start removing panels. - * @param {Number} [direction] - The direction in which we are changing indices. A negative - * value signifies that we are moving backwards, and want to remove panels whose indices are - * greater than the current index. Conversely, a positive value signifies that we are moving - * forwards, and panels whose indices are less than the current index should be removed. To - * maintain backwards-compatibility with the existing API, this parameter is optional and, if - * not specified, will default to the popOnBack behavior. + * @param {Number} direction - The direction in which we are changing indices. A negative value + * signifies that we are moving backwards, and want to remove panels whose indices are greater + * than the current index. Conversely, a positive value signifies that we are moving forwards, + * and panels whose indices are less than the current index should be removed. * @public */ removePanels: function (index, direction) { From cf4fe9c876c3893890b59bc0d391ce3ce8e48164 Mon Sep 17 00:00:00 2001 From: "bongsub.kim" Date: Mon, 21 Sep 2015 11:40:27 +0900 Subject: [PATCH 084/195] PLAT-8119: Add or modify transition related functions for safari browser https://jira2.lgsvl.com/browse/PLAT-8119 Enyo-DCO-1.1-Signed-off-by: Bongsub Kim bongsub.kim@lgepartner.com Change-Id: Ia5e83dc042d55cbfec3f44c00764f8cc56c32ec0 --- src/LightPanels/LightPanels.js | 5 +++-- src/LightPanels/LightPanels.less | 8 ++++++++ src/ViewPreloadSupport.js | 6 +++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index f3039c519..b814502a3 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -8,6 +8,7 @@ require('enyo'); var kind = require('../kind'), dom = require('../dom'), + animation = require('../animation'), asyncMethod = require('../utils').asyncMethod; var @@ -179,7 +180,7 @@ module.exports = kind( * @private */ tools: [ - {kind: Control, name: 'client', classes: 'panels-container', ontransitionend: 'transitionFinished'} + {kind: Control, name: 'client', classes: 'panels-container', ontransitionend: 'transitionFinished', onwebkitTransitionEnd: 'transitionFinished'} ], /** @@ -716,7 +717,7 @@ module.exports = kind( else this.applyTransitions(nextPanel); }); - if (this.generated) global.requestAnimationFrame(fnSetupClasses); + if (this.generated) animation.requestAnimationFrame(fnSetupClasses); else fnSetupClasses(); } }, diff --git a/src/LightPanels/LightPanels.less b/src/LightPanels/LightPanels.less index 6ce7c1113..669b80087 100644 --- a/src/LightPanels/LightPanels.less +++ b/src/LightPanels/LightPanels.less @@ -12,19 +12,23 @@ > .panels-container { > .shifted { transform: translateX(100%); + -webkit-transform: translateX(100%); } } } &.backwards { > .panels-container { transform: translateX(-50%); + -webkit-transform: translateX(-50%); > * { transform: translateX(100%); + -webkit-transform: translateX(100%); } > .shifted { transform: translateX(0%); + -webkit-transform: translateX(0%); } } } @@ -44,19 +48,23 @@ > .panels-container { > .shifted { transform: translateY(100%); + -webkit-transform: translateY(100%); } } } &.backwards { > .panels-container { transform: translateY(-50%); + -webkit-transform: translateY(-50%); > * { transform: translateY(100%); + -webkit-transform: translateY(100%); } > .shifted { transform: translateY(0%); + -webkit-transform: translateY(0%); } } } diff --git a/src/ViewPreloadSupport.js b/src/ViewPreloadSupport.js index 8403c2555..fc554c120 100644 --- a/src/ViewPreloadSupport.js +++ b/src/ViewPreloadSupport.js @@ -118,7 +118,11 @@ var ViewPreloadSupport = { // The panel could have already been removed from DOM and torn down if we popped when // moving forward. if (view.node) { - view.node.remove(); + if (!view.node.remove) { // Polyfill remove for safari browser + view.parent.node.removeChild(view.node); + } else { + view.node.remove(); + } view.teardownRender(true); } From ffff521e03b9931562f209d03a6ae34225c320f1 Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Mon, 21 Sep 2015 11:46:14 -0700 Subject: [PATCH 085/195] JSDoc cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/Store.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Store.js b/src/Store.js index a0c8dc226..a0cd310a4 100644 --- a/src/Store.js +++ b/src/Store.js @@ -47,7 +47,7 @@ var BaseStore = kind({ */ /** -* An anonymous kind used internally for the singleton {@link module:enyo/store~store}. +* An anonymous kind used internally for the singleton {@link module:enyo/Store~Store}. * * @class Store * @mixes module:enyo/EventEmitter @@ -166,7 +166,7 @@ var Store = kind( }, /** - * Determines, from the given parameters, whether the [store]{@link module:enyo/store~store} + * Determines, from the given parameters, whether the [store]{@link module:enyo/Store~Store} * has a specific [model]{@link module:enyo/Model~Model}. * * @param {(Function|module:enyo/Model~Model)} ctor Can be the constructor for an {@link module:enyo/Model~Model} @@ -176,7 +176,7 @@ var Store = kind( * constructor, this may be a [Number]{@glossary Number} or a [String]{@glossary String} * representing a [primaryKey]{@link module:enyo/Model~Model#primaryKey} for the given model, or an * instance of a model. - * @returns {Boolean} Whether or not the [store]{@link module:enyo/store~store} has the given + * @returns {Boolean} Whether or not the [store]{@link module:enyo/Store~Store} has the given * [model]{@link module:enyo/Model~Model}. * @public */ From f6ff7d41028603f6d492ed4960f9800ac83a07ab Mon Sep 17 00:00:00 2001 From: "art.dahm" Date: Mon, 21 Sep 2015 14:52:46 -0700 Subject: [PATCH 086/195] ENYO-2441 Fix failing Enyo unit tests and re-enable testing on Travis Enyo-DCO-1.1-Signed-off-by: Art Dahm art.dahm@lge.com --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a899b0142..a553f2a63 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ } ], "scripts": { - "test": "./node_modules/.bin/gulp" + "test": "./node_modules/.bin/gulp test" }, "repository": { "type": "git", From 7cf9040545d43bbbcc627a45c39ce617f35ce8a9 Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Mon, 21 Sep 2015 15:09:29 -0700 Subject: [PATCH 087/195] JSDoc cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/LightPanels/LightPanel.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/LightPanels/LightPanel.js b/src/LightPanels/LightPanel.js index cb556af96..fe334da90 100644 --- a/src/LightPanels/LightPanel.js +++ b/src/LightPanels/LightPanel.js @@ -1,3 +1,8 @@ +/** +* Contains the declaration for the {@link module:enyo/LightPanels~LightPanel} kind. +* @module enyo/LightPanels +*/ + require('enyo'); var @@ -8,7 +13,7 @@ var /** * @enum {Number} -* @memberof module:enyo/LightPanel~LightPanel +* @memberof module:enyo/LightPanels~LightPanel * @public */ var States = { @@ -19,7 +24,7 @@ var States = { }; /** -* A light-weight panels implementation that has basic support for side-to-side transitions +* A lightweight panels implementation that has basic support for side-to-side transitions * between child components. * * @class LightPanel @@ -28,7 +33,7 @@ var States = { * @public */ module.exports = kind( - /** @lends module:enyo/LightPanel~LightPanel.prototype */ { + /** @lends module:enyo/LightPanels~LightPanel.prototype */ { /** * @private @@ -41,9 +46,9 @@ module.exports = kind( kind: Control, /** - * The current [state]{@link module:enyo/LightPanel~LightPanel#States}. + * The current [state]{@link module:enyo/LightPanels~LightPanel#States}. * - * @type {module:enyo/LightPanel~LightPanel#States} + * @type {module:enyo/LightPanels~LightPanel#States} * @default null * @public */ From d16b51cb45d024957122f8a439dc1dbb444fece5 Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Mon, 21 Sep 2015 15:09:44 -0700 Subject: [PATCH 088/195] JSDoc cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/RelationalModel/RelationalModel.js | 11 ++++++----- src/RelationalModel/manyToMany.js | 11 ++++++++--- src/RelationalModel/toMany.js | 6 +++--- src/RelationalModel/toOne.js | 7 ++++++- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/RelationalModel/RelationalModel.js b/src/RelationalModel/RelationalModel.js index 081f027c5..90cd16d08 100644 --- a/src/RelationalModel/RelationalModel.js +++ b/src/RelationalModel/RelationalModel.js @@ -42,13 +42,14 @@ var RelationalModel = module.exports = kind( /** * An [array]{@glossary Array} declaring relationships of this - * [model]{@link module:enyo/RelationalModel~RelationalModel} to other models. These are - * [hashes]{@glossary Object} of corresponding + * [model]{@link module:enyo/RelationalModel~RelationalModel} to other models. + * These are [hashes]{@glossary Object} of corresponding * [properties]{@link module:enyo/RelationalModel~RelationOptions} used to define * and configure individual relations. Relations may be of the type - * {@link module:enyo/toOne~toOne}, {@link module:enyo/toMany~toMany}, or {@link module:enyo/manyToMany~manyToMany}. - * Each relation must include a `key` property that is the name of the - * local [attribute]{@link module:enyo/Model~Model#attributes}. For example: + * {@link module:enyo/RelationalModel~toOne}, {@link module:enyo/RelationalModel~toMany}, + * or {@link module:enyo/RelationalModel~manyToMany}. Each relation must include a + * `key` property that is the name of the local + * [attribute]{@link module:enyo/Model~Model#attributes}. For example: * * ```javascript * var diff --git a/src/RelationalModel/manyToMany.js b/src/RelationalModel/manyToMany.js index fe8c543db..2949d9677 100644 --- a/src/RelationalModel/manyToMany.js +++ b/src/RelationalModel/manyToMany.js @@ -1,3 +1,8 @@ +/** +* Contains the declaration for the {@link module:moonstone/RelationalModel~manyToMany} kind. +* @module enyo/RelationalModel +*/ + var kind = require('../kind'); @@ -9,11 +14,11 @@ var * models. This is an internally-used class. * * @class manyToMany -* @extends module:enyo/toMany~toMany +* @extends toMany * @protected */ var manyToMany = module.exports = kind( - /** @lends module:enyo/manyToMany~manyToMany.prototype */ { + /** @lends module:enyo/RelationalModel~manyToMany.prototype */ { /** * @private @@ -29,7 +34,7 @@ var manyToMany = module.exports = kind( * The default [options]{@link module:enyo/RelationalModel~RelationOptions} overloaded for this * [kind]{@glossary kind}. * - * @see module:enyo/toMany~toMany.options + * @see module:enyo/RelationalModel~toMany#options * @type module:enyo/RelationalModel~RelationOptions * @property {String} inverseType=enyo.manyToMany - This is the **required** type. * @public diff --git a/src/RelationalModel/toMany.js b/src/RelationalModel/toMany.js index 209683a31..35fc60663 100644 --- a/src/RelationalModel/toMany.js +++ b/src/RelationalModel/toMany.js @@ -1,6 +1,6 @@ /** - * Contains the declaration for the {@link toMany} kind. - * @module enyo/toMany + * Contains the declaration for the {@link module:enyo/RelationalModel~toMany} kind. + * @module enyo/RelationalModel */ var @@ -23,7 +23,7 @@ var * @protected */ var toMany = module.exports = kind( - /** @lends module:enyo/toMany~toMany.prototype */ { + /** @lends module:enyo/RelationalModel~toMany.prototype */ { /** * @private diff --git a/src/RelationalModel/toOne.js b/src/RelationalModel/toOne.js index c1c73a009..44a0dd15b 100644 --- a/src/RelationalModel/toOne.js +++ b/src/RelationalModel/toOne.js @@ -1,3 +1,8 @@ +/** +* Contains the declaration for the {@link module:enyo/RelationalModel~toOne} kind. +* @module enyo/RelationalModel +*/ + var kind = require('../kind'), utils = require('../utils'), @@ -14,7 +19,7 @@ var * @protected */ var toOne = module.exports = kind( - /** @lends module:enyo/toOne~toOne.prototype */ { + /** @lends module:enyo/RelationalModel~toOne.prototype */ { /** * @private From eb9f8186595e0342f90599940bbbd0e79e5b6359 Mon Sep 17 00:00:00 2001 From: Ankur Mishra Date: Tue, 22 Sep 2015 16:49:20 +0530 Subject: [PATCH 089/195] Added delay in framework Enyo-DCO-1.1-Signed-off-by: Ankur Mishra --- lib/AnimationSupport/AnimationSupport.js | 15 +++++++++++---- lib/AnimationSupport/Frame.js | 20 ++++++++++++++------ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/lib/AnimationSupport/AnimationSupport.js b/lib/AnimationSupport/AnimationSupport.js index f6a5aa196..5d467d175 100644 --- a/lib/AnimationSupport/AnimationSupport.js +++ b/lib/AnimationSupport/AnimationSupport.js @@ -20,12 +20,19 @@ var AnimationSupport = { active: false, + animationState: "", + /** * Check if the character is suitable for animation * @public */ ready: function() { - return this.generated && this.animating; + var ret = this.generated && this.animating; + if (ret && this._startTime) + ret = this._startTime <= utils.perfNow(); + + if(ret) this.set('animationState', 'started'); + return ret; }, /** @@ -33,7 +40,7 @@ var AnimationSupport = { * @public */ setInitial: function (initial) { - this._startAnim = frame.copy(initial); + this._startAnim = initial; }, /** @@ -106,9 +113,9 @@ var AnimationSupport = { * Trigger animation for this character. * @public */ - start: function (active) { + start: function (active, delay) { this._duration = parseInt(this.getDuration(), 10); - this._startTime = utils.perfNow(); + this._startTime = utils.perfNow() + (delay || 0) ; this._lastTime = this._startTime + this._duration; this.animating = true; this.active = active; diff --git a/lib/AnimationSupport/Frame.js b/lib/AnimationSupport/Frame.js index fe16bcf1d..99ef36a1a 100644 --- a/lib/AnimationSupport/Frame.js +++ b/lib/AnimationSupport/Frame.js @@ -324,12 +324,20 @@ var frame = module.exports = { } } - m = this.getMatrix(s) || Matrix.identity(); - if(m && this.decomposeMatrix(m, dP)){ - for(k in dP) { - sP[k] = dP[k]; - eP[k] = tP[k] || dP[k]; - } + if (initial) { + dP.translate = initial.translate; + dP.rotate = initial.rotate; + dP.scale = initial.scale; + dP.skew = initial.skew; + dP.perspective = initial.perspective; + } else { + m = this.getMatrix(s) || Matrix.identity(); + this.decomposeMatrix(m, dP); + } + + for(k in dP) { + sP[k] = dP[k]; + eP[k] = tP[k] || dP[k]; } return {_startAnim: sP, _endAnim: eP, _transform: dP}; } From c13d8138826858094898e0102c1135b7cdc02183 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Tue, 22 Sep 2015 10:32:45 -0700 Subject: [PATCH 090/195] ENYO-2536: Use cross-platform rAF call. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/LightPanels/LightPanels.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index 1132a2a5c..7c4d7c4f1 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -8,6 +8,7 @@ require('enyo'); var kind = require('../kind'), dom = require('../dom'), + animation = require('../animation'), asyncMethod = require('../utils').asyncMethod; var @@ -179,7 +180,7 @@ module.exports = kind( * @private */ tools: [ - {kind: Control, name: 'client', classes: 'panels-container', ontransitionend: 'transitionFinished'} + {kind: Control, name: 'client', classes: 'panels-container', ontransitionend: 'transitionFinished', onwebkitTransitionEnd: 'transitionFinished'} ], /** @@ -715,12 +716,12 @@ module.exports = kind( if (currPanel) currPanel.addRemoveClass('shifted', !shiftCurrent); // timestamp will be truthy if this is triggered from a rAF - if (timestamp) global.requestAnimationFrame(this.bindSafely('applyTransitions', nextPanel, animate)); + if (timestamp) animation.requestAnimationFrame(this.bindSafely('applyTransitions', nextPanel, animate)); else this.applyTransitions(nextPanel, animate); }); if (!this.generated || !animate) fnInitiateTransition(); - else global.requestAnimationFrame(fnInitiateTransition); + else animation.requestAnimationFrame(fnInitiateTransition); } }, From 85cd70a7b22930b8303bcc2ccc3143241466a393 Mon Sep 17 00:00:00 2001 From: Gray Norton Date: Tue, 22 Sep 2015 14:00:52 -0700 Subject: [PATCH 091/195] ENYO-2565: Bullet-proof logic for constraining min threshold In `NewDataList`, the logic for constraining the minimum scroll threshold value was susceptible to failure in the case where the values being compared were long floating point values (we were checking for equality, but in the failure case two values that were "conceptually equal" for the purpose of our algorithm were actually different in digits far to the right of the decimal). We replace the equality operator with a call to `Math.min()` to constrain the threshold value, which gives us the desired result. Enyo-DCO-1.1-Signed-Off-By: Gray Norton (gray.norton@lge.com) --- src/NewDataList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NewDataList.js b/src/NewDataList.js index 44e47ce67..183be5b62 100644 --- a/src/NewDataList.js +++ b/src/NewDataList.js @@ -171,7 +171,7 @@ module.exports = kind({ st = Math.ceil(d / delta); j = st * delta; tt.max = Math.min(maxVal, tt.max + j); - tt.min = (tt.max == maxVal) ? maxMin : tt.max - delta; + tt.min = Math.min(maxMin, tt.max - delta); this.set('first', (d2x * Math.ceil(this.first / d2x)) + (st * d2x)); } else if (dir == -1 && val < tt.min) { From b2148aba128f79b40c9ec41a90abf3d1816a4fad Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Tue, 22 Sep 2015 16:55:14 -0700 Subject: [PATCH 092/195] JSDoc cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/Collection.js | 42 +++++------ src/Model.js | 58 +++++++------- src/ObserverSupport.js | 83 ++++++++++++--------- src/Relation.js | 2 +- src/RelationalModel/RelationalCollection.js | 7 +- src/RelationalModel/manyToMany.js | 2 +- src/RelationalModel/toMany.js | 2 +- src/RelationalModel/toOne.js | 2 +- src/States.js | 36 ++++----- 9 files changed, 124 insertions(+), 110 deletions(-) diff --git a/src/Collection.js b/src/Collection.js index e151019a2..7d63876fa 100644 --- a/src/Collection.js +++ b/src/Collection.js @@ -239,8 +239,8 @@ var BaseCollection = kind({ * implemented like callbacks used with [Array.sort()]{@glossary Array.sort}. * * @see {@glossary Array.sort} -* @see module:enyo/Collection~Collection.sort -* @see module:enyo/Collection~Collection.comparator +* @see module:enyo/Collection~Collection#sort +* @see module:enyo/Collection~Collection#comparator * @callback module:enyo/Collection~Collection~Comparator * @param {module:enyo/Model~Model} a - The first [model]{@link module:enyo/Model~Model} to compare. * @param {module:enyo/Model~Model} b - The second model to compare. @@ -278,7 +278,7 @@ exports = module.exports = kind( * [committed]{@link module:enyo/Collection~Collection#commit}, or [destroyed]{@link module:enyo/Collection~Collection#destroy}. * Some sources may use this property in other ways. * - * @see module:enyo/Collection~Collection.getUrl + * @see module:enyo/Collection~Collection#getUrl * @see module:enyo/Source~Source * @see module:enyo/AjaxSource~AjaxSource * @see module:enyo/JsonpSource~JsonpSource @@ -297,7 +297,7 @@ exports = module.exports = kind( * Note that if this method is implemented, the [url]{@link module:enyo/Collection~Collection#url} * property will not be used. * - * @see module:enyo/Collection~Collection.url + * @see module:enyo/Collection~Collection#url * @see module:enyo/Source~Source * @see module:enyo/AjaxSource~AjaxSource * @see module:enyo/JsonpSource~JsonpSource @@ -328,7 +328,7 @@ exports = module.exports = kind( * [emit]{@link module:enyo/EventEmitter~EventEmitter#emit} a [reset]{@link module:enyo/Collection~Collection#reset} * event. * - * @see module:enyo/Collection~Collection.modelsChanged + * @see module:enyo/Collection~Collection#modelsChanged * @type module:enyo/ModelList~ModelList * @default null * @readonly @@ -799,7 +799,7 @@ exports = module.exports = kind( * output of the [collection]{@link module:enyo/Collection~Collection}. Will automatically be executed by * [JSON.parse()]{@glossary JSON.parse}. * - * @see module:enyo/Collection~Collection.raw + * @see module:enyo/Collection~Collection#raw * @returns {Object} The return value of [raw()]{@link module:enyo/Collection~Collection#raw}. * @public */ @@ -844,14 +844,14 @@ exports = module.exports = kind( * Commits the [collection]{@link module:enyo/Collection~Collection} to a * [source]{@link module:enyo/Collection~Collection#source} or sources. An {@link module:enyo/Collection~Collection} * cannot be committed if it is in an [error]{@link module:enyo/States.ERROR} - * ({@link module:enyo/StateSupport~StateSupport.isError}) or [busy]{@link module:enyo/States.BUSY} - * ({@link module:enyo/StateSupport~StateSupport.isBusy}) [state]{@link module:enyo/Model~Model#status}. While + * ({@link module:enyo/StateSupport~StateSupport#isError}) or [busy]{@link module:enyo/States.BUSY} + * ({@link module:enyo/StateSupport~StateSupport#isBusy}) [state]{@link module:enyo/Model~Model#status}. While * executing, it will add the [COMMITTING]{@link module:enyo/States.COMMITTING} flag * to the collection's [status]{@link module:enyo/Collection~Collection#status}. Once it has * completed execution, it will remove this flag (even if it fails). * - * @see module:enyo/Collection~Collection.committed - * @see module:enyo/Collection~Collection.status + * @see module:enyo/Collection~Collection#committed + * @see module:enyo/Collection~Collection#status * @param {module:enyo/Collection~Collection~ActionOptions} [opts] - Optional configuration options. * @returns {this} The callee for chaining. * @public @@ -896,14 +896,14 @@ exports = module.exports = kind( * Fetches the [collection]{@link module:enyo/Collection~Collection} from a * [source]{@link module:enyo/Collection~Collection#source} or sources. An {@link module:enyo/Collection~Collection} * cannot be fetched if it is in an [error]{@link module:enyo/States.ERROR} - * ({@link module:enyo/StateSupport~StateSupport.isError}) or [busy]{@link module:enyo/States.BUSY} - * ({@link module:enyo/StateSupport~StateSupport.isBusy}) [state]{@link module:enyo/Model~Model#status}. While + * ({@link module:enyo/StateSupport~StateSupport#isError}) or [busy]{@link module:enyo/States.BUSY} + * ({@link module:enyo/StateSupport~StateSupport#isBusy}) [state]{@link module:enyo/Model~Model#status}. While * executing, it will add the [FETCHING]{@link module:enyo/States.FETCHING} flag to * the collection's [status]{@link module:enyo/Collection~Collection#status}. Once it has * completed execution, it will remove this flag (even if it fails). * - * @see module:enyo/Collection~Collection.fetched - * @see module:enyo/Collection~Collection.status + * @see module:enyo/Collection~Collection#fetched + * @see module:enyo/Collection~Collection#status * @param {module:enyo/Collection~Collection~ActionOptions} [opts] - Optional configuration options. * @returns {this} The callee for chaining. * @public @@ -951,14 +951,14 @@ exports = module.exports = kind( * [commit default option]{@link module:enyo/Collection~Collection#options} must be `true` or a * `source` property must be explicitly provided in the `opts` parameter. A * collection cannot be destroyed (using a source) if it is in an - * [error]{@link module:enyo/States.ERROR} ({@link module:enyo/StateSupport~StateSupport.isError}) or - * [busy]{@link module:enyo/States.BUSY} ({@link module:enyo/StateSupport~StateSupport.isBusy}) + * [error]{@link module:enyo/States.ERROR} ({@link module:enyo/StateSupport~StateSupport#isError}) or + * [busy]{@link module:enyo/States.BUSY} ({@link module:enyo/StateSupport~StateSupport#isBusy}) * [state]{@link module:enyo/Collection~Collection#status}. While executing, it will add the * [DESTROYING]{@link module:enyo/States.DESTROYING} flag to the collection's * [status]{@link module:enyo/Collection~Collection#status}. Once it has completed execution, * it will remove this flag (even if it fails). * - * @see module:enyo/Collection~Collection.status + * @see module:enyo/Collection~Collection#status * @param {module:enyo/Collection~Collection~ActionOptions} [opts] - Optional configuration options. * @returns {this} The callee for chaining. * @method @@ -1202,10 +1202,10 @@ exports = module.exports = kind( /** * Overloaded version of the method to call [set()]{@link module:enyo/Collection~Collection#set} * instead of simply assigning the value. This allows it to - * [notify observers]{@link module:enyo/ObserverSupport~ObserverSupport.notify} and thus update - * [bindings]{@link module:enyo/BindingSupport~BindingSupport.bindings} as well. + * [notify observers]{@link module:enyo/ObserverSupport} and thus update + * [bindings]{@link module:enyo/BindingSupport#binding} as well. * - * @see module:enyo/StateSupport.clearError + * @see {@link module:enyo/StateSupport~StateSupport#clearError} * @public */ clearError: function () { @@ -1229,7 +1229,7 @@ exports = module.exports = kind( /** * Responds to changes to the [models]{@link module:enyo/Collection~Collection#models} property. * - * @see module:enyo/Collection~Collection.models + * @see module:enyo/Collection~Collection#models * @fires module:enyo/Collection~Collection#reset * @type {module:enyo/ObserverSupport~ObserverSupport~Observer} * @public diff --git a/src/Model.js b/src/Model.js index f62b52a92..63fa57aa8 100644 --- a/src/Model.js +++ b/src/Model.js @@ -140,7 +140,7 @@ var Model = module.exports = kind( * [committed]{@link module:enyo/Model~Model#commit}, or [destroyed]{@link module:enyo/Model~Model#destroy}. * Some sources may use this property in other ways. * - * @see module:enyo/Model~Model.getUrl + * @see module:enyo/Model~Model#getUrl * @see module:enyo/Source~Source * @see module:enyo/AjaxSource~AjaxSource * @see module:enyo/JsonpSource~JsonpSource @@ -158,7 +158,7 @@ var Model = module.exports = kind( * property in other ways. Note that, if this method is implemented, the * [url]{@link module:enyo/Model~Model#url} will not be used. * - * @see module:enyo/Model~Model.url + * @see module:enyo/Model~Model#url * @see module:enyo/Source~Source * @see module:enyo/AjaxSource~AjaxSource * @see module:enyo/JsonpSource~JsonpSource @@ -193,12 +193,12 @@ var Model = module.exports = kind( * Any method that uses sources may override this default value in its configuration * options. This value may be a [string]{@glossary String}, an * [Array]{@glossary Array} of strings, an instance of {@link module:enyo/Source~Source}, or an - * array of `enyo.Source` instances. + * array of `enyo/Source` instances. * * @see module:enyo/Source~Source - * @see module:enyo/Model~Model.fetch - * @see module:enyo/Model~Model.commit - * @see module:enyo/Model~Model.destroy + * @see module:enyo/Model~Model#fetch + * @see module:enyo/Model~Model#commit + * @see module:enyo/Model~Model#destroy * @type {(String|String[]|module:enyo/Source~Source|module:enyo/Source~Source[])} * @default null * @public @@ -213,8 +213,8 @@ var Model = module.exports = kind( * not defined, all keys from the [attributes]{@link module:enyo/Model~Model#attributes} * [hash]{@glossary Object} will be used. * - * @see module:enyo/Model~Model.raw - * @see module:enyo/Model~Model.toJSON + * @see module:enyo/Model~Model#raw + * @see module:enyo/Model~Model#toJSON * @type {String[]} * @default null * @public @@ -246,7 +246,7 @@ var Model = module.exports = kind( * Note that this is **not** a [bindable]{@link module:enyo/BindingSupport~BindingSupport} property. * * @see module:enyo/States~States - * @see module:enyo/StateSupport~StateSupport + * @see {@link module:enyo/StateSupport~StateSupport} * @type {module:enyo/States~States} * @readonly * @public @@ -291,8 +291,8 @@ var Model = module.exports = kind( * [includeKeys]{@link module:enyo/Model~Model#includeKeys}. * [Computed properties]{@link module:enyo/ComputedSupport} are **never** included. * - * @see module:enyo/Model~Model.includeKeys - * @see module:enyo/Model~Model.attributes + * @see module:enyo/Model~Model#includeKeys + * @see module:enyo/Model~Model#attributes * @returns {Object} The formatted [hash]{@glossary Object} representing the underlying * data structure of the [model]{@link module:enyo/Model~Model}. * @public @@ -316,7 +316,7 @@ var Model = module.exports = kind( * of the [model]{@link module:enyo/Model~Model}. Will automatically be executed by * [JSON.parse()]{@glossary JSON.parse}. * - * @see module:enyo/Model~Model.raw + * @see module:enyo/Model~Model#raw * @returns {Object} The return value of [raw()]{@link module:enyo/Model~Model#raw}. * @public */ @@ -330,8 +330,8 @@ var Model = module.exports = kind( * Restores an [attribute]{@link module:enyo/Model~Model#attributes} to its previous value. If no * attribute is specified, all previous values will be restored. * - * @see module:enyo/Model~Model.set - * @see module:enyo/Model~Model.previous + * @see module:enyo/Model~Model#set + * @see module:enyo/Model~Model#previous * @param {String} [prop] - The [attribute]{@link module:enyo/Model~Model#attributes} to * [restore]{@link module:enyo/Model~Model#restore}. If not provided, all attributes will be * restored to their previous values. @@ -351,15 +351,15 @@ var Model = module.exports = kind( /** * Commits the [model]{@link module:enyo/Model~Model} to a [source or sources]{@link module:enyo/Model~Model#source}. * A model cannot be [committed]{@link module:enyo/Model~Model#commit} if it is in an - * [error]{@link module:enyo/States#ERROR} ({@link module:enyo/StateSupport~StateSupport.isError}) or - * [busy]{@link module:enyo/States#BUSY} ({@link module:enyo/StateSupport~StateSupport.isBusy}) + * [error]{@link module:enyo/States#ERROR} ({@link module:enyo/StateSupport~StateSupport#isError}) or + * [busy]{@link module:enyo/States#BUSY} ({@link module:enyo/StateSupport~StateSupport#isBusy}) * [state]{@link module:enyo/Model~Model#status}. While executing, it will add the * [COMMITTING]{@link module:enyo/States#COMMITTING} flag to the model's * [status]{@link module:enyo/Model~Model#status}. Once it has completed execution, it will * remove this flag (even if it fails). * - * @see module:enyo/Model~Model.committed - * @see module:enyo/Model~Model.status + * @see module:enyo/Model~Model#committed + * @see module:enyo/Model~Model#status * @param {module:enyo/Model~Model~ActionOptions} [opts] - Optional configuration options. * @returns {this} The callee for chaining. * @public @@ -404,15 +404,15 @@ var Model = module.exports = kind( * Fetches the [model]{@link module:enyo/Model~Model} from a * [source or sources]{@link module:enyo/Model~Model#source}. A model cannot be * [fetched]{@link module:enyo/Model~Model#fetch} if it is in an - * [error]{@link module:enyo/States#ERROR} ({@link module:enyo/StateSupport~StateSupport.isError}) or - * [busy]{@link module:enyo/States#BUSY} ({@link module:enyo/StateSupport~StateSupport.isBusy}) + * [error]{@link module:enyo/States#ERROR} ({@link module:enyo/StateSupport~StateSupport#isError}) or + * [busy]{@link module:enyo/States#BUSY} ({@link module:enyo/StateSupport~StateSupport#isBusy}) * [state]{@link module:enyo/Model~Model#status}. While executing, it will add the * [FETCHING]{@link module:enyo/States#FETCHING} flag to the model's * [status]{@link module:enyo/Model~Model#status}. Once it has completed execution, it will * remove this flag (even if it fails). * - * @see module:enyo/Model~Model.fetched - * @see module:enyo/Model~Model.status + * @see module:enyo/Model~Model#fetched + * @see module:enyo/Model~Model#status * @param {module:enyo/Model~Model~ActionOptions} [opts] - Optional configuration options. * @returns {this} The callee for chaining. * @public @@ -460,14 +460,14 @@ var Model = module.exports = kind( * [commit default option]{@link module:enyo/Model~Model#options} must be `true` or a * `source` property must be explicitly provided in the `opts` parameter. * A model cannot be destroyed (using a source) if it is in an - * [error]{@link module:enyo/States#ERROR} ({@link module:enyo/StateSupport~StateSupport.isError}) - * or [busy]{@link module:enyo/States#BUSY} ({@link module:enyo/StateSupport~StateSupport.isBusy}) + * [error]{@link module:enyo/States#ERROR} ({@link module:enyo/StateSupport~StateSupport#isError}) + * or [busy]{@link module:enyo/States#BUSY} ({@link module:enyo/StateSupport~StateSupport#isBusy}) * [state]{@link module:enyo/Model~Model#status}. While executing, it will add the * [DESTROYING]{@link module:enyo/States#DESTROYING} flag to the model's * [status]{@link module:enyo/Model~Model#status}. Once it has completed execution, it * will remove this flag (even if it fails). * - * @see module:enyo/Model~Model.status + * @see module:enyo/Model~Model#status * @param {module:enyo/Model~Model~ActionOptions} [opts] - Optional configuration options. * @returns {this} The callee for chaining. * @public @@ -580,8 +580,8 @@ var Model = module.exports = kind( * properties that were modified. * * @fires module:enyo/Model~Model#change - * @see module:enyo/ObserverSupport~ObserverSupport - * @see module:enyo/BindingSupport~BindingSupport + * @see {@link module:enyo/ObserverSupport~ObserverSupport} + * @see {@link module:enyo/BindingSupport~BindingSupport} * @param {(String|Object)} path - Either the property name or a [hash]{@glossary Object} * of properties and values to set. * @param {(*|module:enyo/Model~Options)} is If `path` is a [string]{@glossary String}, @@ -669,7 +669,7 @@ var Model = module.exports = kind( }, /** - * A bit of hackery to facade the normal [getter]{@link module:enyo/ComputedSupport~ComputedSupport.get}. Note that + * A bit of hackery to facade the normal [getter]{@link module:enyo/ComputedSupport~ComputedSupport#get}. Note that * we pass an arbitrary super-method that automatically returns `undefined`, which is * consistent with this use case and its intended purpose. * @@ -862,7 +862,7 @@ var Model = module.exports = kind( * be determined. If an [error callback]{@link module:enyo/Model~Model~Error} was provided, this method * will execute it. * - * @see module:enyo/StateSupport~StateSupport.clearError + * @see {@link module:enyo/StateSupport~StateSupport#clearError} * @param {String} action - The action (one of `'FETCHING'`, `'COMMITTING'`, or * `'DESTROYING'`) that failed and is now in an [error state]{@link module:enyo/States#ERROR}. * @param {module:enyo/Model~Model~ActionOptions} opts - The original options passed to the `action` diff --git a/src/ObserverSupport.js b/src/ObserverSupport.js index 66bfbcb02..1df5d353c 100644 --- a/src/ObserverSupport.js +++ b/src/ObserverSupport.js @@ -17,14 +17,14 @@ kind.concatenated.push("observers"); /** * Responds to changes in one or more properties. -* [Observers]{@link module:enyo/ObserverSupport~ObserverSupport.observer} may be registered in +* [Observers]{@link module:enyo/ObserverSupport~ObserverSupport#observers} may be registered in * several different ways. See the {@link module:enyo/ObserverSupport} documentation * for more details. Also note that, while observers should not be called * directly, if defined on a [kind]{@glossary kind}, they may be * overloaded for special behavior. * -* @see module:enyo/ObserverSupport -* @see module:enyo/ObserverSupport~ObserverSupport.observe +* @see {@link module:enyo/ObserverSupport} +* @see {@link module:enyo/ObserverSupport~ObserverSupport#observe} * @callback module:enyo/ObserverSupport~ObserverSupport~Observer * @param {*} was - The previous value of the property that has changed. * @param {*} is - The current value of the property that has changed. @@ -125,14 +125,17 @@ function flushQueue (obj) { * {@link module:enyo/CoreObject~Object}) already have this {@glossary mixin} applied. * This allows for * [observers]{@link module:enyo/ObserverSupport~ObserverSupport~Observer} to be -* [declared]{@link module:enyo/ObserverSupport~ObserverSupport.observers} or "implied" (see below). +* [declared]{@link module:enyo/ObserverSupport~ObserverSupport#observers} or "implied" (see below). * * Implied observers are not declared, but derived from their `name`. They take * the form `Changed`, where `` is the property to -* [observe]{@link module:enyo/ObserverSupport~ObserverSupport.observe}. For example: +* [observe]{@link module:enyo/ObserverSupport~ObserverSupport#observe}. For example: * * ```javascript -* enyo.kind({ +* var +* kind = require('enyo/kind'); +* +* module.exports = kind({ * name: 'MyKind', * * // some local property @@ -153,7 +156,10 @@ function flushQueue (obj) { * observe any property (or properties), regardless of its `name`. For example: * * ```javascript -* enyo.kind({ +* var +* kind = require('enyo/kind'); +* +* module.exports = kind({ * name: 'MyKind', * * // some local property @@ -180,18 +186,21 @@ function flushQueue (obj) { * mine.set('count', 2); // -> count was "1" but now it is "2" * ``` * -* While observers may be [notified]{@link module:enyo/ObserverSupport~ObserverSupport.notify} of +* While observers may be [notified]{@link module:enyo/ObserverSupport~ObserverSupport#notify} of * changes to multiple properties, this is not a typical use case for implied * observers, since, by convention, they are only registered for the named * property. * * There is one additional way to use observers, if necessary. You may use the -* API methods [observe()]{@link module:enyo/ObserverSupport~ObserverSupport.observe} and -* [unobserve()]{@link module:enyo/ObserverSupport~ObserverSupport.unobserve} to dynamically +* API methods [observe()]{@link module:enyo/ObserverSupport~ObserverSupport#observe} and +* [unobserve()]{@link module:enyo/ObserverSupport~ObserverSupport#unobserve} to dynamically * register and unregister observers as needed. For example: * * ```javascript -* var object = new enyo.Object({value: true}); +* var +* Object = require('enyo/CoreObject').Object; +* +* var object = new Object({value: true}); * var observer = function (was, is) { * enyo.log('value was "' + was + '" but now it is "' + is + '"'); * }; @@ -237,11 +246,11 @@ var ObserverSupport = { /** * Determines whether `_observing` is enabled. If - * [stopNotifications()]{@link module:enyo/ObserverSupport~ObserverSupport.stopNotifications} has + * [stopNotifications()]{@link module:enyo/ObserverSupport~ObserverSupport#stopNotifications} has * been called, then this will return `false`. * - * @see module:enyo/ObserverSupport~ObserverSupport.stopNotifications - * @see module:enyo/ObserverSupport~ObserverSupport.startNotifications + * @see {@link module:enyo/ObserverSupport~ObserverSupport#stopNotifications} + * @see {@link module:enyo/ObserverSupport~ObserverSupport#startNotifications} * @returns {Boolean} Whether or not the callee is observing. */ isObserving: function () { @@ -287,7 +296,7 @@ var ObserverSupport = { /** * @deprecated - * @alias enyo.ObserverSupport.observe + * @alias {@link module:enyo/ObserverSupport~ObserverSupport#observe} * @public */ addObserver: function () { @@ -297,16 +306,16 @@ var ObserverSupport = { /** * Registers an [observer]{@link module:enyo/ObserverSupport~ObserverSupport~Observer} to be - * [notified]{@link module:enyo/ObserverSupport~ObserverSupport.notify} when the given property has + * [notified]{@link module:enyo/ObserverSupport~ObserverSupport#notify} when the given property has * been changed. It is important to note that it is possible to register the * same observer multiple times (although this is never the intention), so * care should be taken to avoid that scenario. It is also important to * understand how observers are stored and unregistered - * ([unobserved]{@link module:enyo/ObserverSupport~ObserverSupport.unobserve}). The `ctx` (context) + * ([unobserved]{@link module:enyo/ObserverSupport~ObserverSupport#unobserve}). The `ctx` (context) * parameter is stored with the observer reference. **If used when * registering, it should also be used when unregistering.** * - * @see module:enyo/ObserverSupport~ObserverSupport.unobserve + * @see {@link module:enyo/ObserverSupport~ObserverSupport#unobserve} * @param {String} path - The property or property path to observe. * @param {module:enyo/ObserverSupport~ObserverSupport~Observer} fn - The * [observer]{@link module:enyo/ObserverSupport~ObserverSupport~Observer} method that responds to changes. @@ -322,7 +331,7 @@ var ObserverSupport = { /** * @deprecated - * @alias enyo.ObserverSupport.unobserve + * @alias {@link module:enyo/ObserverSupport~ObserverSupport#unobserve} * @public */ removeObserver: function (path, fn, ctx) { @@ -331,10 +340,10 @@ var ObserverSupport = { /** * Unregisters an [observer]{@link module:enyo/ObserverSupport~ObserverSupport~Observer}. If a `ctx` - * (context) was supplied to [observe()]{@link module:enyo/ObserverSupport~ObserverSupport.observe}, + * (context) was supplied to [observe()]{@link module:enyo/ObserverSupport~ObserverSupport#observe}, * then it should also be supplied to this method. * - * @see module:enyo/ObserverSupport~ObserverSupport.observe + * @see {@link module:enyo/ObserverSupport~ObserverSupport#observe} * @param {String} path - The property or property path to unobserve. * @param {module:enyo/ObserverSupport~ObserverSupport~Observer} fn - The * [observer]{@link module:enyo/ObserverSupport~ObserverSupport~Observer} method that responds to changes. @@ -373,7 +382,7 @@ var ObserverSupport = { /** * @deprecated - * @alias enyo.ObserverSupport.notify + * @alias module:enyo/ObserverSupport~ObserverSupport#notify * @public */ notifyObservers: function (path, was, is, opts) { @@ -397,19 +406,19 @@ var ObserverSupport = { }, /** - * Stops all [notifications]{@link module:enyo/ObserverSupport~ObserverSupport.notify} from + * Stops all [notifications]{@link module:enyo/ObserverSupport~ObserverSupport#notify} from * propagating. By default, all notifications will be queued and flushed once - * [startNotifications()]{@link module:enyo/ObserverSupport~ObserverSupport.startNotifications} + * [startNotifications()]{@link module:enyo/ObserverSupport~ObserverSupport#startNotifications} * has been called. Setting the optional `noQueue` flag will also disable the * queue, or you can use the - * [disableNotificationQueue()]{@link module:enyo/ObserverSupport~ObserverSupport.disableNotificationQueue} and - * [enableNotificationQueue()]{@link module:enyo/ObserverSupport~ObserverSupport.enableNotificationQueue} + * [disableNotificationQueue()]{@link module:enyo/ObserverSupport~ObserverSupport#disableNotificationQueue} and + * [enableNotificationQueue()]{@link module:enyo/ObserverSupport~ObserverSupport#enableNotificationQueue} * API methods. `startNotifications()` will need to be called the same number * of times that this method has been called. * - * @see module:enyo/ObserverSupport~ObserverSupport.startNotifications - * @see module:enyo/ObserverSupport~ObserverSupport.disableNotificationQueue - * @see module:enyo/ObserverSupport~ObserverSupport.enableNotificationQueue + * @see {@link module:enyo/ObserverSupport~ObserverSupport#startNotifications} + * @see {@link module:enyo/ObserverSupport~ObserverSupport#disableNotificationQueue} + * @see {@link module:enyo/ObserverSupport~ObserverSupport#enableNotificationQueue} * @param {Boolean} [noQueue] - If `true`, this will also disable the notification queue. * @returns {this} The callee for chaining. */ @@ -421,17 +430,17 @@ var ObserverSupport = { }, /** - * Starts [notifications]{@link module:enyo/ObserverSupport~ObserverSupport.notify} if they have - * been [disabled]{@link module:enyo/ObserverSupport~ObserverSupport.stopNotifications}. If the + * Starts [notifications]{@link module:enyo/ObserverSupport~ObserverSupport#notify} if they have + * been [disabled]{@link module:enyo/ObserverSupport~ObserverSupport#stopNotifications}. If the * notification queue was not disabled, this will automatically flush the * queue of all notifications that were encountered while stopped. This * method must be called the same number of times that - * [stopNotifications()]{@link module:enyo/ObserverSupport~ObserverSupport.stopNotifications} was + * [stopNotifications()]{@link module:enyo/ObserverSupport~ObserverSupport#stopNotifications} was * called. * - * @see module:enyo/ObserverSupport~ObserverSupport.stopNotifications - * @see module:enyo/ObserverSupport~ObserverSupport.disableNotificationQueue - * @see module:enyo/ObserverSupport~ObserverSupport.enableNotificationQueue + * @see {@link module:enyo/ObserverSupport~ObserverSupport#stopNotifications} + * @see {@link module:enyo/ObserverSupport~ObserverSupport#disableNotificationQueue} + * @see {@link module:enyo/ObserverSupport~ObserverSupport#enableNotificationQueue} * @param {Boolean} [queue] - If `true` and the notification queue is disabled, * the queue will be re-enabled. * @returns {this} The callee for chaining. @@ -447,7 +456,7 @@ var ObserverSupport = { /** * Re-enables the notification queue, if it was disabled. * - * @see module:enyo/ObserverSupport~ObserverSupport.disableNotificationQueue + * @see {@link module:enyo/ObserverSupport~ObserverSupport#disableNotificationQueue} * @returns {this} The callee for chaining. */ enableNotificationQueue: function () { @@ -459,7 +468,7 @@ var ObserverSupport = { * If the notification queue is enabled (the default), it will be disabled * and any notifications in the queue will be removed. * - * @see module:enyo/ObserverSupport~ObserverSupport.enableNotificationQueue + * @see {@link module:enyo/ObserverSupport~ObserverSupport#enableNotificationQueue} * @returns {this} The callee for chaining. */ disableNotificationQueue: function () { diff --git a/src/Relation.js b/src/Relation.js index c5894148c..6cccc89c9 100644 --- a/src/Relation.js +++ b/src/Relation.js @@ -1,7 +1,7 @@ require('enyo'); /** -* Contains the declaration for the {@link module:enyo/Relation} kind. +* Contains the declaration for the {@link module:enyo/Relation~Relation} kind. * @module enyo/Relation */ diff --git a/src/RelationalModel/RelationalCollection.js b/src/RelationalModel/RelationalCollection.js index a7bc4df63..4989f64b4 100644 --- a/src/RelationalModel/RelationalCollection.js +++ b/src/RelationalModel/RelationalCollection.js @@ -1,3 +1,8 @@ +/** +* Contains the declaration for the {@link module:enyo/RelationalModel~RelationalCollection} kind. +* @module enyo/RelationalModel +*/ + var kind = require('../kind'), Collection = require('../Collection'); @@ -14,7 +19,7 @@ var * @private */ module.exports = kind( - /** @lends RelationalCollection.prototype */ { + /** @lends module:enyo/RelationalModel~RelationalCollection.prototype */ { /** * @private diff --git a/src/RelationalModel/manyToMany.js b/src/RelationalModel/manyToMany.js index 2949d9677..221f9f7fa 100644 --- a/src/RelationalModel/manyToMany.js +++ b/src/RelationalModel/manyToMany.js @@ -14,7 +14,7 @@ var * models. This is an internally-used class. * * @class manyToMany -* @extends toMany +* @extends module:enyo/RelationalModel~toMany * @protected */ var manyToMany = module.exports = kind( diff --git a/src/RelationalModel/toMany.js b/src/RelationalModel/toMany.js index 35fc60663..e2cdd1add 100644 --- a/src/RelationalModel/toMany.js +++ b/src/RelationalModel/toMany.js @@ -19,7 +19,7 @@ var * models. This is an internally-used class. * * @class toMany -* @extends Relation +* @extends module:enyo/Relation~Relation * @protected */ var toMany = module.exports = kind( diff --git a/src/RelationalModel/toOne.js b/src/RelationalModel/toOne.js index 44a0dd15b..1dd3f0847 100644 --- a/src/RelationalModel/toOne.js +++ b/src/RelationalModel/toOne.js @@ -15,7 +15,7 @@ var * model. This is an internally-used class. * * @class toOne -* @extends Relation +* @extends module:enyo/Relation~Relation * @protected */ var toOne = module.exports = kind( diff --git a/src/States.js b/src/States.js index 672b1b0fa..3e72e5d18 100644 --- a/src/States.js +++ b/src/States.js @@ -88,9 +88,9 @@ module.exports = { /** * Currently attempting to fetch. * - * @see module:enyo/Model~Model.fetch - * @see module:enyo/RelationalModel~RelationalModel.fetch - * @see module:enyo/Collection~Collection.fetch + * @see module:enyo/Model~Model#fetch + * @see module:enyo/RelationalModel~RelationalModel#fetch + * @see module:enyo/Collection~Collection#fetch * * @type {Number} * @default 16 @@ -100,9 +100,9 @@ module.exports = { /** * Currently attempting to commit. * - * @see module:enyo/Model~Model.commit - * @see module:enyo/RelationalModel~RelationalModel.commit - * @see module:enyo/Collection~Collection.commit + * @see module:enyo/Model~Model#commit + * @see module:enyo/RelationalModel~RelationalModel#commit + * @see module:enyo/Collection~Collection#commit * * @type {Number} * @default 32 @@ -112,9 +112,9 @@ module.exports = { /** * Currently attempting to destroy. * - * @see module:enyo/Model~Model.destroy - * @see module:enyo/RelationalModel~RelationalModel.destroy - * @see module:enyo/Collection~Collection.destroy + * @see module:enyo/Model~Model#destroy + * @see module:enyo/RelationalModel~RelationalModel#destroy + * @see module:enyo/Collection~Collection#destroy * * @type {Number} * @default 64 @@ -124,9 +124,9 @@ module.exports = { /** * There was an error during commit. * - * @see module:enyo/Model~Model.commit - * @see module:enyo/RelationalModel~RelationalModel.commit - * @see module:enyo/Collection~Collection.commit + * @see module:enyo/Model~Model#commit + * @see module:enyo/RelationalModel~RelationalModel#commit + * @see module:enyo/Collection~Collection#commit * * @type {Number} * @default 128 @@ -136,9 +136,9 @@ module.exports = { /** * There was an error during fetch. * - * @see module:enyo/Model~Model.fetch - * @see module:enyo/RelationalModel~RelationalModel.fetch - * @see module:enyo/Collection~Collection.fetch + * @see module:enyo/Model~Model#fetch + * @see module:enyo/RelationalModel~RelationalModel#fetch + * @see module:enyo/Collection~Collection#fetch * * @type {Number} * @default 256 @@ -148,9 +148,9 @@ module.exports = { /** * There was an error during destroy. * - * @see module:enyo/Model~Model.destroy - * @see module:enyo/RelationalModel~RelationalModel.destroy - * @see module:enyo/Collection~Collection.destroy + * @see module:enyo/Model~Model#destroy + * @see module:enyo/RelationalModel~RelationalModel#destroy + * @see module:enyo/Collection~Collection#destroy * * @type {Number} * @default 512 From 22b4ceb6cd062c6467bdb9ee4c70c0c96b37fd84 Mon Sep 17 00:00:00 2001 From: Gray Norton Date: Wed, 23 Sep 2015 09:40:37 -0700 Subject: [PATCH 093/195] ENYO-2557: Allow resize events to waterfall freely Some time ago, we added some logic to prevent resize events from waterfalling through hidden UI components. The intention was to prevent unnecessary layout from happening. This approach generally worked, but there were some problem cases, as noted in a comment that went in with the original change. Specifically, a component that is hidden and therefore never receives a resize event has no way of "knowing" that a resize has been requested, so won't reflow its layout when it is later shown. We recently added some different but related logic to allow components to defer reflows when hidden and perform them later upon being shown. This newer mechanism has the advantage of avoiding the problem described above. Since the expensive part of dealing with a resize event is almost always performing a reflow, it seems that we can safely remove the prevent-resize-event logic and rely on the new defer-reflow logic to address the original problem. I did some quick tests on a range of Moonstone samples with instrumentation in place to ensure that we weren't substantially increasing the number of resize events flowing through the UI. Based on these tests, it appears that the number of additional resize events is not significant, and since virtually all of the processing that would have been caused by the additional events is short-circuited by the defer-reflow logic, I feel good about this change. Enyo-DCO-1.1-Signed-Off-By: Gray Norton (gray.norton@lge.com) --- src/UiComponent.js | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/src/UiComponent.js b/src/UiComponent.js index 5cc3a6dde..2fa5745ca 100644 --- a/src/UiComponent.js +++ b/src/UiComponent.js @@ -144,13 +144,6 @@ var UiComponent = module.exports = kind( */ addBefore: undefined, - /** - * @private - */ - protectedStatics: { - _resizeFlags: {showingOnly: true} // don't waterfall these events into hidden controls - }, - /** * @method * @private @@ -631,8 +624,8 @@ var UiComponent = module.exports = kind( * @public */ resize: function () { - this.waterfall('onresize', UiComponent._resizeFlags); - this.waterfall('onpostresize', UiComponent._resizeFlags); + this.waterfall('onresize'); + this.waterfall('onpostresize'); }, /** @@ -677,13 +670,7 @@ var UiComponent = module.exports = kind( } // waterfall to my children for (var i=0, cs=this.children, c; (c=cs[i]); i++) { - // Do not send {showingOnly: true} events to hidden controls. This flag is set for resize events - // which are broadcast from within the framework. This saves a *lot* of unnecessary layout. - // TODO: Maybe remember that we did this, and re-send those messages on setShowing(true)? - // No obvious problems with it as-is, though - if (c.showing || !(event && event.showingOnly)) { - c.waterfall(nom, event, sender); - } + c.waterfall(nom, event, sender); } }, From 4b011f51952ac9f0ea722131a7aa8f1de547e485 Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Wed, 23 Sep 2015 12:35:26 -0700 Subject: [PATCH 094/195] JSDoc cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/Collection.js | 2 +- src/LocalStorageSource.js | 4 ++-- src/TransitionScrollStrategy.js | 6 +++--- src/UiComponent.js | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Collection.js b/src/Collection.js index 7d63876fa..097311234 100644 --- a/src/Collection.js +++ b/src/Collection.js @@ -378,7 +378,7 @@ exports = module.exports = kind( * Modifies the structure of data so that it can be used by the * [add()]{@link module:enyo/Collection~Collection#add} method. This method will only be used * during initialization or after a successful [fetch]{@link module:enyo/Collection~Collection#fetch} - * if the [parse]{@link module:enyo/Collection~Options.parse} flag is set to `true`. + * if the [parse]{@link module:enyo/Collection~Options#parse} flag is set to `true`. * It may be used for simple remapping, renaming, or complex restructuring of * data coming from a [source]{@link module:enyo/Collection~Collection#source} that requires * modification before it can be added to the [collection]{@link module:enyo/Collection~Collection}. diff --git a/src/LocalStorageSource.js b/src/LocalStorageSource.js index 826e0cf71..0038b9edd 100644 --- a/src/LocalStorageSource.js +++ b/src/LocalStorageSource.js @@ -29,8 +29,8 @@ if (typeof localStorage != 'undefined') { * * It is important to note that usage of this source requires that * [models]{@link module:enyo/Model~Model} and [collections]{@link module:enyo/Collection~Collection} use - * their respective `url` properties ({@link module:enyo/Model~Model.url} and - * {@link module:enyo/Collection~Collection.url}). Any collection that needs to be + * their respective `url` properties ({@link module:enyo/Model~Model#url} and + * {@link module:enyo/Collection~Collection#url}). Any collection that needs to be * [committed]{@link module:enyo/Collection~Collection#commit} must have a unique `url` value. * Any model that will be [committed]{@link module:enyo/Model~Model#commit} directly, or * within an {@link module:enyo/Collection~Collection}, must have a unique diff --git a/src/TransitionScrollStrategy.js b/src/TransitionScrollStrategy.js index d2aa76be4..0c05cc2a8 100644 --- a/src/TransitionScrollStrategy.js +++ b/src/TransitionScrollStrategy.js @@ -54,9 +54,9 @@ var * {@link module:enyo/TouchScrollStrategy~TouchScrollStrategy}, optimizing it for scrolling environments in which * effecting scroll changes with transforms using CSS transitions is fastest. * -* `enyo.TransitionScrollStrategy` is not typically created in application code. Instead, it is +* `enyo/TransitionScrollStrategy` is not typically created in application code. Instead, it is * specified as the value of the [strategyKind]{@link module:enyo/Scroller~Scroller#strategyKind} property of -* an {@link module:enyo/Scroller~Scroller} or {@link module:layout/List#List}, or is used by the framework implicitly. +* an {@link module:enyo/Scroller~Scroller} or {@link module:layout/List~List}, or is used by the framework implicitly. * * @class TransitionScrollStrategy * @extends module:enyo/TouchScrollStrategy~TouchScrollStrategy @@ -565,7 +565,7 @@ var TransitionScrollStrategy = module.exports = kind( /** * Special synthetic [DOM events]{@glossary DOMEvent} served up by the - * [Gesture]{@link module:enyo/gesture~gesture} system. + * [Gesture]{@link module:enyo/gesture} system. * * @private */ diff --git a/src/UiComponent.js b/src/UiComponent.js index 5cc3a6dde..66711b3d6 100644 --- a/src/UiComponent.js +++ b/src/UiComponent.js @@ -18,7 +18,7 @@ var * * `UiComponent` itself is abstract. Concrete [subkinds]{@glossary subkind} include * {@link module:enyo/Control~Control} (for HTML/DOM) and -* {@link module:enyo/canvas~canvas.Control} (for Canvas contexts). +* {@link module:canvas/Control~Control} (for Canvas contexts). * * @class UiComponent * @extends module:enyo/Component~Component From 4f594b041dd832e9f9ef6007b34605317247c125 Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Wed, 23 Sep 2015 14:28:41 -0700 Subject: [PATCH 095/195] JSDoc cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/NewThumb/NewThumb.js | 20 +++++++++++++------- src/Scrim/Scrim.js | 6 ++---- src/ScrollStrategy.js | 4 ++-- src/ScrollThumb/ScrollThumb.js | 2 +- src/TouchScrollStrategy.js | 8 ++++---- src/TranslateScrollStrategy.js | 2 +- 6 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/NewThumb/NewThumb.js b/src/NewThumb/NewThumb.js index 4726b1982..7e1f0c74b 100644 --- a/src/NewThumb/NewThumb.js +++ b/src/NewThumb/NewThumb.js @@ -1,5 +1,10 @@ require('enyo'); +/** +* Contains the declaration for the {@link module:enyo/NewThumb~NewThumb} kind. +* @module enyo/NewThumb +*/ + var kind = require('../kind'), ri = require('../resolution'), @@ -9,17 +14,18 @@ var Control = require('../Control'); /** -* {@link module:enyo/ScrollThumb~ScrollThumb} is a helper [kind]{@glossary kind} used by -* {@link module:enyo/TouchScrollStrategy~TouchScrollStrategy} and {@link module:enyo/TranslateScrollStrategy~TranslateScrollStrategy} to -* display a small visual scroll indicator. -* -* `enyo.ScrollThumb` is not typically created in application code. +* {@link module:enyo/NewThumb~NewThumb} is a helper [kind]{@glossary kind} used +* by {@link module:enyo/TouchScrollStrategy~TouchScrollStrategy} and +* {@link module:enyo/TranslateScrollStrategy~TranslateScrollStrategy} to display +* a small visual scroll indicator. +* +* `enyo/NewThumb` is not typically created in application code. * -* @class ScrollThumb +* @class NewThumb * @protected */ module.exports = kind( - /** @lends module:enyo/ScrollThumb~ScrollThumb.prototype */ { + /** @lends module:enyo/NewThumb~NewThumb.prototype */ { /** * @private diff --git a/src/Scrim/Scrim.js b/src/Scrim/Scrim.js index 2bd49a28d..0768fb32b 100644 --- a/src/Scrim/Scrim.js +++ b/src/Scrim/Scrim.js @@ -5,8 +5,6 @@ require('enyo'); * @module enyo/Scrim */ - - var kind = require('../kind'), utils = require('../utils'); @@ -159,11 +157,11 @@ var Scrim = module.exports = kind( * Scrim singleton exposing a subset of the Scrim API; * it is replaced with a proper {@link module:enyo/Scrim~Scrim} instance. * -* @class enyo.scrimSingleton +* @class ScrimSingleton * @private */ var ScrimSingleton = kind( - /** @lends module:enyo/scrimSingleton~scrimSingleton.prototype */ { + /** @lends module:enyo/Scrim~ScrimSingleton.prototype */ { /** * @private diff --git a/src/ScrollStrategy.js b/src/ScrollStrategy.js index 487893db6..34e5b50c1 100644 --- a/src/ScrollStrategy.js +++ b/src/ScrollStrategy.js @@ -16,9 +16,9 @@ var * {@link module:enyo/ScrollStrategy~ScrollStrategy} is a helper [kind]{@glossary kind} that implements a default * scrolling strategy for an {@link module:enyo/Scroller~Scroller}. * -* `enyo.ScrollStrategy` is not typically created in application code. Instead, it is specified +* `enyo/ScrollStrategy` is not typically created in application code. Instead, it is specified * as the value of the [strategyKind]{@link module:enyo/Scroller~Scroller#strategyKind} property of an -* `enyo.Scroller` or {@link module:layout/List#List}, or is used by the framework implicitly. +* `enyo/Scroller` or {@link module:layout/List~List}, or is used by the framework implicitly. * * @class ScrollStrategy * @protected diff --git a/src/ScrollThumb/ScrollThumb.js b/src/ScrollThumb/ScrollThumb.js index 0aefe13a9..27908d28d 100644 --- a/src/ScrollThumb/ScrollThumb.js +++ b/src/ScrollThumb/ScrollThumb.js @@ -17,7 +17,7 @@ var * {@link module:enyo/TouchScrollStrategy~TouchScrollStrategy} and {@link module:enyo/TranslateScrollStrategy~TranslateScrollStrategy} to * display a small visual scroll indicator. * -* `enyo.ScrollThumb` is not typically created in application code. +* `enyo/ScrollThumb` is not typically created in application code. * * @class ScrollThumb * @protected diff --git a/src/TouchScrollStrategy.js b/src/TouchScrollStrategy.js index 90f63b1cb..42a2d5eb2 100644 --- a/src/TouchScrollStrategy.js +++ b/src/TouchScrollStrategy.js @@ -34,11 +34,11 @@ var /** * {@link module:enyo/TouchScrollStrategy~TouchScrollStrategy} is a helper [kind]{@glossary kind} for implementing a * touch-based [scroller]{@link module:enyo/Scroller~Scroller}. It integrates the scrolling simulation provided -* by {@link module:enyo/ScrollMath~ScrollMath} into an `enyo.Scroller`. +* by {@link module:enyo/ScrollMath~ScrollMath} into an `enyo/Scroller`. * -* `enyo.TouchScrollStrategy` is not typically created in application code. Instead, it is +* `enyo/TouchScrollStrategy` is not typically created in application code. Instead, it is * specified as the value of the [strategyKind]{@link module:enyo/Scroller~Scroller#strategyKind} property -* of an `enyo.Scroller` or {@link module:layout/List#List}, or is used by the framework implicitly. +* of an `enyo/Scroller` or {@link module:layout/List~List}, or is used by the framework implicitly. * * @class TouchScrollStrategy * @extends module:enyo/ScrollStrategy~ScrollStrategy @@ -806,7 +806,7 @@ module.exports = kind( /** * This method exists primarily to support an internal use case for - * [enyo.DataList]{@link module:enyo/DataList~DataList}. It is intended to be called by the + * [enyo/DataList]{@link module:enyo/DataList~DataList}. It is intended to be called by the * [scroller]{@link module:enyo/Scroller~Scroller} that owns this strategy. * * Triggers a remeasurement of the scroller's metrics (specifically, the diff --git a/src/TranslateScrollStrategy.js b/src/TranslateScrollStrategy.js index e8fb449c8..3be9e1800 100644 --- a/src/TranslateScrollStrategy.js +++ b/src/TranslateScrollStrategy.js @@ -20,7 +20,7 @@ var * * `TranslateScrollStrategy` is not typically created in application code. Instead, it is * specified as the value of the [strategyKind]{@link module:enyo/Scroller~Scroller#strategyKind} property of -* an {@link module:enyo/Scroller~Scroller} or {@link module:layout/List#List}, or is used by the framework implicitly. +* an {@link module:enyo/Scroller~Scroller} or {@link module:layout/List~List}, or is used by the framework implicitly. * * @class TranslateScrollStrategy * @extends module:enyo/TouchScrollStrategy~TouchScrollStrategy From cdaa40d4d22bebe14818b7529facb7902481feaa Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Wed, 23 Sep 2015 15:56:16 -0700 Subject: [PATCH 096/195] ENYO-2556: Allow for reversing animation in RTL. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/LightPanels/LightPanels.js | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index 9cbe08247..2a8e5b121 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -176,6 +176,17 @@ module.exports = kind( */ direction: Direction.FORWARDS, + /** + * Whether or not to reverse the panel animation when the directionality changes (i.e. rtl). Note + * that the animation is only reversed if {@link module:enyo/LightPanels~LightPanels} is in a + * [horizontal orientation]{@link module:enyo/LightPanels~LightPanels#Orientation.HORIZONTAL}. + * + * @type {Boolean} + * @default false + * @public + */ + reverseForRtl: false, + /** * @private */ @@ -224,11 +235,16 @@ module.exports = kind( * @private */ directionChanged: function (was) { - var key, value; + var shouldReverse = this.reverseForRtl && this.rtl && this.orientation == Orientation.HORIZONTAL, + key, value; + + // Set internal direction property that respects RTL, if desired + this._direction = this.direction * (shouldReverse ? -1 : 1); + for (key in Direction) { value = Direction[key]; if (value == was) this.removeClass(key.toLowerCase()); - if (value == this.direction) this.addClass(key.toLowerCase()); + if (value == this._direction) this.addClass(key.toLowerCase()); } }, @@ -756,7 +772,7 @@ module.exports = kind( var container = this.$.client, value; - if (this.direction == Direction.FORWARDS) value = indexDirection > 0 ? -50 : 0; + if (this._direction == Direction.FORWARDS) value = indexDirection > 0 ? -50 : 0; else value = indexDirection > 0 ? 0 : -50; container.applyStyle('-webkit-transition', animate ? wTrans : null); From e4c4fb2621e0e0447baeae804e52f515e4456fa3 Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Thu, 24 Sep 2015 15:33:00 -0700 Subject: [PATCH 097/195] JSDoc cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/HorizontalDelegate.js | 2 +- src/ProxyObject.js | 10 +++++----- src/Scrollable/Scrollable.js | 26 +++++++++++++------------- src/StylesheetSupport.js | 8 ++++++-- src/animation.js | 10 +++++----- src/cookie.js | 4 ++-- src/dom.js | 24 +++++++++++++++--------- src/logger.js | 18 +++++++++--------- src/pageVisibility.js | 10 ++++++++-- src/utils.js | 8 ++++---- 10 files changed, 68 insertions(+), 52 deletions(-) diff --git a/src/HorizontalDelegate.js b/src/HorizontalDelegate.js index 361386b1c..2d62bdb27 100644 --- a/src/HorizontalDelegate.js +++ b/src/HorizontalDelegate.js @@ -11,7 +11,7 @@ var * for horizontally-oriented lists. This is used by all lists for this strategy; * it does not get copied, but is called directly from the list. * -* Note that this is based on the [vertical delegate]{@link module:enyo/DataList~DataList.delegates.vertical} +* Note that this is based on the [vertical delegate]{@link module:enyo/VerticalDelegate} * and shares most of that delegate's logic. Overloads are implemented only where necessary. * * @module enyo/HorizontalDelegate diff --git a/src/ProxyObject.js b/src/ProxyObject.js index a6be6e4a6..4a23d8f1e 100644 --- a/src/ProxyObject.js +++ b/src/ProxyObject.js @@ -5,8 +5,8 @@ var /** * A {@glossary mixin} designed to abstract interaction of the -* [enyo.Object.get()]{@link module:enyo/CoreObject~Object#get} and -* [enyo.Object.set()]{@link module:enyo/CoreObject~Object#set} methods. It does not need to be +* {@link module:enyo/CoreObject~Object#get} and +* {@link module:enyo/CoreObject~Object#set} methods. It does not need to be * applied to [subkinds]{@glossary subkind} of {@link module:enyo/CoreObject~Object}. * * @module enyo/ProxyObject @@ -32,10 +32,10 @@ module.exports = { /** * The overloaded [getter]{@link module:enyo/CoreObject~Object#get}. Accepts the same * parameters and attempts to call the same method on the - * [proxy]{@link module:enyo/ProxyObject~ProxyObject.proxyObjectKey}. Returns `undefined` if it + * [proxy]{@link module:enyo/ProxyObject~ProxyObject#proxyObjectKey}. Returns `undefined` if it * cannot find a proxy. * - * @see module:enyo/CoreObject~Object.get + * @see module:enyo/CoreObject~Object#get * @see module:enyo/utils#getPath * @param {String} path - The path from which to retrieve a value. * @returns {this} The value for the given path, or `undefined` if the path @@ -58,7 +58,7 @@ module.exports = { /** * The overloaded [setter]{@link module:enyo/CoreObject~Object#set}. Accepts the same * parameters and attempts to call the same method on the - * [proxy]{@link module:enyo/ProxyObject~ProxyObject~proxyObjectKey}. Returns the callee for + * [proxy]{@link module:enyo/ProxyObject~ProxyObject#proxyObjectKey}. Returns the callee for * chaining if it cannot find the proxy. * * @param {String} path - The path for which to set the given value. diff --git a/src/Scrollable/Scrollable.js b/src/Scrollable/Scrollable.js index 15bee5b83..4df032273 100644 --- a/src/Scrollable/Scrollable.js +++ b/src/Scrollable/Scrollable.js @@ -83,7 +83,7 @@ module.exports = { * * @type {Number} * @default null - * @memberof enyo.Scroller.prototype + * @memberof module:enyo/Scroller~Scroller.prototype * @public */ maxHeight: null, @@ -103,8 +103,8 @@ module.exports = { /** * TODO: Document. Based on CSSOM View spec (). * Options: 'smooth', 'instant', maybe 'auto' - * See: http://dev.w3.org/csswg/cssom-view/ - * https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView + * @see {@linkplain http://dev.w3.org/csswg/cssom-view/} + * @see {@linkplain https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView} * * @type {String} * @default 'smooth' @@ -116,8 +116,8 @@ module.exports = { * TODO: Document. Based on CSSOM View spec (), but modified to add 'nearest' and * 'farthest' to the possible values. * Options: 'start', 'end', 'nearest', 'farthest' - * See: http://dev.w3.org/csswg/cssom-view/ - * https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView + * @see {@linkplain http://dev.w3.org/csswg/cssom-view/} + * @see {@linkplain https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView} * * @type {String} * @default 'nearest' @@ -309,8 +309,8 @@ module.exports = { /** * TODO: Document. Based on CSSOM View spec () - * See: http://dev.w3.org/csswg/cssom-view/ - * https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView + * @see {@linkplain http://dev.w3.org/csswg/cssom-view/} + * @see {@linkplain https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView} * * @public */ @@ -322,8 +322,8 @@ module.exports = { /** * TODO: Document. Based on CSSOM View spec () - * See: http://dev.w3.org/csswg/cssom-view/ - * https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView + * @see {@linkplain http://dev.w3.org/csswg/cssom-view/} + * @see {@linkplain https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView} * * @public */ @@ -337,8 +337,8 @@ module.exports = { /** * TODO: Document. Based on CSSOM View spec () - * See: http://dev.w3.org/csswg/cssom-view/ - * https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView + * @see {@linkplain http://dev.w3.org/csswg/cssom-view/} + * @see {@linkplain https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView} * * @public */ @@ -985,7 +985,7 @@ module.exports = { * scroll controls, or if `control` is not a child of the * Scrollable at all. * - * @param {enyo.Control} control - The control to be tested + * @param {module:enyo/Control~Control} control - The control to be tested * @protected */ isScrollingChild: function (control) { @@ -1005,7 +1005,7 @@ module.exports = { * Returns `true` if `control` is one of the Scrollable's * scroll controls. * - * @param {enyo.Control} control - The control to be tested + * @param {module:enyo/Control~Control} control - The control to be tested * @protected */ isScrollControl: function (control) { diff --git a/src/StylesheetSupport.js b/src/StylesheetSupport.js index 982a3339a..a288118ad 100644 --- a/src/StylesheetSupport.js +++ b/src/StylesheetSupport.js @@ -1,3 +1,8 @@ +/** +* Exports the {@link module:enyo/StylesheetSupport~StylesheetSupport} mixin. +* @module enyo/StylesheetSupport +*/ + require('enyo'); var @@ -12,7 +17,7 @@ var * for procedurally-generated CSS that can't live in the more appropriate * location (i.e., in a CSS/LESS file). * -* @module enyo/StylesheetSupport +* @mixin * @public */ module.exports = { @@ -37,7 +42,6 @@ module.exports = { * * @type {String} * @default '' - * @memberof enyo.StylesheetSupport.prototype * @public */ stylesheetContent: '' diff --git a/src/animation.js b/src/animation.js index 39a41f663..2aa09be80 100644 --- a/src/animation.js +++ b/src/animation.js @@ -67,7 +67,7 @@ for (var i = 0, pl = prefix.length, p, wc, wr; (p = prefix[i]) || i < pl; i++) { * animation frame. * @param {Node} node - The DOM node to request the animation frame for. * @returns {Object} A request id to be used with -* [enyo.cancelRequestAnimationFrame()]{@link module:enyo/cancelRequestAnimationFrame~cancelRequestAnimationFrame}. +* {@link module:enyo/animation#cancelRequestAnimationFrame}. * @public */ exports.requestAnimationFrame = function(callback, node) { @@ -86,13 +86,13 @@ exports.cancelRequestAnimationFrame = function(inId) { * A set of interpolation functions for animations, similar in function to CSS3 * transitions. * -* These are intended for use with {@link module:enyo/animation.easedLerp}. Each easing function +* These are intended for use with {@link module:enyo/animation#easedLerp}. Each easing function * accepts one (1) [Number]{@glossary Number} parameter and returns one (1) * [Number]{@glossary Number} value. * * @public */ -exports.easing = /** @lends module:enyo/easing~easing */ { +exports.easing = /** @lends module:enyo/animation~easing.prototype */ { /** * cubicIn * @@ -149,7 +149,7 @@ exports.easing = /** @lends module:enyo/easing~easing */ { * @param {Number} t0 - Start time. * @param {Number} duration - Duration in milliseconds. * @param {Function} easing - An easing [function]{@glossary Function} reference from -* {@link module:enyo/easing~easing}. +* {@link module:enyo/animation#easing}. * @param {Boolean} reverse - Whether the animation will run in reverse. * @returns {Number} The resulting position, capped at a maximum of 100%. * @public @@ -173,7 +173,7 @@ exports.easedLerp = function(t0, duration, easing, reverse) { * @param {Number} t0 - Start time. * @param {Number} duration - Duration in milliseconds. * @param {Function} easing - An easing [function]{@glossary Function} reference from -* {@link module:enyo/easing~easing}. +* {@link module:enyo/animation#easing}. * @param {Boolean} reverse - Whether the animation will run in reverse. * @param {Number} time * @param {Number} startValue - Starting value. diff --git a/src/cookie.js b/src/cookie.js index e99cf4575..d9e50f1f8 100644 --- a/src/cookie.js +++ b/src/cookie.js @@ -38,7 +38,7 @@ exports.getCookie = function(nom) { * Sets a [cookie]{@glossary cookie} for the given name and value to * [document.cookie]{@glossary document.cookie}. Use the optional configuration * [hash]{@glossary Object} to specify the -* [cookie properties]{@link enyo~CookieProperties}. You may remove a +* [cookie properties]{@link module:enyo/cookie~CookieOptions}. You may remove a * cookie using this method by setting its `Max-Age` value to `0`. * * Also note that if you are developing in Google Chrome with a local file as your @@ -47,7 +47,7 @@ exports.getCookie = function(nom) { * * @param {String} nom - The name of the cookie. * @param {*} value - The value to be [encoded]{@glossary encodeURIComponent} for storage. -* @param {module:enyo/cookie~CookieProperties} [props] The optional configuration properties to apply to +* @param {module:enyo/cookie~CookieOptions} [props] The optional configuration properties to apply to * the cookie. * @public */ diff --git a/src/dom.js b/src/dom.js index 63e5ffc4e..bcef59c27 100644 --- a/src/dom.js +++ b/src/dom.js @@ -17,8 +17,11 @@ var dom = module.exports = { * (optional) `doc` parameter. * * ```javascript + * var + * dom = require('enyo/dom'); + * * // find 'node' if it's a string id, or return it unchanged if it's already a node reference - * var domNode = enyo.dom.byId(node); + * var domNode = dom.byId(node); * ``` * * @param {String} id - The document element ID to get. @@ -296,7 +299,7 @@ var dom = module.exports = { /** * Gets the calculated padding of a node. Shortcut for - * [enyo.dom.calcBoxExtents()]{@link enyo.dom.calcBoxExtents}. + * {@link module:enyo/dom#calcBoxExtents}. * * @param {Node} node - The [node]{@glossary Node} to measure. * @returns {Object} An object containing the properties `top`, `right`, `bottom`, and @@ -309,7 +312,7 @@ var dom = module.exports = { /** * Gets the calculated margin of a node. Shortcut for - * [enyo.dom.calcBoxExtents()]{@link enyo.dom.calcBoxExtents}. + * {@link module:enyo/dom#calcBoxExtents}. * * @param {Node} node - The [node]{@glossary Node} to measure. * @returns {Object} An object containing the properties `top`, `right`, `bottom`, and @@ -522,13 +525,16 @@ var dom = module.exports = { /** * Convert to various unit formats. Useful for converting pixels to a resolution-independent * measurement method, like "rem". Other units are available if defined in the - * [enyo.dom.unitToPixelFactors]{@link enyo.dom.unitToPixelFactors} object. + * {@link module:enyo/dom#unitToPixelFactors} object. * * ```javascript + * var + * dom = require('enyo/dom'); + * * // Do calculations and get back the desired CSS unit. * var frameWidth = 250, - * frameWithMarginInches = enyo.dom.unit( 10 + frameWidth + 10, 'in' ), - * frameWithMarginRems = enyo.dom.unit( 10 + frameWidth + 10, 'rem' ); + * frameWithMarginInches = dom.unit( 10 + frameWidth + 10, 'in' ), + * frameWithMarginRems = dom.unit( 10 + frameWidth + 10, 'rem' ); * // '2.8125in' == frameWithMarginInches * // '22.5rem' == frameWithMarginRems * ``` @@ -770,9 +776,9 @@ dom.transformValue = function(control, transform, value) { /** * Applies a transform that should trigger GPU compositing for the specified -* {@link enyo.Control}. By default, the acceleration is only applied if the -* browser supports it. You may also optionally force-set `value` directly, to -* be applied to `translateZ(value)`. +* {@link module:enyo/Control~Control}. By default, the acceleration is only +* applied if the browser supports it. You may also optionally force-set `value` +* directly, to be applied to `translateZ(value)`. * * @param {module:enyo/Control~Control} control - The {@link module:enyo/Control~Control} to accelerate. * @param {(String|Number)} [value] - An optional value to apply to the acceleration transform diff --git a/src/logger.js b/src/logger.js index db3f524dc..83440115f 100644 --- a/src/logger.js +++ b/src/logger.js @@ -27,7 +27,7 @@ exports = module.exports = { /** * The log level to use. Can be a value from -1 to 99, where -1 disables all * logging, 0 is 'error', 10 is 'warn', and 20 is 'log'. It is preferred that - * this value be set using the [setLogLevel()]{@link module:enyo/logging.setLogLevel} + * this value be set using the [setLogLevel()]{@link module:enyo/logging#setLogLevel} * method. * * @type {Number} @@ -125,12 +125,12 @@ exports = module.exports = { * Sets the log level to the given value. This will restrict the amount of output depending on * the settings. The higher the value, the more output that will be allowed. The default is * 99. The value, -1, would silence all logging, even 'error' (0). -* Without the 'see': {@link module:enyo/logger.log}. +* Without the 'see': {@link module:enyo/logging#log}. * -* @see module:enyo/logging.level -* @see module:enyo/logging.log -* @see module:enyo/logging.warn -* @see module:enyo/logging.error +* @see module:enyo/logging#level +* @see module:enyo/logging#log +* @see module:enyo/logging#warn +* @see module:enyo/logging#error * @param {Number} level - The level to set logging to. */ exports.setLogLevel = function (level) { @@ -143,7 +143,7 @@ exports.setLogLevel = function (level) { /** * A wrapper for [console.log()]{@glossary console.log}, compatible * across supported platforms. Will output only if the current -* [log level]{@link module:enyo/logging.level} allows it. [Object]{@glossary Object} +* [log level]{@link module:enyo/logging#level} allows it. [Object]{@glossary Object} * parameters will be serialized via [JSON.stringify()]{@glossary JSON.stringify} * automatically. * @@ -156,7 +156,7 @@ exports.setLogLevel = function (level) { /** * A wrapper for [console.warn()]{@glossary console.warn}, compatible * across supported platforms. Will output only if the current -* [log level]{@link module:enyo/logging.level} allows it. [Object]{@glossary Object} +* [log level]{@link module:enyo/logging#level} allows it. [Object]{@glossary Object} * parameters will be serialized via [JSON.stringify()]{@glossary JSON.stringify} * automatically. * @@ -172,7 +172,7 @@ exports.warn = function () { /** * A wrapper for [console.error()]{@glossary console.error}, compatible * across supported platforms. Will output only if the current -* [log level]{@link module:enyo/logging.level} allows it. [Object]{@glossary Object} +* [log level]{@link module:enyo/logging#level} allows it. [Object]{@glossary Object} * parameters will be serialized via [JSON.stringify()]{@glossary JSON.stringify} * automatically. * diff --git a/src/pageVisibility.js b/src/pageVisibility.js index c13189276..c493e7ff9 100644 --- a/src/pageVisibility.js +++ b/src/pageVisibility.js @@ -10,11 +10,16 @@ var * `document.visibilityState` in supported browsers. The `visibilitychange` * event is channelled through the [Signals]{@link module:enyo/Signals~Signals} mechanism. * -* Partly based on {@link http://stackoverflow.com/a/1060034}. +* Partly based on {@linkplain http://stackoverflow.com/a/1060034}. * * Example: +* * ```javascript -* enyo.kind({ +* var +* kind = require('enyo/kind'), +* Signals = require('enyo/Signals'); +* +* module.exports = kind({ * name: 'App', * components: [ * {kind: 'Signals', onvisibilitychange: 'visibilitychanged'} @@ -28,6 +33,7 @@ var * } * }); * ``` +* * @module enyo/pageVisibility * @private */ diff --git a/src/utils.js b/src/utils.js index dd3f80df3..3eb6980c6 100644 --- a/src/utils.js +++ b/src/utils.js @@ -305,7 +305,7 @@ var bind = exports.bind = function (scope, method) { /** * Binds a callback to a scope. If the object has a `destroyed` property that's truthy, then the * callback will not be run if called. This can be used to implement both -* {@link module:enyo/CoreObject~Object.bindSafely} and {@link module:enyo/CoreObject~Object}-like objects like +* {@link module:enyo/CoreObject~Object#bindSafely} and {@link module:enyo/CoreObject~Object}-like objects like * {@link module:enyo/Model~Model} and {@link module:enyo/Collection~Collection}. * * @param {Object} scope - The `this` context for the method. @@ -405,8 +405,8 @@ var now = exports.now = Date.now || function () { * When [window.performance]{@glossary window.performance} is available, supplies * a high-precision, high-performance monotonic timestamp, which is independent of * changes to the system clock and thus safer for use in animation, etc. Falls back to -* [enyo.now()]{@link enyo#now} (based on the JavaScript [Date]{@glossary Date} -* object), which is subject to system time changes. +* {@link module:enyo/utils#now} (based on the JavaScript [Date]{@glossary Date} object), +* which is subject to system time changes. * * @returns {Number} Number of milliseconds representing the current time or time since * start of application execution as reported by the platform. @@ -1323,7 +1323,7 @@ exports.remove = function (array, el) { }; /** -* This regex pattern is used by the [isRtl()]{@link module:enyo/util#isRtl} function. +* This regex pattern is used by the [isRtl()]{@link module:enyo/utils#isRtl} function. * * Arabic: \u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFE * Hebrew: \u0590-\u05FF\uFB1D-\uFB4F From 4b5f4ff253cc01b757b3ef5fb5c6a145c618d6f6 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Fri, 25 Sep 2015 02:59:35 -0700 Subject: [PATCH 098/195] ENYO-2592: Add replaceAt method. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/LightPanels/LightPanels.js | 43 +++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index 9cbe08247..ab62af523 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -9,7 +9,7 @@ var kind = require('../kind'), dom = require('../dom'), animation = require('../animation'), - asyncMethod = require('../utils').asyncMethod; + utils = require('../utils'); var Control = require('../Control'), @@ -268,10 +268,10 @@ module.exports = kind( /** * @private */ - indexChanged: function (was, is) { + indexChanged: function (was, is, nom, opts) { // when notifyObservers is called without arguments, we do not need to do any work here // (since there's no transition required with the indices are the same) - if (was !== is) { + if (was !== is || (opts && opts.force)) { this.setupTransitions(was); } }, @@ -394,7 +394,7 @@ module.exports = kind( nextPanel.postTransition(); } - if (!this.animate || (opts && opts.direct)) this.set('index', newIndex); + if (!this.animate || (opts && opts.direct)) this.set('index', newIndex, {force: opts && opts.force}); else this.animateTo(newIndex); // TODO: When pushing panels after we have gone back (but have not popped), we need to @@ -444,7 +444,7 @@ module.exports = kind( targetIdx = (opts && opts.targetIndex != null) ? opts.targetIndex : lastIndex + newPanels.length - 1; - if (!this.animate || (opts && opts.direct)) this.set('index', targetIdx); + if (!this.animate || (opts && opts.direct)) this.set('index', targetIdx, {force: opts && opts.force}); else this.animateTo(targetIdx); return newPanels; @@ -495,6 +495,37 @@ module.exports = kind( } }, + /** + * Replaces the panel(s) at the specified index with panels that will be created via provided + * component definition(s). + * + * @param {Number} start - The index where we wish to begin the replacement. If this is negative, + * it will be treated as `panelsLength` + `start` i.e. we will use the absolute value of the + * start index as a count from the end of the set of panels. + * @param {Number} count - The number of panels we wish to replace. + * @param {Object|Object[]} info - The component definition (or array of component definitions) + * for the replacement panel(s). + * @public + */ + replaceAt: function (start, count, info) { + var panels = this.getPanels(), + insertBefore, commonInfo, end, idx; + + start = start < 0 ? panels.length + start : start; + end = start + count; + insertBefore = panels[end]; + commonInfo = {addBefore: insertBefore}; + + // remove existing panels + for (idx = start; idx < end; idx++) { + this.popPanel(idx); + } + + // add replacement panels + if (utils.isArray(info)) this.pushPanels(info, commonInfo, {direct: true, force: true}); + else this.pushPanel(info, commonInfo, {direct: true, force: true}); + }, + /* @@ -605,7 +636,7 @@ module.exports = kind( if (panel.postTransition) { // Async'ing this as it seems to improve ending transition performance on the TV. // Requires further investigation into its behavior. - asyncMethod(this, function () { + utils.asyncMethod(this, function () { panel.postTransition(); }); } From 73d3cd5289e40908cf484aceb811cf574150df4d Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Fri, 25 Sep 2015 11:59:46 -0700 Subject: [PATCH 099/195] ENYO-2592: Undefer purging. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/LightPanels/LightPanels.js | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index ab62af523..e6f5e67d5 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -668,8 +668,6 @@ module.exports = kind( prevPanel.addClass('offscreen'); } - if (this.popQueue && this.popQueue.length) this.finalizePurge(); - this.cleanUpPanel(prevPanel); this.cleanUpPanel(currPanel); @@ -802,20 +800,9 @@ module.exports = kind( * @private */ purge: function () { - var panels = this.getPanels(); - this.popQueue = panels.slice(); - panels.length = 0; - this.index = -1; - }, - - /** - * Clean-up any panels queued for destruction. - * - * @private - */ - finalizePurge: function () { - var panels = this.popQueue, + var panels = this.getPanels(), panel; + while (panels.length) { panel = panels.pop(); if (this.cacheViews) { @@ -825,6 +812,8 @@ module.exports = kind( panel.destroy(); } } + + this.index = -1; }, /** From 68dda6ebc2f5d8bc8b0a4d49846d4d08d0ddc90d Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Fri, 25 Sep 2015 13:15:17 -0700 Subject: [PATCH 100/195] JSDoc cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/ProxyObject.js | 4 ++-- src/StylesheetSupport.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ProxyObject.js b/src/ProxyObject.js index 4a23d8f1e..525b01413 100644 --- a/src/ProxyObject.js +++ b/src/ProxyObject.js @@ -32,7 +32,7 @@ module.exports = { /** * The overloaded [getter]{@link module:enyo/CoreObject~Object#get}. Accepts the same * parameters and attempts to call the same method on the - * [proxy]{@link module:enyo/ProxyObject~ProxyObject#proxyObjectKey}. Returns `undefined` if it + * [proxy]{@link module:enyo/ProxyObject#proxyObjectKey}. Returns `undefined` if it * cannot find a proxy. * * @see module:enyo/CoreObject~Object#get @@ -58,7 +58,7 @@ module.exports = { /** * The overloaded [setter]{@link module:enyo/CoreObject~Object#set}. Accepts the same * parameters and attempts to call the same method on the - * [proxy]{@link module:enyo/ProxyObject~ProxyObject#proxyObjectKey}. Returns the callee for + * [proxy]{@link module:enyo/ProxyObject#proxyObjectKey}. Returns the callee for * chaining if it cannot find the proxy. * * @param {String} path - The path for which to set the given value. diff --git a/src/StylesheetSupport.js b/src/StylesheetSupport.js index a288118ad..7370fe900 100644 --- a/src/StylesheetSupport.js +++ b/src/StylesheetSupport.js @@ -1,5 +1,5 @@ /** -* Exports the {@link module:enyo/StylesheetSupport~StylesheetSupport} mixin. +* Exports the {@link module:enyo/StylesheetSupport} mixin. * @module enyo/StylesheetSupport */ @@ -12,7 +12,7 @@ var Style = require('./Style'); /** -* The {@link module:enyo/StylesheetSupport~StylesheetSupport} {@glossary mixin} is used to add a +* The {@link module:enyo/StylesheetSupport} {@glossary mixin} is used to add a * "side-car" inline stylesheet to a [control]{@link module:enyo/Control~Control}, specifically * for procedurally-generated CSS that can't live in the more appropriate * location (i.e., in a CSS/LESS file). From b4c0036fe7eb2c13ca7635f7684a7008df785027 Mon Sep 17 00:00:00 2001 From: Roy Sutton Date: Fri, 25 Sep 2015 16:38:26 -0700 Subject: [PATCH 101/195] ENYO-2416 Fix issues with VideoPlayer == Issue The Video component would not always successfully start playing the video after a rewind operation. == Solution This problem was, at least in part, caused by the need to emulate rewind on platforms that don't support negative `playbackRate`s. Additionally, a problem was discovered with handling the switch between slow and fastForward. The slowForward playback rate array was updated to remove the rate of '1' (normal speed) and the fastforward mechanism is updated to move to play when reaching the end of the playback rate array (Previously, it rotated to the fast forward array after playing the '1' rate). There should be no change for apps that did not alter the playback rate array. The player has also been updated so that rewinding after a slowForward will now result in a slowRewind. The converse also applies. --- src/Video.js | 60 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/src/Video.js b/src/Video.js index 720f00ca6..892ff660f 100644 --- a/src/Video.js +++ b/src/Video.js @@ -263,7 +263,7 @@ module.exports = kind( * @default { * fastForward: ['2', '4', '8', '16'], * rewind: ['-2', '-4', '-8', '-16'], - * slowForward: ['1/4', '1/2', '1'], + * slowForward: ['1/4', '1/2'], * slowRewind: ['-1/2', '-1'] * } * @public @@ -271,7 +271,7 @@ module.exports = kind( playbackRateHash: { fastForward: ['2', '4', '8', '16'], rewind: ['-2', '-4', '-8', '-16'], - slowForward: ['1/4', '1/2', '1'], + slowForward: ['1/4', '1/2'], slowRewind: ['-1/2', '-1'] } }, @@ -508,13 +508,11 @@ module.exports = kind( switch (this._prevCommand) { case 'slowForward': if (this._speedIndex == this._playbackRateArray.length - 1) { - // reached to the end of array => go to fastforward - this.selectPlaybackRateArray('fastForward'); - this._speedIndex = 0; - this._prevCommand = 'fastForward'; + // reached to the end of array => go to play + this.play(); + return; } else { this._speedIndex = this.clampPlaybackRate(this._speedIndex+1); - this._prevCommand = 'slowForward'; } break; case 'pause': @@ -526,14 +524,14 @@ module.exports = kind( this._prevCommand = 'slowForward'; break; case 'rewind': - var pbNumber = this.calcNumberValueOfPlaybackRate(this.playbackRate); - if (pbNumber < 0) { - this.selectPlaybackRateArray('slowForward'); - this._prevCommand = 'slowForward'; - } else { - this.selectPlaybackRateArray('fastForward'); - this._prevCommand = 'fastForward'; - } + node.play(); + this.selectPlaybackRateArray('fastForward'); + this._prevCommand = 'fastForward'; + this._speedIndex = 0; + break; + case 'slowRewind': + this.selectPlaybackRateArray('slowForward'); + this._prevCommand = 'slowForward'; this._speedIndex = 0; break; case 'fastForward': @@ -572,9 +570,18 @@ module.exports = kind( this._prevCommand = 'rewind'; } else { this._speedIndex = this.clampPlaybackRate(this._speedIndex+1); - this._prevCommand = 'slowRewind'; } break; + case 'fastForward': + this.selectPlaybackRateArray('rewind'); + this._prevCommand = 'rewind'; + this._speedIndex = 0; + break; + case 'slowForward': + this.selectPlaybackRateArray('slowRewind'); + this._prevCommand = 'slowRewind'; + this._speedIndex = 0; + break; case 'pause': this.selectPlaybackRateArray('slowRewind'); this._speedIndex = 0; @@ -607,16 +614,23 @@ module.exports = kind( * @public */ jumpBackward: function () { - var node = this.hasNode(); + var node = this.hasNode(), + oldPlaybackRateNumber; if (!node) { return; } + oldPlaybackRateNumber = this.calcNumberValueOfPlaybackRate(this.playbackRate); + this.setPlaybackRate(1); node.currentTime -= this.jumpSec; this._prevCommand = 'jumpBackward'; + if(oldPlaybackRateNumber < 0) { + this.node.play(); + } + this.doJumpBackward(utils.mixin(this.createEventData(), {jumpSize: this.jumpSec})); }, @@ -627,16 +641,23 @@ module.exports = kind( * @public */ jumpForward: function () { - var node = this.hasNode(); + var node = this.hasNode(), + oldPlaybackRateNumber; if (!node) { return; } + oldPlaybackRateNumber = this.calcNumberValueOfPlaybackRate(this.playbackRate); + this.setPlaybackRate(1); node.currentTime += parseInt(this.jumpSec, 10); this._prevCommand = 'jumpForward'; + if(oldPlaybackRateNumber < 0) { + this.node.play(); + } + this.doJumpForward(utils.mixin(this.createEventData(), {jumpSize: this.jumpSec})); }, @@ -723,8 +744,7 @@ module.exports = kind( */ setPlaybackRate: function (rate) { var node = this.hasNode(), - pbNumber - ; + pbNumber; if (!node) { return; From ea9957a6f04bab6d1bdf8c49fbc38e8558eb2399 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Mon, 28 Sep 2015 14:50:07 -0700 Subject: [PATCH 102/195] PLAT-8119: Use generic node removal method. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/Control/Control.js | 14 ++++++++------ src/Popup/Popup.js | 5 +---- src/ViewPreloadSupport.js | 8 ++------ 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/Control/Control.js b/src/Control/Control.js index 0132d3415..8e98f9e14 100644 --- a/src/Control/Control.js +++ b/src/Control/Control.js @@ -241,7 +241,7 @@ var Control = module.exports = kind( * @public */ renderOnShow: false, - + /** * @todo Find out how to document "handlers". * @public @@ -792,7 +792,7 @@ var Control = module.exports = kind( // our last known value for display was or use the default if (!was && this.showing) { this.applyStyle('display', this._display || ''); - + // note the check for generated and canGenerate as changes to canGenerate will force // us to ignore the renderOnShow value so we don't undo whatever the developer was // intending @@ -802,7 +802,7 @@ var Control = module.exports = kind( this.set('canGenerate', true); this.render(); } - + this.sendShowingChangedEvent(was); } @@ -829,7 +829,7 @@ var Control = module.exports = kind( // the state of renderOnShow this.canGenerate = (this.canGenerate && !this.renderOnShow); }, - + /** * @private */ @@ -1197,8 +1197,10 @@ var Control = module.exports = kind( * @private */ removeNodeFromDom: function() { - if (this.hasNode() && this.node.parentNode) { - this.node.parentNode.removeChild(this.node); + var node = this.hasNode(); + if (node) { + if (node.remove) node.remove(); + else if (node.parentNode) node.parentNode.removeChild(node); } }, diff --git a/src/Popup/Popup.js b/src/Popup/Popup.js index 4b2063d35..89c7a7e72 100644 --- a/src/Popup/Popup.js +++ b/src/Popup/Popup.js @@ -313,10 +313,7 @@ var Popup = module.exports = kind( return function () { // if this is a rendered floating popup, remove the node from the // floating layer because it won't be removed otherwise - var node = this.hasNode(); - if(this.floating && node) { - this.node.remove(); - } + if (this.floating) this.removeNodeFromDom(); sup.apply(this, arguments); }; diff --git a/src/ViewPreloadSupport.js b/src/ViewPreloadSupport.js index fc554c120..31bc57845 100644 --- a/src/ViewPreloadSupport.js +++ b/src/ViewPreloadSupport.js @@ -117,12 +117,8 @@ var ViewPreloadSupport = { // The panel could have already been removed from DOM and torn down if we popped when // moving forward. - if (view.node) { - if (!view.node.remove) { // Polyfill remove for safari browser - view.parent.node.removeChild(view.node); - } else { - view.node.remove(); - } + if (view.hasNode()) { + view.removeNodeFromDom(); view.teardownRender(true); } From 483200f171b299beebfe47dd5d270f84ac5fabe3 Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Mon, 28 Sep 2015 15:46:43 -0700 Subject: [PATCH 103/195] JSDoc cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/Scroller/Scroller.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Scroller/Scroller.js b/src/Scroller/Scroller.js index 377c070ef..42f99c8f4 100644 --- a/src/Scroller/Scroller.js +++ b/src/Scroller/Scroller.js @@ -20,7 +20,7 @@ var /** * An [object]{@glossary Object} representing the scroll boundaries. * -* @typedef {Object} enyo.Scroller~BoundaryObject +* @typedef {Object} module:enyo/Scroller~Scroller~BoundaryObject * @property {Number} left - The left scroll position. * @property {Number} top - The top scroll position. * @property {Number} maxLeft - Maximum value for the left scroll position (minimum is always 0). @@ -41,7 +41,7 @@ var /** * An [object]{@glossary Object} representing the overscroll boundaries. * -* @typedef {Object} enyo.Scroller~OverscrollBoundaryObject +* @typedef {Object} module:enyo/Scroller~Scroller~OverscrollBoundaryObject * @property {Number} overleft - The left overscroll position. * @property {Number} overtop - The top overscroll position. */ @@ -50,8 +50,8 @@ var * The extended {@glossary event} [object]{@glossary Object} that is provided * when a scroll event is fired. * -* @typedef {Object} enyo.Scroller~ScrollEvent -* @property {module:enyo/Scroller~BoundaryObject} bounds Current values of scroller bounds. +* @typedef {Object} module:enyo/Scroller~Scroller~ScrollEvent +* @property {module:enyo/Scroller~Scroller~BoundaryObject} bounds - Current values of scroller bounds. */ /** @@ -61,7 +61,7 @@ var * @type {Object} * @property {Object} sender - The [component]{@link module:enyo/Component~Component} that most recently * propagated the {@glossary event}. -* @property {module:enyo/Scroller~ScrollEvent} event - An [object]{@glossary Object} containing +* @property {module:enyo/Scroller~Scroller~ScrollEvent} event - An [object]{@glossary Object} containing * event information. * @public */ @@ -95,11 +95,11 @@ var * applications. * * In some mobile environments, a default scrolling solution is not implemented for -* DOM elements. In such cases, `enyo.Scroller` implements a touch-based scrolling +* DOM elements. In such cases, `enyo/Scroller` implements a touch-based scrolling * solution, which may be opted into either globally (by setting * [touchScrolling]{@link module:enyo/Scroller~Scroller#touchScrolling} to `true`) or on a * per-instance basis (by specifying a [strategyKind]{@link module:enyo/Scroller~Scroller#strategyKind} -* of `"TouchScrollStrategy"`). +* of `'TouchScrollStrategy'`). * * For more information, see the documentation on * [Scrollers]{@linkplain $dev-guide/building-apps/layout/scrollers.html} in the @@ -166,7 +166,7 @@ var Scroller = module.exports = kind( * * @type {Number} * @default null - * @memberof enyo.Scroller.prototype + * @memberof enyo/Scroller~Scroller.prototype * @public */ maxHeight: null, @@ -238,7 +238,7 @@ var Scroller = module.exports = kind( * @name touchScrolling * @type {Boolean} * @default undefined - * @memberof enyo.Scroller.prototype + * @memberof enyo/Scroller~Scroller.prototype * @public */ @@ -273,7 +273,7 @@ var Scroller = module.exports = kind( preventScrollPropagation: true, /** - * Needed to allow global mods to `enyo.Scroller.touchScrolling`. + * Needed to allow global mods to `enyo/Scroller.touchScrolling`. * * @private */ @@ -562,7 +562,7 @@ var Scroller = module.exports = kind( * You should generally not need to call this from application code, as the * scroller usually remeasures automatically whenever needed. This method * exists primarily to support an internal use case for - * [enyo.DataList]{@link module:enyo/DataList~DataList}. + * [enyo/DataList]{@link module:enyo/DataList~DataList}. * * @public */ From 9131c525f664f2ee4dd982155d8bf6272de012ad Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Mon, 28 Sep 2015 17:31:58 -0700 Subject: [PATCH 104/195] ENYO-2592: Update documentation. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/LightPanels/LightPanels.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index e6f5e67d5..10e96dbb4 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -53,6 +53,11 @@ var Orientation = { * immediately after each panel is created. * @property {Number} [targetIndex] - The index of the panel to display, otherwise the last panel * created will be displayed. +* @property {Boolean} [purge] - If `true`, removes and clears the existing set of panels before +* pushing new panels. +* @property {Boolean} [force] - If `true`, forces an index change even if we are targetting an index +* that is the same as the current index. This can be useful in cases where we are purging panels +* and want to properly setup the positioning of the newly pushed panels. */ /** @@ -795,7 +800,7 @@ module.exports = kind( }, /** - * Destroys all panels. These will be queued for destruction after the next panel has loaded. + * Destroys all panels. * * @private */ From 611cc6125fda26c9b7b20c9dc677f1e3af3dcd49 Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Tue, 29 Sep 2015 14:03:19 -0700 Subject: [PATCH 105/195] JSDoc cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/LightPanels/LightPanels.js | 6 ++++-- src/TaskManagerSupport.js | 18 +++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index 83afee5c6..c8a71fdef 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -46,7 +46,7 @@ var Orientation = { /** * The configurable options used by {@link module:enyo/LightPanels~LightPanels} when pushing panels. * -* @typedef {Object} enyo.LightPanels~PushPanelOptions +* @typedef {Object} enyo/LightPanels~LightPanels~PushPanelOptions * @property {Boolean} [direct] - If `true`, the transition to the panel whose index we are * changing to will not be animated. * @property {Boolean} [forcePostTransition] - If `true`, forces post-transition work to be run @@ -61,11 +61,13 @@ var Orientation = { */ /** -* A light-weight panels implementation that has basic support for CSS transitions between child +* A lightweight panels implementation that has basic support for CSS transitions between child * components. * * @class LightPanels * @extends module:enyo/Control~Control +* @mixes module:enyo/TaskManagerSupport~TaskManagerSupport +* @mixes module:enyo/ViewPreloadSupport~ViewPreloadSupport * @ui * @public */ diff --git a/src/TaskManagerSupport.js b/src/TaskManagerSupport.js index ef1e2e55f..f7c8516db 100644 --- a/src/TaskManagerSupport.js +++ b/src/TaskManagerSupport.js @@ -38,8 +38,8 @@ var TaskManagerSupport = { /** * If `true`, we are being managed in a management queue, usually the queue for - * {@link module:enyo/BackgroundTaskManager~BackgroundTaskManager}, otherwise we have not yet been added to a management - * queue. + * {@link module:enyo/BackgroundTaskManager}; otherwise, we have not yet been + * added to a management queue. * * @type {Boolean} * @default false @@ -51,14 +51,14 @@ var TaskManagerSupport = { * The default priority for added tasks. * * @type {Number} - * @default enyo.Priorities.SOMETIME + * @default {@link module:enyo/PriorityQueue~PriorityQueue#Priorities.SOMETIME} * @public */ defaultPriority: Priorities.SOMETIME, /** * The name of this manager, which can be used to easily retrieve this specific manager from the - * {@link enyo.BackgroundTaskManager}. + * {@link module:enyo/BackgroundTaskManager}. * * @type {String} * @default '' @@ -108,7 +108,7 @@ var TaskManagerSupport = { /** * Removes the specified task from the queue. * - * @param {Function} nom - The name of the task to be cancelled. + * @param {Function} nom - The name of the task to be canceled. * @public */ removeTask: function (nom) { @@ -164,9 +164,9 @@ var TaskManagerSupport = { }, /** - * Determines whether or not this {@link enyo.TaskManager} has tasks or not. + * Determines whether or not this task manager has tasks. * - * @returns {Boolean} If there exist tasks and we are not paused, returns `true`; `false` + * @returns {Boolean} `true` if tasks exist and we are not paused; otherwise, `false` * otherwise. * @public */ @@ -175,8 +175,8 @@ var TaskManagerSupport = { }, /** - * Execute the next task in the queue. If the task had previously been paused, it will be - * resumed. + * Executes the next task in the queue. If the task had previously been paused, + * it will be resumed. * * @public */ From 78920cd4e576cf7b5694f1394ff67299fe188633 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Tue, 29 Sep 2015 17:33:34 -0700 Subject: [PATCH 106/195] Update version string to 2.6.0-pre.17 --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index e176aaf94..6eb82594d 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ 'use strict'; exports = module.exports = require('./src/options'); -exports.version = '2.6.0-pre.16'; +exports.version = '2.6.0-pre.17'; From f5a51fb593908de21f82966afcdb0ff2245cc0d2 Mon Sep 17 00:00:00 2001 From: Ryan Duffy Date: Wed, 30 Sep 2015 19:49:05 -0500 Subject: [PATCH 107/195] add kind() method to Kind constructors Issue: ENYO-2619 Enyo-DCO-1.1-Signed-off-by: Ryan Duffy (ryan.duffy@lge.com) --- src/kind.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/kind.js b/src/kind.js index fd56c198c..db0507cb2 100644 --- a/src/kind.js +++ b/src/kind.js @@ -83,6 +83,11 @@ var kind = exports = module.exports = function (props) { utils.forEach(kind.features, function(fn){ fn(ctor, props); }); if (name) kindCtors[name] = ctor; + + ctor.kind = function (props) { + props.kind = ctor; + return kind(props); + }; return ctor; }; From 87bf13fd6704b169cff85a4ed92f9fe6a8b23de1 Mon Sep 17 00:00:00 2001 From: Ryan Duffy Date: Wed, 30 Sep 2015 20:07:20 -0500 Subject: [PATCH 108/195] add back linting for npm test Issue: ENYO-2622 Enyo-DCO-1.1-Signed-off-by: Ryan Duffy (ryan.duffy@lge.com) --- gulpfile.js | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index d9147f2de..7b2f43df2 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -21,11 +21,11 @@ var cache: false }; -gulp.task('default', ['jshint']); +gulp.task('default', ['test']); gulp.task('jshint', lint); gulp.task('build-lib', buildLib); gulp.task('build-tests', buildTests); -gulp.task('test', ['build-lib', 'build-tests'], test); +gulp.task('test', ['jshint', 'build-lib', 'build-tests'], test); function lint () { return gulp diff --git a/package.json b/package.json index a553f2a63..a899b0142 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ } ], "scripts": { - "test": "./node_modules/.bin/gulp test" + "test": "./node_modules/.bin/gulp" }, "repository": { "type": "git", From bf76237cc831f6b1e33728c19424afa5fa83e410 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Thu, 1 Oct 2015 11:37:11 -0700 Subject: [PATCH 109/195] Update version string to 2.6.0-pre.17.1 --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 6eb82594d..2e2d34231 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ 'use strict'; exports = module.exports = require('./src/options'); -exports.version = '2.6.0-pre.17'; +exports.version = '2.6.0-pre.17.1'; From 79cb796ad7c9d42f8c7d00cee75afa16dad197c1 Mon Sep 17 00:00:00 2001 From: Ryan Duffy Date: Thu, 1 Oct 2015 14:40:04 -0500 Subject: [PATCH 110/195] move constructor.kind() into statics block Issue: ENYO-2619 Enyo-DCO-1.1-Signed-off-by: Ryan Duffy (ryan.duffy@lge.com) --- src/kind.js | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/kind.js b/src/kind.js index db0507cb2..98dc8f50e 100644 --- a/src/kind.js +++ b/src/kind.js @@ -83,11 +83,6 @@ var kind = exports = module.exports = function (props) { utils.forEach(kind.features, function(fn){ fn(ctor, props); }); if (name) kindCtors[name] = ctor; - - ctor.kind = function (props) { - props.kind = ctor; - return kind(props); - }; return ctor; }; @@ -313,6 +308,9 @@ kind.features.push(function(ctor, props) { if (!ctor.extend) { ctor.extend = kind.statics.extend; } + if (!ctor.kind) { + ctor.kind = kind.statics.kind; + } // move props statics to constructor if (props.statics) { utils.mixin(ctor, props.statics); @@ -392,6 +390,24 @@ kind.statics = { } return target || ctor; + }, + + /** + * Creates a new sub-[kind]{@glossary kind} of the current kind. + * + * @param {Object} props A [hash]{@glossary Object} of properties used to define and create + * the [kind]{@glossary kind} + * @return {Function} Constructor of the new kind + * @memberof enyo.kind + * @public + */ + kind: function (props) { + if (props.kind && props.kind !== this) { + logger.warn('Creating a different kind from a constructor\'s kind() method is not ' + + 'supported and will be replaced with the constructor.'); + } + props.kind = this; + return kind(props); } }; From 393c8224a56d07a5b981edf11558b8ef10eb6566 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Fri, 2 Oct 2015 14:05:46 -0700 Subject: [PATCH 111/195] ENYO-2625: Prevent overriding of panel content directionality. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/LightPanels/LightPanels.less | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/LightPanels/LightPanels.less b/src/LightPanels/LightPanels.less index 669b80087..406226e9b 100644 --- a/src/LightPanels/LightPanels.less +++ b/src/LightPanels/LightPanels.less @@ -96,5 +96,9 @@ > .offscreen { display: none; } + + .enyo-locale-right-to-left & > * { + direction: rtl; + } } } From 8c04840c1fcea0bbd2e1c3a315ab7d3c00961347 Mon Sep 17 00:00:00 2001 From: Roy Sutton Date: Fri, 2 Oct 2015 16:38:08 -0700 Subject: [PATCH 112/195] Prevent video from restarting when skipping forward to end Enyo-DCO-1.1-Signed-off-by: Roy Sutton --- src/Video.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Video.js b/src/Video.js index 892ff660f..428ea0f8c 100644 --- a/src/Video.js +++ b/src/Video.js @@ -651,13 +651,14 @@ module.exports = kind( oldPlaybackRateNumber = this.calcNumberValueOfPlaybackRate(this.playbackRate); this.setPlaybackRate(1); - node.currentTime += parseInt(this.jumpSec, 10); - this._prevCommand = 'jumpForward'; if(oldPlaybackRateNumber < 0) { - this.node.play(); + this.node.play(); // Play before skip so video won't restart } + node.currentTime += parseInt(this.jumpSec, 10); + this._prevCommand = 'jumpForward'; + this.doJumpForward(utils.mixin(this.createEventData(), {jumpSize: this.jumpSec})); }, From f829526ebb1dff06c2861523b6d580f709fa29f3 Mon Sep 17 00:00:00 2001 From: Cole Davis Date: Mon, 5 Oct 2015 13:02:41 -0700 Subject: [PATCH 113/195] ensure that we don't munge separate ideas together, default task should run linter and unit tests, but each can still be executed separately as they are separate by concern Enyo-DCO-1.1-Signed-Off-By: Cole Davis (cole.davis@lge.com) --- gulpfile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 7b2f43df2..1912b83ed 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -21,11 +21,11 @@ var cache: false }; -gulp.task('default', ['test']); +gulp.task('default', ['jshint', 'test']); gulp.task('jshint', lint); gulp.task('build-lib', buildLib); gulp.task('build-tests', buildTests); -gulp.task('test', ['jshint', 'build-lib', 'build-tests'], test); +gulp.task('test', ['build-lib', 'build-tests'], test); function lint () { return gulp From 71959d81b2ef5d33955388dd2f16479876a4a15e Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Mon, 5 Oct 2015 15:40:18 -0700 Subject: [PATCH 114/195] JSDoc cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/ContentAreaSupport.js | 56 ++++++++++++++++++---------------- src/LightPanels/LightPanels.js | 4 +-- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/ContentAreaSupport.js b/src/ContentAreaSupport.js index e159dc5cf..8ffefed56 100644 --- a/src/ContentAreaSupport.js +++ b/src/ContentAreaSupport.js @@ -1,6 +1,6 @@ /** -* Provides {@link module:enyo/ContentAreaSupport~ContentAreaSupport} which adds ability to define -* property-bound content areas. +* Contains declaration for {@link module:enyo/ContentAreaSupport~ContentAreaSupport} +* mixin, which adds ability to define property-bound content areas. * * @module enyo/ContentAreaSupport */ @@ -12,7 +12,7 @@ var /** * Populates a content area either initially or in response to a change to either the content or -* components property. `owner` is only defined for the initial call +* components property. `owner` is only defined for the initial call. * * @private */ @@ -39,7 +39,7 @@ function updateContentArea (control, targetName, contentProperty, componentsProp } /** -* Defines the declaratively-configured content areas +* Defines the declaratively-configured content areas. * * @private */ @@ -61,34 +61,36 @@ function initContentAreas (control) { * * ```javascript * var -* ContentAreaSupport = require('enyo/ContentAreaSupport'); +* kind = require('enyo/kind'), +* ContentAreaSupport = require('enyo/ContentAreaSupport'); * -* // Defines a new kind with a single content area rendering the value of `user` into the control -* // named `name`. `user` can contain either a string or a components declaration array. +* // Defines a new kind with a single content area rendering the value of `user` +* // into the control named `name`. `user` can contain either a string or a +* // components declaration array. * var Hello = kind({ -* kind: Control, -* mixins: [ContentAreaSupport], -* user: 'The Doctor', +* kind: Control, +* mixins: [ContentAreaSupport], +* user: 'The Doctor', * -* contentAreas: [ -* {target: 'name', content: 'user'} -* ], +* contentAreas: [ +* {target: 'name', content: 'user'} +* ], * -* components: [ -* {name: 'greeting', content: 'Hello, my name is'}, -* {name: 'name'}, -* ] +* components: [ +* {name: 'greeting', content: 'Hello, my name is'}, +* {name: 'name'}, +* ] * }); * * // Uses the new kind with customized `user` value * var SayHello = kind({ -* kind: Control, -* components: [ -* {kind: Hello, user: [ -* {kind: Img, src: 'who.png'}, -* {content: 'David Tennant'} -* ]} -* ] +* kind: Control, +* components: [ +* {kind: Hello, user: [ +* {kind: Img, src: 'who.png'}, +* {content: 'David Tennant'} +* ]} +* ] * }); * ``` * @@ -105,10 +107,10 @@ var ContentAreaSupport = { * *Note:* If updating a property at run-time to a new components block, the owner must be * explicitly set or the new components will be owned by `target`. * - * @param {String|enyo.Control} target Control name or instance that will be populated with the + * @param {String|module:enyo/Control~Control} target - Control name or instance that will be populated with the * content. - * @param {String} contentProperty Name of property from which the content will be sourced. - * @param {String} [componentsProperty] Name of property from which the components will be + * @param {String} contentProperty - Name of property from which the content will be sourced. + * @param {String} [componentsProperty] - Name of property from which the components will be * sourced. May be omitted if `contentProperty` should support either string content or a * component declaration array. * diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index c8a71fdef..7c0c1c5f2 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -155,7 +155,7 @@ module.exports = kind( /** * The timing function to be applied to the transition animation between panels. Please refer - * to https://developer.mozilla.org/en-US/docs/Web/CSS/transition-timing-function. + * to {@linkplain https://developer.mozilla.org/en-US/docs/Web/CSS/transition-timing-function}. * * @type {String} * @default 'ease-out' @@ -184,7 +184,7 @@ module.exports = kind( direction: Direction.FORWARDS, /** - * Whether or not to reverse the panel animation when the directionality changes (i.e. rtl). Note + * Whether or not to reverse the panel animation when the directionality changes (i.e., rtl). Note * that the animation is only reversed if {@link module:enyo/LightPanels~LightPanels} is in a * [horizontal orientation]{@link module:enyo/LightPanels~LightPanels#Orientation.HORIZONTAL}. * From f7f7c091c9d3d14b8f204800ea6a22b79260c374 Mon Sep 17 00:00:00 2001 From: Anish_Ramesan Date: Tue, 6 Oct 2015 16:34:10 +0530 Subject: [PATCH 115/195] Moved animationsupport inside src --- {lib => src}/AnimationSupport/AnimationInterfaceSupport.js | 0 {lib => src}/AnimationSupport/AnimationSupport.js | 0 {lib => src}/AnimationSupport/Core.js | 0 {lib => src}/AnimationSupport/Fadeable.js | 0 {lib => src}/AnimationSupport/Flippable.js | 0 {lib => src}/AnimationSupport/Frame.js | 0 {lib => src}/AnimationSupport/HierarchicalMixin.js | 0 {lib => src}/AnimationSupport/KeyFrame.js | 0 {lib => src}/AnimationSupport/Matrix.js | 0 {lib => src}/AnimationSupport/Slideable.js | 0 {lib => src}/AnimationSupport/Tween.js | 0 {lib => src}/AnimationSupport/Vector.js | 0 {lib => src}/AnimationSupport/package.json | 0 13 files changed, 0 insertions(+), 0 deletions(-) rename {lib => src}/AnimationSupport/AnimationInterfaceSupport.js (100%) rename {lib => src}/AnimationSupport/AnimationSupport.js (100%) rename {lib => src}/AnimationSupport/Core.js (100%) rename {lib => src}/AnimationSupport/Fadeable.js (100%) rename {lib => src}/AnimationSupport/Flippable.js (100%) rename {lib => src}/AnimationSupport/Frame.js (100%) rename {lib => src}/AnimationSupport/HierarchicalMixin.js (100%) rename {lib => src}/AnimationSupport/KeyFrame.js (100%) rename {lib => src}/AnimationSupport/Matrix.js (100%) rename {lib => src}/AnimationSupport/Slideable.js (100%) rename {lib => src}/AnimationSupport/Tween.js (100%) rename {lib => src}/AnimationSupport/Vector.js (100%) rename {lib => src}/AnimationSupport/package.json (100%) diff --git a/lib/AnimationSupport/AnimationInterfaceSupport.js b/src/AnimationSupport/AnimationInterfaceSupport.js similarity index 100% rename from lib/AnimationSupport/AnimationInterfaceSupport.js rename to src/AnimationSupport/AnimationInterfaceSupport.js diff --git a/lib/AnimationSupport/AnimationSupport.js b/src/AnimationSupport/AnimationSupport.js similarity index 100% rename from lib/AnimationSupport/AnimationSupport.js rename to src/AnimationSupport/AnimationSupport.js diff --git a/lib/AnimationSupport/Core.js b/src/AnimationSupport/Core.js similarity index 100% rename from lib/AnimationSupport/Core.js rename to src/AnimationSupport/Core.js diff --git a/lib/AnimationSupport/Fadeable.js b/src/AnimationSupport/Fadeable.js similarity index 100% rename from lib/AnimationSupport/Fadeable.js rename to src/AnimationSupport/Fadeable.js diff --git a/lib/AnimationSupport/Flippable.js b/src/AnimationSupport/Flippable.js similarity index 100% rename from lib/AnimationSupport/Flippable.js rename to src/AnimationSupport/Flippable.js diff --git a/lib/AnimationSupport/Frame.js b/src/AnimationSupport/Frame.js similarity index 100% rename from lib/AnimationSupport/Frame.js rename to src/AnimationSupport/Frame.js diff --git a/lib/AnimationSupport/HierarchicalMixin.js b/src/AnimationSupport/HierarchicalMixin.js similarity index 100% rename from lib/AnimationSupport/HierarchicalMixin.js rename to src/AnimationSupport/HierarchicalMixin.js diff --git a/lib/AnimationSupport/KeyFrame.js b/src/AnimationSupport/KeyFrame.js similarity index 100% rename from lib/AnimationSupport/KeyFrame.js rename to src/AnimationSupport/KeyFrame.js diff --git a/lib/AnimationSupport/Matrix.js b/src/AnimationSupport/Matrix.js similarity index 100% rename from lib/AnimationSupport/Matrix.js rename to src/AnimationSupport/Matrix.js diff --git a/lib/AnimationSupport/Slideable.js b/src/AnimationSupport/Slideable.js similarity index 100% rename from lib/AnimationSupport/Slideable.js rename to src/AnimationSupport/Slideable.js diff --git a/lib/AnimationSupport/Tween.js b/src/AnimationSupport/Tween.js similarity index 100% rename from lib/AnimationSupport/Tween.js rename to src/AnimationSupport/Tween.js diff --git a/lib/AnimationSupport/Vector.js b/src/AnimationSupport/Vector.js similarity index 100% rename from lib/AnimationSupport/Vector.js rename to src/AnimationSupport/Vector.js diff --git a/lib/AnimationSupport/package.json b/src/AnimationSupport/package.json similarity index 100% rename from lib/AnimationSupport/package.json rename to src/AnimationSupport/package.json From 37b81a69844807ba5ed16f81b7797ae16dadf8a9 Mon Sep 17 00:00:00 2001 From: Gray Norton Date: Tue, 6 Oct 2015 14:45:00 -0700 Subject: [PATCH 116/195] ENYO-2644: Override scrollToControl() in enyo/NewDataList enyo/Scrollable provides a `scrollToControl()` method that has a generalized implementation for calculating a Control's position and scrolling to it. In the case of enyo/NewDataList, it's possible to provide an alternative implementation that is more performant and more accurate (more performant because it doesn't need to query the DOM for bounds, more accurate because the generalized implementation is influenced by current scroll position and may give slightly different values when animating vs. not). We tweak enyo/Scrollable to defer to an alternative method when one is provided, and we provide an alternative method in enyo/NewDataList. Enyo-DCO-1.1-Signed-Off-By: Gray Norton (gray.norton@lge.com) --- src/NewDataList.js | 20 ++++++++++++++++++++ src/Scrollable/Scrollable.js | 28 +++++++++++++++++++++------- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/NewDataList.js b/src/NewDataList.js index 183be5b62..77d797ddc 100644 --- a/src/NewDataList.js +++ b/src/NewDataList.js @@ -72,6 +72,26 @@ module.exports = kind({ this.scrollTo(b.x, b.y, opts); }, + /** + * This method is considered private for NewDataList, although + * the corresponding method defined in the Scrollable mixin is + * public in the more general case. For NewDataList, application + * code should generally use `scrollToItem()` instead, because it works + * regardless of whether the target item is currently "virtualized," + * whereas `scrollToControl()` works only on non-virtual items. + * + * NewDataList overrides this method because it can provide a + * more accurate, more performant implementation than the general + * one provided by enyo/Scrollable. + * + * @private + */ + scrollToControl: function (control, opts) { + if (typeof control.index === 'number' && control.parent === this.$.container) { + this.scrollToItem(control.index, opts); + } + }, + /** * @private */ diff --git a/src/Scrollable/Scrollable.js b/src/Scrollable/Scrollable.js index 4df032273..3775889ef 100644 --- a/src/Scrollable/Scrollable.js +++ b/src/Scrollable/Scrollable.js @@ -327,13 +327,27 @@ module.exports = { * * @public */ - scrollToControl: function (control, opts) { - var n = control.hasNode(); - - if (n) { - this.scrollToNode(n, opts); - } - }, + scrollToControl: kind.inherit(function (sup) { + return function (control, opts) { + var n; + + // Default implementation -- in case the Control + // applying the Scrollable mixin does not supply + // its own + if (sup === utils.nop) { + n = control.hasNode(); + + if (n) { + this.scrollToNode(n, opts); + } + } + // If the Control does provide an alternative + // implementation, we use it + else { + sup.apply(this, arguments); + } + }; + }), /** * TODO: Document. Based on CSSOM View spec () From e7457a30d273c55a567408cb6729ac4f4116a852 Mon Sep 17 00:00:00 2001 From: Anish_Ramesan Date: Wed, 7 Oct 2015 21:36:53 +0530 Subject: [PATCH 117/195] keyframe animation to retain its initial state --- src/AnimationSupport/KeyFrame.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/AnimationSupport/KeyFrame.js b/src/AnimationSupport/KeyFrame.js index bfd87caa8..660d91198 100644 --- a/src/AnimationSupport/KeyFrame.js +++ b/src/AnimationSupport/KeyFrame.js @@ -48,6 +48,7 @@ var keyFrame = module.exports = kind.singleton({ charc.keyTime.push(prop); charc.keyProps.push(keyframe[prop]); } + charc.currentIndex = 0; charc.keyframeCallback = cb; charc.initialTime = utils.perfNow(); charc.totalDuration = proto.duration; From 78fcd85fdaa538f67343552d1b21a906881e9b04 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Wed, 7 Oct 2015 17:26:34 -0700 Subject: [PATCH 118/195] ENYO-2667: Account for queue being modified during iteration. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/History.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/History.js b/src/History.js index 189a24435..30a75c90b 100644 --- a/src/History.js +++ b/src/History.js @@ -235,14 +235,13 @@ var EnyoHistory = module.exports = kind.singleton( * @private */ processQueue: function () { - var i, l = _queue.length, next, - i2, entries; + var next, i, entries; this.silencePushEntries(); - for (i = 0; i < l; i++) { + while (_queue.length) { next = _queue.shift(); - + if (next.type === 'push') { this.pushEntry(next.entry, next.silenced); } else { @@ -251,9 +250,9 @@ var EnyoHistory = module.exports = kind.singleton( // if a 'pop' was requested if (next.type == 'pop') { // iterate the requested number of history entries - for (i2 = entries.length - 1; i2 >= 0; --i2) { + for (i = entries.length - 1; i >= 0; --i) { // and call each handler if it exists - this.processPopEntry(entries[i2]); + this.processPopEntry(entries[i]); } } // otherwise we just drop the entries and do nothing From e180e9db5276220efd2d9cc34bdf46c50c64ef26 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Thu, 8 Oct 2015 15:10:53 -0700 Subject: [PATCH 119/195] ENYO-2673: Prevent bounds of zero when restoring a cached view. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/LightPanels/LightPanels.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LightPanels/LightPanels.less b/src/LightPanels/LightPanels.less index 406226e9b..f86242e8a 100644 --- a/src/LightPanels/LightPanels.less +++ b/src/LightPanels/LightPanels.less @@ -94,7 +94,7 @@ } > .offscreen { - display: none; + visibility: hidden; } .enyo-locale-right-to-left & > * { From 1798bae8d87b4305abdcee5474f886eb1d21facb Mon Sep 17 00:00:00 2001 From: "rajkumar.selvam" Date: Fri, 9 Oct 2015 22:29:45 +0530 Subject: [PATCH 120/195] Easing support added to Animation Module using Cubic Bezier Curves Enyo-DCO-1.1-Signed-off-by:Rajkumar Selvam --- src/AnimationSupport/AnimationSupport.js | 13 ++- src/AnimationSupport/Frame.js | 2 +- src/AnimationSupport/KeyFrame.js | 2 +- src/AnimationSupport/Matrix.js | 62 +++++++++++ src/AnimationSupport/Tween.js | 132 ++++++++++++++++++++++- 5 files changed, 203 insertions(+), 8 deletions(-) diff --git a/src/AnimationSupport/AnimationSupport.js b/src/AnimationSupport/AnimationSupport.js index 5d467d175..de4eefdf6 100644 --- a/src/AnimationSupport/AnimationSupport.js +++ b/src/AnimationSupport/AnimationSupport.js @@ -2,6 +2,7 @@ require('enyo'); var kind = require('../kind'), + core = require('./Core'), activator = require('./KeyFrame'), frame = require('./Frame'), utils = require('../utils'); @@ -139,7 +140,17 @@ var AnimationSupport = { this.initiate(); frame.accelerate(this.hasNode(), this.matrix); }; - }) + }), + + /** + * @private + */ + destroy: kind.inherit(function(sup) { + return function() { + core.remove(this); + sup.apply(this, arguments); + }; + }), }; module.exports = AnimationSupport; diff --git a/src/AnimationSupport/Frame.js b/src/AnimationSupport/Frame.js index 99ef36a1a..88f525824 100644 --- a/src/AnimationSupport/Frame.js +++ b/src/AnimationSupport/Frame.js @@ -339,6 +339,6 @@ var frame = module.exports = { sP[k] = dP[k]; eP[k] = tP[k] || dP[k]; } - return {_startAnim: sP, _endAnim: eP, _transform: dP}; + return {_startAnim: sP, _endAnim: eP, _transform: dP, currentState: dP}; } }; \ No newline at end of file diff --git a/src/AnimationSupport/KeyFrame.js b/src/AnimationSupport/KeyFrame.js index 660d91198..32c37265f 100644 --- a/src/AnimationSupport/KeyFrame.js +++ b/src/AnimationSupport/KeyFrame.js @@ -44,11 +44,11 @@ var keyFrame = module.exports = kind.singleton({ charc.keyProps = []; charc.keyTime = []; + charc.currentIndex = 0; for (prop in keyframe) { charc.keyTime.push(prop); charc.keyProps.push(keyframe[prop]); } - charc.currentIndex = 0; charc.keyframeCallback = cb; charc.initialTime = utils.perfNow(); charc.totalDuration = proto.duration; diff --git a/src/AnimationSupport/Matrix.js b/src/AnimationSupport/Matrix.js index c9186a01c..9bc65a236 100644 --- a/src/AnimationSupport/Matrix.js +++ b/src/AnimationSupport/Matrix.js @@ -145,6 +145,68 @@ module.exports = { ]; }, + /** + * @public + */ + multiplyN: function(m1, m2) { + var j, k, i, sum, + m = [], + l1 = m1.length, + l2 = m2.length; + + for (j = 0; j < l2; j++) { + m[j] = []; + for (k = 0; k < m1[0].length; k++) { + sum = 0; + for (i = 0; i < l1; i++) { + sum += m1[i][k] * m2[j][k]; + } + m[j].push(sum); + } + } + return m; + }, + + /** + * @public + */ + inverseN: function(matrix, n) { + var i, j, k, r, + result = [], + row = []; + for (i = 0; i < n; i++) { + for (j = n; j < 2 * n; j++) { + if (i == (j - n)) matrix[i][j] = 1.0; + else matrix[i][j] = 0.0; + } + } + for (i = 0; i < n; i++) { + for (j = 0; j < n; j++) { + if (i != j) { + r = matrix[j][i] / matrix[i][i]; + for (k = 0; k < 2 * n; k++) { + matrix[j][k] -= r * matrix[i][k]; + } + } + } + } + for (i = 0; i < n; i++) { + for (j = 0; j < 2 * n; j++) { + matrix[i][j] /= matrix[i][i]; + } + } + + for (i = 0; i < n; i++) { + row = []; + for (k = 0, j = n; j < 2 * n; j++, k++) { + row.push(matrix[i][j]); + } + result.push(row); + } + + return result; + }, + /** * @public */ diff --git a/src/AnimationSupport/Tween.js b/src/AnimationSupport/Tween.js index 978895506..ef65c5315 100644 --- a/src/AnimationSupport/Tween.js +++ b/src/AnimationSupport/Tween.js @@ -45,17 +45,26 @@ module.exports = { * @private */ step: function(charc, t) { - var k, c; + var k, c, pts; node = charc.node; newState = charc._endAnim; + props = charc.getAnimation(), oldState = charc._startAnim; charc.currentState = charc.currentState || {}; - for (k in newState) { + for (k in props) { cState = frame.copy(charc.currentState[k] || []); - c = k == 'rotate'? this.slerp : this.lerp; - cState = t ? c(oldState[k], newState[k], this.ease(t), cState) : newState[k]; + // console.log(charc.name, charc.ease); + // console.log(charc.ease, (typeof charc.ease !== 'function'), (k !== 'rotate')); + if (charc.ease && (typeof charc.ease !== 'function') && (k !== 'rotate')) { + pts = this.calculateEase(charc.ease, frame.copy(oldState[k]), frame.copy(newState[k])); + cState = this.getBezier(t, pts, cState); + } else { + c = k == 'rotate' ? this.slerp : this.lerp; + cState = t ? c(oldState[k], newState[k], ((typeof charc.ease === 'function') ? charc.ease : this.ease)(t), cState) : newState[k]; + } + if (!frame.isTransform(k)) { frame.setProperty(node, k, cState); } @@ -80,7 +89,49 @@ module.exports = { ease: function (t) { return t; }, - + + calculateEase: function(data, startPoint, endPoint) { + var controlPoints = [startPoint]; + var timeValues = []; + var animValues = []; + var thirdMatrix = []; + + for (var key in data) { + var timeVal = parseFloat(key)/100; + var animVal = parseFloat(data[key])/100; + timeValues.push(timeVal); + animValues.push(animVal); + thirdMatrix.push(animVal - (timeVal * timeVal * timeVal)); + } + + var t1 = timeValues[0]; + var t2 = timeValues[1]; + var d1 = animValues[0]; + var d2 = animValues[1]; + + // Bezier values + var A = 3 * t1 * [(1 - t1) * (1 - t1)]; + var B = (3 * (t1 * t1)) * (1 - t1); + var C = 3 * t2 * [(1 - t2) * (1 - t2)]; + var D = (3 * (t2 * t2)) * (1 - t2); + + var E = thirdMatrix[0]; + var F = thirdMatrix[1]; + var det = 1 / [(A * D) - (B * C)]; + var C1 = ((E * D) - (B * F)) * det; + var C2 = ((A * F) - (C * E)) * det; + controlPoints.push([C1,C1,C1]); + controlPoints.push([C2,C2,C2]); + controlPoints.push(endPoint); + + // console.log(data); + // console.log("the third matrix values are " + thirdMatrix); + // console.log("E is " + E + "the F is " + F); + + console.log("CP", controlPoints); + return controlPoints; + }, + complete: function (charc) { charc.animating = false; charc._startAnim = undefined; @@ -115,5 +166,76 @@ module.exports = { qR[i] = a + b; } return qR; + }, + + /** + * @public + * @params t: time, points: knot and control points, vR: resulting point + */ + getBezier: function (t, points, vR) { + if (!vR) vR = []; + + var i, j, + c = points.length, + l = points[0].length, + lastIndex = (c - 1), + endPoint = points[lastIndex], + values = this.getBezierValues(t, lastIndex); + + for (i = 0; i < l; i++) { + vR[i] = 0; + for (j = 0; j < c; j++) { + if ((j > 0) && (j < lastIndex)) { + vR[i] = vR[i] + (points[j][i] * endPoint[i] * values[j]); + } else { + vR[i] = vR[i] + (points[j][i] * values[j]); + } + } + } + + console.log("vR", vR); + return vR; + }, + + /** + * @private + * @params t: time, n: order + */ + getCoeff: function (n, k) { + + // Credits + // https://math.stackexchange.com/questions/202554/how-do-i-compute-binomial-coefficients-efficiently#answer-927064 + + if (k > n) + return void 0; + if (k === 0) + return 1; + if (k === n) + return 1; + if (k > n / 2) + return this.getCoeff(n, n - k); + + return n * this.getCoeff(n - 1, k - 1) / k; + }, + + /** + * @public + * @params t: time, n: order + */ + getBezierValues: function (t, n) { + var c, + values = [], + x = (1 - t), + y = t; + + // + // Binomial theorem to expand (x+y)^n + // + for (var k = 0; k <= n; k++) { + c = this.getCoeff(n, k) * Math.pow(x, (n - k)) * Math.pow(y, k); + values.push(c); + } + + return values; } }; \ No newline at end of file From 681505b0712dc130aa780268216a2eec5ed9d845 Mon Sep 17 00:00:00 2001 From: "rajkumar.selvam" Date: Fri, 9 Oct 2015 23:30:35 +0530 Subject: [PATCH 121/195] Issue on last step in easing animation is fixed Enyo-DCO-1.1-Signed-off-by:Rajkumar Selvam --- src/AnimationSupport/Tween.js | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/AnimationSupport/Tween.js b/src/AnimationSupport/Tween.js index ef65c5315..f76466d4b 100644 --- a/src/AnimationSupport/Tween.js +++ b/src/AnimationSupport/Tween.js @@ -33,11 +33,11 @@ module.exports = { since = ts - charc._startTime; if (since < 0) return; - if (since < dur) { + if (since <= dur) { t = since / dur; this.step(charc, t); } else { - this.step(charc); + this.step(charc, 1); } }, @@ -55,8 +55,6 @@ module.exports = { for (k in props) { cState = frame.copy(charc.currentState[k] || []); - // console.log(charc.name, charc.ease); - // console.log(charc.ease, (typeof charc.ease !== 'function'), (k !== 'rotate')); if (charc.ease && (typeof charc.ease !== 'function') && (k !== 'rotate')) { pts = this.calculateEase(charc.ease, frame.copy(oldState[k]), frame.copy(newState[k])); cState = this.getBezier(t, pts, cState); @@ -123,12 +121,8 @@ module.exports = { controlPoints.push([C1,C1,C1]); controlPoints.push([C2,C2,C2]); controlPoints.push(endPoint); - - // console.log(data); - // console.log("the third matrix values are " + thirdMatrix); - // console.log("E is " + E + "the F is " + F); - console.log("CP", controlPoints); + //console.log("CP", controlPoints); return controlPoints; }, @@ -185,7 +179,7 @@ module.exports = { for (i = 0; i < l; i++) { vR[i] = 0; for (j = 0; j < c; j++) { - if ((j > 0) && (j < lastIndex)) { + if ((j > 0) && (j < (c - 1))) { vR[i] = vR[i] + (points[j][i] * endPoint[i] * values[j]); } else { vR[i] = vR[i] + (points[j][i] * values[j]); @@ -193,7 +187,7 @@ module.exports = { } } - console.log("vR", vR); + // console.log("vR", vR); return vR; }, From b93e15b1239346fe92fd914e847c487e9c32bb0a Mon Sep 17 00:00:00 2001 From: Blake Stephens Date: Fri, 9 Oct 2015 11:26:29 -0700 Subject: [PATCH 122/195] ENYO-2674: Exit early if addClass receives no classname Enyo-DCO-1.1-Signed-off-by: Blake Stephens --- src/Control/Control.js | 2 +- src/dom.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Control/Control.js b/src/Control/Control.js index 8e98f9e14..5324d610f 100644 --- a/src/Control/Control.js +++ b/src/Control/Control.js @@ -531,7 +531,7 @@ var Control = module.exports = kind( // NOTE: Because this method accepts a string and for efficiency does not wish to // parse it to determine if it is actually multiple classes we later pull a trick // to keep it normalized and synchronized with our attributes hash and the node's - if (!this.hasClass(name)) { + if (name && !this.hasClass(name)) { // this is hooked this.set('classes', classes + (classes ? (' ' + name) : name)); diff --git a/src/dom.js b/src/dom.js index bcef59c27..34085da15 100644 --- a/src/dom.js +++ b/src/dom.js @@ -431,7 +431,7 @@ var dom = module.exports = { * @public */ hasClass: function(node, s) { - if (!node || !node.className) { return; } + if (!node || !s || !node.className) { return; } return (' ' + node.className + ' ').indexOf(' ' + s + ' ') >= 0; }, @@ -443,7 +443,7 @@ var dom = module.exports = { * @public */ addClass: function(node, s) { - if (node && !this.hasClass(node, s)) { + if (node && s && !this.hasClass(node, s)) { var ss = node.className; node.className = (ss + (ss ? ' ' : '') + s); } @@ -457,7 +457,7 @@ var dom = module.exports = { * @public */ removeClass: function(node, s) { - if (node && this.hasClass(node, s)) { + if (node && s && this.hasClass(node, s)) { var ss = node.className; node.className = (' ' + ss + ' ').replace(' ' + s + ' ', ' ').slice(1, -1); } From 7b4eaedc01737697173e2abef11b12cef89620bc Mon Sep 17 00:00:00 2001 From: Madala Cholan Satyanarayana Date: Mon, 12 Oct 2015 19:55:33 +0530 Subject: [PATCH 123/195] Included the duration values in the parameters from RAF to be available for the easing functions Enyo-DCO-1.1-Signed-off-by:Madala Satyanarayana --- src/AnimationSupport/Tween.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/AnimationSupport/Tween.js b/src/AnimationSupport/Tween.js index f76466d4b..abdc78cd0 100644 --- a/src/AnimationSupport/Tween.js +++ b/src/AnimationSupport/Tween.js @@ -45,10 +45,11 @@ module.exports = { * @private */ step: function(charc, t) { - var k, c, pts; + var k, c, d, pts; node = charc.node; newState = charc._endAnim; + d = charc._duration; props = charc.getAnimation(), oldState = charc._startAnim; charc.currentState = charc.currentState || {}; @@ -60,7 +61,7 @@ module.exports = { cState = this.getBezier(t, pts, cState); } else { c = k == 'rotate' ? this.slerp : this.lerp; - cState = t ? c(oldState[k], newState[k], ((typeof charc.ease === 'function') ? charc.ease : this.ease)(t), cState) : newState[k]; + cState = t ? c(oldState[k], newState[k], ((typeof charc.ease === 'function') ? charc.ease : this.ease)(t, d), cState) : newState[k]; } if (!frame.isTransform(k)) { From 2b26e18cc9a50819e1bd3253892132970b048361 Mon Sep 17 00:00:00 2001 From: "rajkumar.selvam" Date: Tue, 13 Oct 2015 11:22:39 +0530 Subject: [PATCH 124/195] Animation framework is now capable of handling multiple control points of Bezier Curve for easing animation. Enyo-DCO-1.1-Signed-off-by:Rajkumar Selvam --- src/AnimationSupport/Matrix.js | 30 +++++++------- src/AnimationSupport/Tween.js | 71 ++++++++++++++++------------------ 2 files changed, 50 insertions(+), 51 deletions(-) diff --git a/src/AnimationSupport/Matrix.js b/src/AnimationSupport/Matrix.js index 9bc65a236..fb59144ed 100644 --- a/src/AnimationSupport/Matrix.js +++ b/src/AnimationSupport/Matrix.js @@ -149,20 +149,17 @@ module.exports = { * @public */ multiplyN: function(m1, m2) { - var j, k, i, sum, + var i, j, sum, m = [], l1 = m1.length, l2 = m2.length; - for (j = 0; j < l2; j++) { - m[j] = []; - for (k = 0; k < m1[0].length; k++) { - sum = 0; - for (i = 0; i < l1; i++) { - sum += m1[i][k] * m2[j][k]; - } - m[j].push(sum); + for (i = 0; i < l1; i++) { + sum = 0; + for (j = 0; j < l2; j++) { + sum += m1[i][j] * m2[j]; } + m.push(sum); } return m; }, @@ -171,7 +168,8 @@ module.exports = { * @public */ inverseN: function(matrix, n) { - var i, j, k, r, + var i, j, k, r, t, + precision = 100000, result = [], row = []; for (i = 0; i < n; i++) { @@ -180,22 +178,28 @@ module.exports = { else matrix[i][j] = 0.0; } } + for (i = 0; i < n; i++) { for (j = 0; j < n; j++) { if (i != j) { r = matrix[j][i] / matrix[i][i]; + r = Math.round(r * precision) / precision; for (k = 0; k < 2 * n; k++) { - matrix[j][k] -= r * matrix[i][k]; + t = Math.round(matrix[j][k] * precision) / precision; + t -= Math.round((r * matrix[i][k]) * precision) / precision; + matrix[j][k] = t; } } } } + for (i = 0; i < n; i++) { + t = matrix[i][i]; for (j = 0; j < 2 * n; j++) { - matrix[i][j] /= matrix[i][i]; + matrix[i][j] = matrix[i][j] / t; } } - + for (i = 0; i < n; i++) { row = []; for (k = 0, j = n; j < 2 * n; j++, k++) { diff --git a/src/AnimationSupport/Tween.js b/src/AnimationSupport/Tween.js index abdc78cd0..6b21ff7a5 100644 --- a/src/AnimationSupport/Tween.js +++ b/src/AnimationSupport/Tween.js @@ -2,6 +2,7 @@ require('enyo'); var frame = require('./Frame'), + matrixUtil = require('./Matrix'), Vector = require('./Vector'); var oldState, newState, node, matrix, cState = []; @@ -89,43 +90,38 @@ module.exports = { return t; }, - calculateEase: function(data, startPoint, endPoint) { - var controlPoints = [startPoint]; - var timeValues = []; - var animValues = []; - var thirdMatrix = []; - - for (var key in data) { - var timeVal = parseFloat(key)/100; - var animVal = parseFloat(data[key])/100; - timeValues.push(timeVal); - animValues.push(animVal); - thirdMatrix.push(animVal - (timeVal * timeVal * timeVal)); - } - - var t1 = timeValues[0]; - var t2 = timeValues[1]; - var d1 = animValues[0]; - var d2 = animValues[1]; - - // Bezier values - var A = 3 * t1 * [(1 - t1) * (1 - t1)]; - var B = (3 * (t1 * t1)) * (1 - t1); - var C = 3 * t2 * [(1 - t2) * (1 - t2)]; - var D = (3 * (t2 * t2)) * (1 - t2); - - var E = thirdMatrix[0]; - var F = thirdMatrix[1]; - var det = 1 / [(A * D) - (B * C)]; - var C1 = ((E * D) - (B * F)) * det; - var C2 = ((A * F) - (C * E)) * det; - controlPoints.push([C1,C1,C1]); - controlPoints.push([C2,C2,C2]); + calculateEase: function(easeObj, startPoint, endPoint) { + var order = (easeObj && Object.keys(easeObj).length) ? (Object.keys(easeObj).length + 1) : 0; + var controlPoints = [startPoint], + bValues = [], + m1 = [], + m2 = [], + m3 = [], + m4 = [], + l = 0; + + var t, a; + for (var key in easeObj) { + t = parseFloat(key) / 100; + a = parseFloat(easeObj[key]) / 100; + bValues = this.getBezierValues(t, order); + bValues.shift(); + m1.push(a - bValues.pop()); + m2.push(bValues); + } + + m3 = matrixUtil.inverseN(m2, bValues.length); + + m4 = matrixUtil.multiplyN(m3, m1); + l = m4.length; + for (var i = 0; i < l; i++) { + controlPoints.push([m4[i], m4[i], m4[i]]); + } + controlPoints.push(endPoint); - - //console.log("CP", controlPoints); - return controlPoints; - }, + + return controlPoints; + }, complete: function (charc) { charc.animating = false; @@ -188,13 +184,12 @@ module.exports = { } } - // console.log("vR", vR); return vR; }, /** * @private - * @params t: time, n: order + * @params n: order, k: current position */ getCoeff: function (n, k) { From f7a5f83edaf552bfb13f0fb73317222fe501ccd4 Mon Sep 17 00:00:00 2001 From: Stephen Choi Date: Wed, 14 Oct 2015 13:44:24 -0700 Subject: [PATCH 125/195] ENYO-2698: Account for popQueueCount when clearing history Enyo-DCO-1.1-Signed-off-by: Stephen Choi --- src/History.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/History.js b/src/History.js index 30a75c90b..231e368fc 100644 --- a/src/History.js +++ b/src/History.js @@ -183,6 +183,9 @@ var EnyoHistory = module.exports = kind.singleton( */ clear: function () { var len = _history.length; + + if (_popQueueCount) len = len - _popQueueCount; + if (len) { this.drop(len); } From c09b4c1f9af9b15ce44990f142668b497292b733 Mon Sep 17 00:00:00 2001 From: Stephen Choi Date: Wed, 14 Oct 2015 16:25:12 -0700 Subject: [PATCH 126/195] ENYO-2689: Clear queue and history instead of adding pop into the queue Enyo-DCO-1.1-Signed-off-by: Stephen Choi --- src/History.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/History.js b/src/History.js index 231e368fc..2efffafc9 100644 --- a/src/History.js +++ b/src/History.js @@ -175,20 +175,17 @@ var EnyoHistory = module.exports = kind.singleton( }, /** - * Asynchronously drops all history entries without calling their respective handlers. When the + * Clears all history entries without calling their respective handlers. When the * entries are popped, the internal history will be empty and the browser history will be * reset to the state when this module started tracking the history. * * @public */ clear: function () { - var len = _history.length; - - if (_popQueueCount) len = len - _popQueueCount; - - if (len) { - this.drop(len); - } + _queue.length = 0; + _popQueueCount = 0; + _history.length = 0; + _pushQueued = false; }, /** From 27fb1755586fa5177466d1c56a699ecb93775146 Mon Sep 17 00:00:00 2001 From: "rajkumar.selvam" Date: Thu, 15 Oct 2015 12:56:45 +0530 Subject: [PATCH 127/195] The function which calculates the binomial coefficient value for bezier functions is updated after unit testing to handle invalid parameters. Enyo-DCO-1.1-Signed-off-by:Rajkumar Selvam --- src/AnimationSupport/Tween.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/AnimationSupport/Tween.js b/src/AnimationSupport/Tween.js index 6b21ff7a5..e6252c4c5 100644 --- a/src/AnimationSupport/Tween.js +++ b/src/AnimationSupport/Tween.js @@ -192,10 +192,14 @@ module.exports = { * @params n: order, k: current position */ getCoeff: function (n, k) { - + n = parseInt(n); + k = parseInt(k); // Credits // https://math.stackexchange.com/questions/202554/how-do-i-compute-binomial-coefficients-efficiently#answer-927064 - + if (isNaN(n) || isNaN(k)) + return void 0; + if ((n < 0) || (k < 0)) + return void 0; if (k > n) return void 0; if (k === 0) From f808a8f482c7a3ddc628df462116b0a2b0bea862 Mon Sep 17 00:00:00 2001 From: Ryan Duffy Date: Thu, 15 Oct 2015 11:01:56 -0500 Subject: [PATCH 128/195] reset the start position on render for translateOptimized scrollers When re-rendering a scrolled scroller using TranslateScrollStrategy with translateOptimized: true, the startX/Y values would be updated to be a non-zero value matching the prior values of scrollLeft/Top. However, the scrollNode was also translated the same amounts. As a result, when the user scrolled, the content would jump the amount of startX/Y resulting in whitespace above the content. Since the purpose of startX/Y is to offset the transform the amount of the scrollLeft/Top of the scrollNode but those values are never updated with translateOptimized: true, we'll reset them to 0 in rendered() so no offsetting occurs. Issue: ENYO-2691 Enyo-DCO-1.1-Signed-off-by: Ryan Duffy (ryan.duffy@lge.com) --- src/TranslateScrollStrategy.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/TranslateScrollStrategy.js b/src/TranslateScrollStrategy.js index 3be9e1800..787898f6d 100644 --- a/src/TranslateScrollStrategy.js +++ b/src/TranslateScrollStrategy.js @@ -67,18 +67,28 @@ module.exports = kind( sup.apply(this, arguments); dispatcher.makeBubble(this.$.clientContainer, 'scroll'); if (this.translateOptimized) { - this.setStartPosition(); + // on render, the start positions should be 0 for translateOptimized because the + // scrollNode's scrollTop/Left will always be 0 and therefore offsetting the + // translate to account for a non-zero scrollTop/Left isn't necessary. + this.setStartPosition(true); } }; }), /** - * @method + * Sets the start position for scrolling. + * + * @param {Boolean} [reset] When true, resets the start position to 0 rather than the current + * scrollTop and scrollLeft. * @private */ - setStartPosition: function() { - this.startX = this.getScrollLeft(); - this.startY = this.getScrollTop(); + setStartPosition: function (reset) { + if (reset) { + this.startX = this.startY = 0; + } else { + this.startX = this.getScrollLeft(); + this.startY = this.getScrollTop(); + } }, /** From e571c3ac838b973bd8a5d80b8448b7168f1a3799 Mon Sep 17 00:00:00 2001 From: Stephen Choi Date: Thu, 15 Oct 2015 15:14:32 -0700 Subject: [PATCH 129/195] ENYO-2689: Ensure queue and history arrays clears object references while clearing --- src/History.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/History.js b/src/History.js index 2efffafc9..2039f6187 100644 --- a/src/History.js +++ b/src/History.js @@ -182,9 +182,9 @@ var EnyoHistory = module.exports = kind.singleton( * @public */ clear: function () { - _queue.length = 0; + _queue.splice(0, _queue.length); + _history.splice(0, _history.length); _popQueueCount = 0; - _history.length = 0; _pushQueued = false; }, From a78079840d0b99493722310d9288dc30ac74c354 Mon Sep 17 00:00:00 2001 From: Stephen Choi Date: Thu, 15 Oct 2015 17:30:05 -0700 Subject: [PATCH 130/195] ENYO-2697: Stop job when clearing to ensure drop handlePop does not get executed Enyo-DCO-1.1-Signed-off-by: Stephen Choi --- src/History.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/History.js b/src/History.js index 2039f6187..5f371310c 100644 --- a/src/History.js +++ b/src/History.js @@ -186,6 +186,7 @@ var EnyoHistory = module.exports = kind.singleton( _history.splice(0, _history.length); _popQueueCount = 0; _pushQueued = false; + this.stopJob('history.go'); }, /** From 4448b8d77d195229f3f6f5c67bbed778dc39b293 Mon Sep 17 00:00:00 2001 From: Gray Norton Date: Thu, 15 Oct 2015 16:45:16 -0700 Subject: [PATCH 131/195] Revert "ENYO-2644: Override scrollToControl() in enyo/NewDataList" This reverts commit 37b81a69844807ba5ed16f81b7797ae16dadf8a9. The original fix for ENYO-2644, which we're reverting here, didn't really address the issue. It made the symptoms disappear, but not in the way originally intended because the issue wasn't properly diagnosed. It also introduced some new issues because it unintentionally skipped some logic related to scrolling options. There were actually some performance- and customization-related benefits to the change, so we'll likely want to reintroduce a version of it at some point in the future, but we'll simply revert it for now since we are at a point in the release cycle where we need to minimize risk of regressions. Enyo-DCO-1.1-Signed-Off-By: Gray Norton (gray.norton@lge.com) --- src/NewDataList.js | 20 -------------------- src/Scrollable/Scrollable.js | 28 +++++++--------------------- 2 files changed, 7 insertions(+), 41 deletions(-) diff --git a/src/NewDataList.js b/src/NewDataList.js index 77d797ddc..183be5b62 100644 --- a/src/NewDataList.js +++ b/src/NewDataList.js @@ -72,26 +72,6 @@ module.exports = kind({ this.scrollTo(b.x, b.y, opts); }, - /** - * This method is considered private for NewDataList, although - * the corresponding method defined in the Scrollable mixin is - * public in the more general case. For NewDataList, application - * code should generally use `scrollToItem()` instead, because it works - * regardless of whether the target item is currently "virtualized," - * whereas `scrollToControl()` works only on non-virtual items. - * - * NewDataList overrides this method because it can provide a - * more accurate, more performant implementation than the general - * one provided by enyo/Scrollable. - * - * @private - */ - scrollToControl: function (control, opts) { - if (typeof control.index === 'number' && control.parent === this.$.container) { - this.scrollToItem(control.index, opts); - } - }, - /** * @private */ diff --git a/src/Scrollable/Scrollable.js b/src/Scrollable/Scrollable.js index 3775889ef..4df032273 100644 --- a/src/Scrollable/Scrollable.js +++ b/src/Scrollable/Scrollable.js @@ -327,27 +327,13 @@ module.exports = { * * @public */ - scrollToControl: kind.inherit(function (sup) { - return function (control, opts) { - var n; - - // Default implementation -- in case the Control - // applying the Scrollable mixin does not supply - // its own - if (sup === utils.nop) { - n = control.hasNode(); - - if (n) { - this.scrollToNode(n, opts); - } - } - // If the Control does provide an alternative - // implementation, we use it - else { - sup.apply(this, arguments); - } - }; - }), + scrollToControl: function (control, opts) { + var n = control.hasNode(); + + if (n) { + this.scrollToNode(n, opts); + } + }, /** * TODO: Document. Based on CSSOM View spec () From ee42cfbe8ae1d98788dd0b953f0065eff8dfb1a9 Mon Sep 17 00:00:00 2001 From: Stephen Choi Date: Thu, 15 Oct 2015 18:15:21 -0700 Subject: [PATCH 132/195] ENYO-2697: Refactor enabledChanged with clear Enyo-DCO-1.1-Signed-off-by: Stephen Choi --- src/History.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/History.js b/src/History.js index 5f371310c..5dc9b0d5f 100644 --- a/src/History.js +++ b/src/History.js @@ -102,12 +102,7 @@ var EnyoHistory = module.exports = kind.singleton( */ enabledChanged: function () { // reset private members - _history = []; - _queue = []; - _popQueueCount = 0; - _pushQueued = false; - _processing = false; - this.stopJob('history.go'); + this.clear(); }, /** @@ -186,6 +181,7 @@ var EnyoHistory = module.exports = kind.singleton( _history.splice(0, _history.length); _popQueueCount = 0; _pushQueued = false; + _processing = false; this.stopJob('history.go'); }, From 63ee03afdfd570c32c7829c917b5cd51c662c7ec Mon Sep 17 00:00:00 2001 From: Gray Norton Date: Thu, 15 Oct 2015 16:39:19 -0700 Subject: [PATCH 133/195] ENYO-2644: Scrollable: Snap to boundaries Often, if you're scrolling to a target near the edge of a scrollable region, it makes sense to scroll all the way to the edge. To support this use case, we add an (experimental, for now) option called `boundarySnapThreshold` to ScrollMath. At the same time, we do a bit of minor code cleanup on another experimental feature of a similar nature (which supports snapping to the boundaries of items within the scrollable region in the specific case where the items are evenly sized and spaced). Due to time constraints and the fact that these features are not ready for wide use, leaving docs as a TODO for the moment. This change was precipitated by an issue filed against Moonstone, where 5-way scrolling to the first / last item in a list was not scrolling all the way to the scroller boundary; as a result, the up / down arrows in the scroll controls were not being disabled, making it unclear that the end of the list had been reached. Enyo-DCO-1.1-Signed-Off-By: Gray Norton (gray.norton@lge.com) --- src/ScrollMath.js | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/ScrollMath.js b/src/ScrollMath.js index 8656bbfd2..242e4a382 100644 --- a/src/ScrollMath.js +++ b/src/ScrollMath.js @@ -158,6 +158,30 @@ module.exports = kind( * @private */ kFrictionEpsilon: platform.webos >= 4 ? 1e-1 : 1e-2, + + /** + * TODO: Document + * Experimental + * + * @private + */ + xSnapIncrement: 0, + + /** + * TODO: Document + * Experimental + * + * @private + */ + ySnapIncrement: 0, + + /** + * TODO: Document + * Experimental + * + * @private + */ + boundarySnapThreshold: 0, /** * Top snap boundary, generally `0`. @@ -683,18 +707,36 @@ module.exports = kind( var animate = !opts || opts.behavior !== 'instant', xSnap = this.xSnapIncrement, ySnap = this.ySnapIncrement, + bSnap = this.boundarySnapThreshold, allowOverScroll = opts && opts.allowOverScroll, maxX = Math.abs(Math.min(0, this.rightBoundary)), maxY = Math.abs(Math.min(0, this.bottomBoundary)); - if (typeof xSnap === 'number') { + + if (xSnap) { x = xSnap * Math.round(x / xSnap); } - if (typeof ySnap === 'number') { + if (ySnap) { y = ySnap * Math.round(y / ySnap); } + if (bSnap) { + if (x > -this.x && maxX > x && maxX - x < bSnap) { + x = maxX; + } + else if (x < -this.x && x > 0 && x < bSnap) { + x = 0; + } + + if (y > -this.y && maxY > y && maxY - y < bSnap) { + y = maxY; + } + else if (y < -this.y && y > 0 && y < bSnap) { + y = 0; + } + } + if (!animate || !allowOverScroll) { x = Math.max(0, Math.min(x, maxX)); y = Math.max(0, Math.min(y, maxY)); From 4d9d06b59bbcb0d0ca6ec7fdb287f9b04f39081c Mon Sep 17 00:00:00 2001 From: Ankur Mishra Date: Fri, 16 Oct 2015 16:32:13 +0530 Subject: [PATCH 134/195] Add test case for easing animations Enyo-DCO-1.1-Signed-off-by:Ankur Mishra --- src/AnimationSupport/Tween.js | 2 + test/tests/DefaultEasing.js | 190 ++++++++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 test/tests/DefaultEasing.js diff --git a/src/AnimationSupport/Tween.js b/src/AnimationSupport/Tween.js index e6252c4c5..827d5e587 100644 --- a/src/AnimationSupport/Tween.js +++ b/src/AnimationSupport/Tween.js @@ -81,6 +81,8 @@ module.exports = { ); frame.accelerate(node, matrix); } + + charc.animationStep && charc.animationStep(t); }, /** diff --git a/test/tests/DefaultEasing.js b/test/tests/DefaultEasing.js new file mode 100644 index 000000000..255ada649 --- /dev/null +++ b/test/tests/DefaultEasing.js @@ -0,0 +1,190 @@ +var + kind = require('enyo/kind'), + Control = require('enyo/Control'); + +var b = 0, + c = 1; + +var easings = { + timeCheck: function(t, d) { + t = t * d; + return t; + }, + easeInCubic: function(t, d) { + t = easings.timeCheck(t, d); + return c * (t /= d) * t * t + b; + }, + easeOutCubic: function(t, d) { + t = easings.timeCheck(t, d); + return c * ((t = t / d - 1) * t * t + 1) + b; + } +}; + +var parseMatrix = function (v) { + var m = [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]; + v = v.replace(/^\w*\(/, '').replace(')', ''); + v = parseValue(v); + if (v.length <= 6) { + m[0] = v[0]; + m[1] = v[1]; + m[4] = v[2]; + m[5] = v[3]; + m[12] = v[4]; + m[13] = v[5]; + } else { + m = v; + } + return m; +}; + +var parseValue = function (val) { + return val.toString().split(",").map(function(v) { + return parseFloat(v, 10); + }); +}; + +describe("Easing Animation", function() { + var TestControl, testControl; + before(function () { + TestControl = kind({ + name: 'TestControl', + kind: Control, + components: [{ + name: "easeIn", + animate: {translate: "100, 0, 0"}, + duration: 100, + ease: easings.easeInCubic + }, { + name: "easeOut", + animate: {translate: "100, 0, 0"}, + duration: 100, + ease: easings.easeOutCubic + }, { + name: "linear", + animate: {translate: "100, 0, 0"}, + duration: 100 + }] + }); + + testControl = new TestControl({parentNode: document.body}); + + }); + + after(function () { + testControl.destroy(); + TestControl = null; + }); + + describe("ease-in timing function", function() { + it("should be at final Position after easing animation on time", function(done) { + testControl.render(); + testControl.$.easeIn.start(true); + testControl.$.easeIn.completed = function() { + var m = parseMatrix(testControl.$.easeIn.node.style.transform); + expect(m[12]).to.equal(100); + done(); + }; + }); + + it("should not be a linear curve", function(done) { + testControl.$.easeIn.addAnimation({translate: "0, 0, 0"}); + testControl.$.easeIn.start(true); + testControl.$.easeIn.animationStep = function(t) { + if(t !== 1) { + var m = parseMatrix(testControl.$.easeIn.node.style.transform); + var distance = ((100 - m[12]) / 100).toFixed(4); + expect(distance !== t.toFixed(4)).to.be.true; + } + }; + testControl.$.easeIn.completed = function() { + //To complete the animation + done(); + }; + }); + + it("should satisfy the equation (d = t^3 i.e easeInCubic curve) in interval [0, 1]", function(done) { + testControl.$.easeIn.addAnimation({translate: "100, 0, 0"}); + testControl.$.easeIn.start(true); + testControl.$.easeIn.animationStep = function(t) { + var m = parseMatrix(testControl.$.easeIn.node.style.transform); + var distance = (m[12] / 100).toFixed(4); + expect(distance === (t * t * t).toFixed(4)).to.be.true; + }; + testControl.$.easeIn.completed = function() { + //To complete the animation + done(); + }; + }); + }); + + describe("ease-out timing function", function() { + it("should be at final Position after easing animation on time", function(done) { + // testControl.render(); + testControl.$.easeOut.start(true); + testControl.$.easeOut.completed = function() { + var m = parseMatrix(testControl.$.easeOut.node.style.transform); + expect(m[12]).to.equal(100); + done(); + }; + }); + + it("should not be a linear curve", function(done) { + testControl.$.easeOut.addAnimation({translate: "0, 0, 0"}); + testControl.$.easeOut.start(true); + testControl.$.easeOut.animationStep = function(t) { + if(t !== 1) { + var m = parseMatrix(testControl.$.easeOut.node.style.transform); + var distance = ((100 - m[12]) / 100).toFixed(4); + expect(distance !== t.toFixed(4)).to.be.true; + } + }; + testControl.$.easeOut.completed = function() { + //To complete the animation + done(); + }; + }); + + it("should satisfy the equation (d = ((t-1)^3) + 1 i.e easeOutCubic curve) in interval [0, 1]", function(done) { + testControl.$.easeOut.addAnimation({translate: "100, 0, 0"}); + testControl.$.easeOut.start(true); + testControl.$.easeOut.animationStep = function(t) { + var m = parseMatrix(testControl.$.easeOut.node.style.transform); + var distance = (m[12] / 100).toFixed(4); + expect(distance === ((--t * t * t) + 1).toFixed(4)).to.be.true; + }; + testControl.$.easeOut.completed = function() { + //To complete the animation + done(); + }; + }); + }); + + describe("when no easing given (linear animation)", function() { + it("should be at final Position after easing animation on time", function(done) { + // testControl.render(); + testControl.$.linear.start(true); + testControl.$.linear.completed = function() { + var m = parseMatrix(testControl.$.linear.node.style.transform); + expect(m[12]).to.equal(100); + done(); + }; + }); + + it("should be a linear i.e d/t = 1", function(done) { + testControl.$.linear.addAnimation({translate: "0, 0, 0"}); + testControl.$.linear.start(true); + testControl.$.linear.animationStep = function(t) { + if(t !== 1) { + var m = parseMatrix(testControl.$.linear.node.style.transform); + var distance = ((100 - m[12]) / 100).toFixed(3); + console.log(distance, t.toFixed(3)); + expect(distance === t.toFixed(3)).to.be.true; + } + }; + testControl.$.linear.completed = function() { + //To complete the animation + done(); + }; + }); + }); +}); \ No newline at end of file From 292281861b499af6a7d2c12b0a594db7e32feaa8 Mon Sep 17 00:00:00 2001 From: "rajkumar.selvam" Date: Fri, 16 Oct 2015 20:04:29 +0530 Subject: [PATCH 135/195] Fixed: Bezier animation starting from any point other zero doesn't animate as expected Enyo-DCO-1.1-Signed-off-by:Rajkumar Selvam --- src/AnimationSupport/Tween.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/AnimationSupport/Tween.js b/src/AnimationSupport/Tween.js index 827d5e587..f80cc7ab2 100644 --- a/src/AnimationSupport/Tween.js +++ b/src/AnimationSupport/Tween.js @@ -172,6 +172,7 @@ module.exports = { c = points.length, l = points[0].length, lastIndex = (c - 1), + startPoint = points[0], endPoint = points[lastIndex], values = this.getBezierValues(t, lastIndex); @@ -179,7 +180,7 @@ module.exports = { vR[i] = 0; for (j = 0; j < c; j++) { if ((j > 0) && (j < (c - 1))) { - vR[i] = vR[i] + (points[j][i] * endPoint[i] * values[j]); + vR[i] = vR[i] + ((startPoint[i] + (points[j][i] * (endPoint[i] - startPoint[i]))) * values[j]); } else { vR[i] = vR[i] + (points[j][i] * values[j]); } @@ -219,6 +220,16 @@ module.exports = { * @params t: time, n: order */ getBezierValues: function (t, n) { + t = parseFloat(t), + n = parseInt(n); + + if (isNaN(t) || isNaN(n)) + return void 0; + if ((t < 0) || (n < 0)) + return void 0; + if (t > 1) + return void 0; + var c, values = [], x = (1 - t), From 3a321ed1cfef4e08e7c08987a1b45e37e2e3af4f Mon Sep 17 00:00:00 2001 From: "rajkumar.selvam" Date: Fri, 16 Oct 2015 20:07:54 +0530 Subject: [PATCH 136/195] Added test cases for bezier points calculation Enyo-DCO-1.1-Signed-off-by:Rajkumar Selvam --- test/tests/TweenTest.js | 377 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 377 insertions(+) create mode 100644 test/tests/TweenTest.js diff --git a/test/tests/TweenTest.js b/test/tests/TweenTest.js new file mode 100644 index 000000000..e57fa0f9d --- /dev/null +++ b/test/tests/TweenTest.js @@ -0,0 +1,377 @@ +describe('Testing Tween Module', function () { + var undefined = void 0; + + describe('Bezier Point Calculation - getBezier() function', function (){ + describe('Random values', function (){ + var result; + it('should be equal to the expect result', function() { + result = [0.00390625, 0.03125, 0.109375, 0.21875, 0.2734375, 0.21875, 0.109375, 0.03125, 0.00390625]; + expect(Tween.getBezierValues(0.5, 8)).to.deep.equal(result); + }); + it('should be equal to the expect result', function() { + result = [0.24009999999999992, 0.4115999999999999, 0.26459999999999995, 0.0756, 0.0081]; + expect(Tween.getBezierValues(0.3, 4)).to.deep.equal(result); + }); + }); + + describe('Boundary values', function (){ + it('should be equal to [1,0,0,0] when the value of t is zero', function() { + expect(Tween.getBezierValues(0, 3)).to.deep.equal([1,0,0,0]); + }); + it('should be equal to [1] when the value of n is zero', function() { + expect(Tween.getBezierValues(0.1, 0)).to.deep.equal([1]); + }); + it('should be equal to [1] when the values of t and n are zero', function() { + expect(Tween.getBezierValues(0, 0)).to.deep.equal([1]); + }); + it('should be equal to undefined when the value of t is greater than 1', function() { + expect(Tween.getBezierValues(2, 3)).to.equal(undefined); + }); + }); + + describe('Mismatching number of arguments', function (){ + it('should return undefined when number of arguments are only one', function() { + expect(Tween.getBezierValues(0.7)).to.equal(undefined); + }); + it('should return undefined when number of arguments are zero', function() { + expect(Tween.getBezierValues()).to.equal(undefined); + }); + }); + + describe('Negative values', function (){ + it('should be equal to undefined when the value of t is less than zero', function() { + expect(Tween.getBezierValues(-0.5, 3)).to.equal(undefined); + }); + it('should be equal to undefined when the value of n is less than zero', function() { + expect(Tween.getBezierValues(0.5, -3)).to.equal(undefined); + }); + it('should be equal to undefined when the values of t and n are less than zero', function() { + expect(Tween.getBezierValues(-0.5, -3)).to.equal(undefined); + }); + }); + + describe('Non number inputs', function (){ + var result = [0.02560000000000001, 0.15360000000000004, 0.3456000000000001, 0.3456, 0.1296]; + it('should return undefined when value of t is string and not parseable to float', function() { + expect(Tween.getBezierValues("abc", 4)).to.equal(undefined); + }); + it('should return undefined when value of n is string and not parseable to integer', function() { + expect(Tween.getBezierValues(0.6, "xy")).to.equal(undefined); + }); + it('should return undefined when values of t and n are string and not parseable to float and integer respectively', function() { + expect(Tween.getBezierValues("abc", "xy")).to.equal(undefined); + }); + it('should return expected result when value of t is string and parseable to float', function() { + expect(Tween.getBezierValues("0.6", 4)).to.deep.equal(result); + }); + it('should return expected result when value of k is string and parseable to integer', function() { + expect(Tween.getBezierValues(0.6, "4")).to.deep.equal(result); + }); + it('should return expected result when value of t is parseable to float and k is parseable to integer', function() { + expect(Tween.getBezierValues("0.6", "4")).to.deep.equal(result); + }); + }); + + describe('Integer input for time (t)', function (){ + var result = [0, 0, 0, 0, 0, 1]; + it('should return bezier values by parsing the value of t as float when givan as integer', function() { + expect(Tween.getBezierValues(1, 5)).to.deep.equal(result); + }); + it('should return undefined if the value of t is greater than 1 after parsing as float', function() { + expect(Tween.getBezierValues(3, 5)).to.equal(undefined); + }); + it('should return undefined if the value of t is less than 0 after parsing as float', function() { + expect(Tween.getBezierValues(-1, 5)).to.equal(undefined); + }); + }); + + describe('Float input for order (n)', function (){ + var result = [0.0024300000000000016, 0.028350000000000014, 0.13230000000000006, 0.30870000000000003, 0.3601499999999999, 0.16806999999999994]; + it('should return bezier values by parsing the value of n as integer when givan as float', function() { + expect(Tween.getBezierValues(0.7, 5.0)).to.deep.equal(result); + }); + it('should return undefined if the value of n is less than 0 after parsing as integer', function() { + expect(Tween.getBezierValues(0.1, -5.0)).to.equal(undefined); + }); + }); + + describe('False values', function (){ + it('should return undefined when value of t is undefined', function() { + expect(Tween.getBezierValues(undefined, 6)).to.equal(undefined); + }); + it('should return undefined when value of n is undefined', function() { + expect(Tween.getBezierValues(0.4, undefined)).to.equal(undefined); + }); + it('should return undefined when values of t and n are undefined', function() { + expect(Tween.getBezierValues(undefined, undefined)).to.equal(undefined); + }); + it('should return undefined when value of t is null', function() { + expect(Tween.getBezierValues(null, 6)).to.equal(undefined); + }); + it('should return undefined when value of n is null', function() { + expect(Tween.getBezierValues(0.4, null)).to.equal(undefined); + }); + it('should return undefined when values of t and n are null', function() { + expect(Tween.getBezierValues(null, null)).to.equal(undefined); + }); + it('should return undefined when value of t is NaN', function() { + expect(Tween.getBezierValues(null, 6)).to.equal(undefined); + }); + it('should return undefined when value of n is NaN', function() { + expect(Tween.getBezierValues(0.4, NaN)).to.equal(undefined); + }); + it('should return undefined when values of t and n are NaN', function() { + expect(Tween.getBezierValues(NaN, NaN)).to.equal(undefined); + }); + }); + }); + + describe('Bezier Values Calculation - getBezierValues() function', function (){ + describe('Random values', function (){ + var result; + it('should be equal to the expect result', function() { + result = [0.00390625, 0.03125, 0.109375, 0.21875, 0.2734375, 0.21875, 0.109375, 0.03125, 0.00390625]; + expect(Tween.getBezierValues(0.5, 8)).to.deep.equal(result); + }); + it('should be equal to the expect result', function() { + result = [0.24009999999999992, 0.4115999999999999, 0.26459999999999995, 0.0756, 0.0081]; + expect(Tween.getBezierValues(0.3, 4)).to.deep.equal(result); + }); + }); + + describe('Boundary values', function (){ + it('should be equal to [1,0,0,0] when the value of t is zero', function() { + expect(Tween.getBezierValues(0, 3)).to.deep.equal([1,0,0,0]); + }); + it('should be equal to [1] when the value of n is zero', function() { + expect(Tween.getBezierValues(0.1, 0)).to.deep.equal([1]); + }); + it('should be equal to [1] when the values of t and n are zero', function() { + expect(Tween.getBezierValues(0, 0)).to.deep.equal([1]); + }); + it('should be equal to undefined when the value of t is greater than 1', function() { + expect(Tween.getBezierValues(2, 3)).to.equal(undefined); + }); + }); + + describe('Bezier values length', function (){ + it('should always be plus one of the value of n', function() { + var n = 5, + values = Tween.getBezierValues(1, 5); + expect(values.length).to.equal(n+1); + }); + }); + + describe('Mismatching number of arguments', function (){ + it('should return undefined when number of arguments are only one', function() { + expect(Tween.getBezierValues(0.7)).to.equal(undefined); + }); + it('should return undefined when number of arguments are zero', function() { + expect(Tween.getBezierValues()).to.equal(undefined); + }); + }); + + describe('Negative values', function (){ + it('should be equal to undefined when the value of t is less than zero', function() { + expect(Tween.getBezierValues(-0.5, 3)).to.equal(undefined); + }); + it('should be equal to undefined when the value of n is less than zero', function() { + expect(Tween.getBezierValues(0.5, -3)).to.equal(undefined); + }); + it('should be equal to undefined when the values of t and n are less than zero', function() { + expect(Tween.getBezierValues(-0.5, -3)).to.equal(undefined); + }); + }); + + describe('Non number inputs', function (){ + var result = [0.02560000000000001, 0.15360000000000004, 0.3456000000000001, 0.3456, 0.1296]; + it('should return undefined when value of t is string and not parseable to float', function() { + expect(Tween.getBezierValues("abc", 4)).to.equal(undefined); + }); + it('should return undefined when value of n is string and not parseable to integer', function() { + expect(Tween.getBezierValues(0.6, "xy")).to.equal(undefined); + }); + it('should return undefined when values of t and n are string and not parseable to float and integer respectively', function() { + expect(Tween.getBezierValues("abc", "xy")).to.equal(undefined); + }); + it('should return expected result when value of t is string and parseable to float', function() { + expect(Tween.getBezierValues("0.6", 4)).to.deep.equal(result); + }); + it('should return expected result when value of k is string and parseable to integer', function() { + expect(Tween.getBezierValues(0.6, "4")).to.deep.equal(result); + }); + it('should return expected result when value of t is parseable to float and k is parseable to integer', function() { + expect(Tween.getBezierValues("0.6", "4")).to.deep.equal(result); + }); + }); + + describe('Integer input for time (t)', function (){ + var result = [0, 0, 0, 0, 0, 1]; + it('should return bezier values by parsing the value of t as float when givan as integer', function() { + expect(Tween.getBezierValues(1, 5)).to.deep.equal(result); + }); + it('should return undefined if the value of t is greater than 1 after parsing as float', function() { + expect(Tween.getBezierValues(3, 5)).to.equal(undefined); + }); + it('should return undefined if the value of t is less than 0 after parsing as float', function() { + expect(Tween.getBezierValues(-1, 5)).to.equal(undefined); + }); + }); + + describe('Float input for order (n)', function (){ + var result = [0.0024300000000000016, 0.028350000000000014, 0.13230000000000006, 0.30870000000000003, 0.3601499999999999, 0.16806999999999994]; + it('should return bezier values by parsing the value of n as integer when givan as float', function() { + expect(Tween.getBezierValues(0.7, 5.0)).to.deep.equal(result); + }); + it('should return undefined if the value of n is less than 0 after parsing as integer', function() { + expect(Tween.getBezierValues(0.1, -5.0)).to.equal(undefined); + }); + }); + + describe('False values', function (){ + it('should return undefined when value of t is undefined', function() { + expect(Tween.getBezierValues(undefined, 6)).to.equal(undefined); + }); + it('should return undefined when value of n is undefined', function() { + expect(Tween.getBezierValues(0.4, undefined)).to.equal(undefined); + }); + it('should return undefined when values of t and n are undefined', function() { + expect(Tween.getBezierValues(undefined, undefined)).to.equal(undefined); + }); + it('should return undefined when value of t is null', function() { + expect(Tween.getBezierValues(null, 6)).to.equal(undefined); + }); + it('should return undefined when value of n is null', function() { + expect(Tween.getBezierValues(0.4, null)).to.equal(undefined); + }); + it('should return undefined when values of t and n are null', function() { + expect(Tween.getBezierValues(null, null)).to.equal(undefined); + }); + it('should return undefined when value of t is NaN', function() { + expect(Tween.getBezierValues(null, 6)).to.equal(undefined); + }); + it('should return undefined when value of n is NaN', function() { + expect(Tween.getBezierValues(0.4, NaN)).to.equal(undefined); + }); + it('should return undefined when values of t and n are NaN', function() { + expect(Tween.getBezierValues(NaN, NaN)).to.equal(undefined); + }); + }); + }); + + describe('Binomial Coefficient Calculation - getCoeff() function', function (){ + describe('Defined values', function (){ + it('should always be equal to 1 when k is 0', function() { + var num = 1000; + expect(Tween.getCoeff(num, 0)).to.equal(1); + }); + it('should always be equal to the input number itself when k is 1', function() { + var num = 1000; + expect(Tween.getCoeff(num, 1)).to.equal(num); + }); + it('should always be equal to 1 when k is same as the input number', function() { + var num = 1000; + expect(Tween.getCoeff(num, num)).to.equal(1); + }); + }); + + describe('Random values', function (){ + it('should be equal to 45 when n is 10 and k is 2', function() { + expect(Tween.getCoeff(10, 2)).to.equal(45); + }); + it('should be equal to 5152635520761925 when n is 350 and k is 8', function() { + expect(Tween.getCoeff(350, 8)).to.equal(5152635520761925); + }); + }); + + describe('Boundary values', function (){ + it('should be equal to undefined when the value of k is greater than the value of n', function() { + expect(Tween.getCoeff(2, 10)).to.equal(undefined); + }); + }); + + describe('Mismatching number of arguments', function (){ + it('should return undefined when number of arguments are only one', function() { + expect(Tween.getCoeff(200)).to.equal(undefined); + }); + it('should return undefined when number of arguments are zero', function() { + expect(Tween.getCoeff()).to.equal(undefined); + }); + }); + + describe('Negative values', function (){ + it('should return undefined when value of n is less than zero', function() { + expect(Tween.getCoeff(-125, 10)).to.equal(undefined); + }); + it('should return undefined when value of k is less than zero', function() { + expect(Tween.getCoeff(125, -10)).to.equal(undefined); + }); + it('should return undefined when values of both n and k are less than zero', function() { + expect(Tween.getCoeff(-125, -10)).to.equal(undefined); + }); + }); + + describe('Non number inputs', function (){ + it('should return undefined when value of n is string and not parseable to integer', function() { + expect(Tween.getCoeff("abc", 10)).to.equal(undefined); + }); + it('should return undefined when value of k is string and not parseable to integer', function() { + expect(Tween.getCoeff(125, "xy")).to.equal(undefined); + }); + it('should return undefined when values of n and k are string and not parseable to integer', function() { + expect(Tween.getCoeff("abc", "xy")).to.equal(undefined); + }); + it('should return expected result when value of n is string and parseable to integer', function() { + expect(Tween.getCoeff("125", 10)).to.equal(177367091094050); + }); + it('should return expected result when value of k is string and parseable to integer', function() { + expect(Tween.getCoeff(125, "10")).to.equal(177367091094050); + }); + it('should return expected result when values of n and k are string and are parseable to integer', function() { + expect(Tween.getCoeff("125", "10")).to.equal(177367091094050); + }); + }); + + describe('Non integer inputs', function (){ + it('should return result as whole number by doing floor of the value of n when givan as float', function() { + expect(Tween.getCoeff(125.2, 10)).to.equal(177367091094050); + }); + it('should return result as whole number by doing floor of the value of k when givan as float', function() { + expect(Tween.getCoeff(125, 10.8)).to.equal(177367091094050); + }); + it('should return result as whole number by doing floor of the values of n and k when givan as float', function() { + expect(Tween.getCoeff(125.2, 10.8)).to.equal(177367091094050); + }); + }); + + describe('False values', function (){ + it('should return undefined when value of n is undefined', function() { + expect(Tween.getCoeff(undefined, 10)).to.equal(undefined); + }); + it('should return undefined when value of k is undefined', function() { + expect(Tween.getCoeff(125, undefined)).to.equal(undefined); + }); + it('should return undefined when values of n and k are undefined', function() { + expect(Tween.getCoeff(undefined, undefined)).to.equal(undefined); + }); + it('should return undefined when value of n is null', function() { + expect(Tween.getCoeff(null, 10)).to.equal(undefined); + }); + it('should return undefined when value of k is null', function() { + expect(Tween.getCoeff(125, null)).to.equal(undefined); + }); + it('should return undefined when values of n and k are null', function() { + expect(Tween.getCoeff(null, null)).to.equal(undefined); + }); + it('should return undefined when value of n is NaN', function() { + expect(Tween.getCoeff(null, 10)).to.equal(undefined); + }); + it('should return undefined when value of k is NaN', function() { + expect(Tween.getCoeff(125, NaN)).to.equal(undefined); + }); + it('should return undefined when values of n and k are NaN', function() { + expect(Tween.getCoeff(NaN, NaN)).to.equal(undefined); + }); + }); + }); +}); From 7cc4e2871a3b0096358055f81c5df7732dac415b Mon Sep 17 00:00:00 2001 From: Madala Cholan Satyanarayana Date: Fri, 16 Oct 2015 20:49:06 +0530 Subject: [PATCH 137/195] Test cases for the custom input of ease values Enyo-DCO-1.1-Signed-off-by:Madala Satyanarayana --- test/tests/CustomEase.js | 120 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 test/tests/CustomEase.js diff --git a/test/tests/CustomEase.js b/test/tests/CustomEase.js new file mode 100644 index 000000000..99232e2c4 --- /dev/null +++ b/test/tests/CustomEase.js @@ -0,0 +1,120 @@ +/*jslint white: true*/ +var + kind = require('enyo/kind'), + Control = require('enyo/Control'); + +var parseMatrix = function(v) { + var m = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + v = v.replace(/^\w*\(/, '').replace(')', ''); + v = parseValue(v); + if (v.length <= 6) { + m[0] = v[0]; + m[1] = v[1]; + m[4] = v[2]; + m[5] = v[3]; + m[12] = v[4]; + m[13] = v[5]; + } else { + m = v; + } + return m; +}; +var parseValue = function(val) { + return val.toString().split(",").map(function(v) { + return parseFloat(v, 10); + }); +}; + + +describe("Easing Animation with custom ease values", function() { + var TestControl, testControl; + before(function() { + TestControl = kind({ + name: 'TestControl', + kind: Control, + id: 'TESTCONTROL1', + components: [{ + name: "test", + animate: { + translate: "100, 0, 0" + }, + duration: 100, + ease: { + 30: 50, + 80: -10 + } + }] + }); + testControl = new TestControl({ + parentNode: document.body + }); + + }); + + after(function() { + testControl.destroy(); + TestControl = null; + }); + + describe("Position of easing element with reference to time ", function() { + it("should be at final Position after easing animation with custom values on time", function(done) { + testControl.render(); + testControl.$.test.start(true); + testControl.$.test.completed = function() { + var m = parseMatrix(testControl.$.test.node.style.transform); + expect(m[12]).to.equal(100); + done(); + }; + }); + + it("should slide right covering 80% animation when the time is 30%", function(done) { + testControl.$.test.addAnimation({ + translate: "-100, 0, 0" + }); + testControl.$.test.start(true); + testControl.$.test.animationStep = function(t) { + if (t > 0.2 && t < 0.4 ) { + var m = parseMatrix(testControl.$.test.node.style.transform); + console.log("first case : " + m); + expect(m[12] >= -60).to.be.true; + } + }; + testControl.$.test.completed = function() { + done(); + }; + }); + it("should slide left to -10% of animation when the time is 80%", function(done) { + testControl.$.test.addAnimation({ + translate: "0, 0, 0" + }); + testControl.$.test.start(true); + testControl.$.test.animationStep = function(t) { + if (t > 0.7 && t < 0.9) { + var m = parseMatrix(testControl.$.test.node.style.transform); + console.log("second case : " + m); + expect(m[12] <-100 ).to.be.true; + } + }; + testControl.$.test.completed = function() { + done(); + }; + }); + + it("should slide right and should be at final point", function(done) { + testControl.$.test.addAnimation({ + translate: "100, 0, 0" + }); + testControl.$.test.start(); + testControl.$.test.animationStep = function(t) { + if (t >= 1) { + var m = parseMatrix(testControl.$.test.node.style.transform); + console.log("third case : " + m); + expect(m[12]).to.equal(100); + } + }; + testControl.$.test.completed = function() { + done(); + }; + }); + }); +}); From b37ecc4b2dacd77b28e5bf621b93d7add2f01c06 Mon Sep 17 00:00:00 2001 From: Gray Norton Date: Fri, 16 Oct 2015 10:43:22 -0700 Subject: [PATCH 138/195] ENYO-2669: VDR: Fix issue with child index Our old logic for updating `childrenByIndex` worked in most cases, but wasn't bullet-proof. In particular, it didn't do well in cases where the indices of existing children changed in "irregular" ways. The issue that exposed the problem was one in which a filter was applied to the underlying collection, causing a potentially random set of items to "disappear" and the indices of remaining items to change, but similar problems could also have occurred when non-contiguous items were added or removed, or when items were reordered. The new logic should work in all such cases. Also did some minor cleanup and added some inline comments describing the logic in the infamous `doIt()` method. Enyo-DCO-1.1-Signed-Off-By: Gray Norton (gray.norton@lge.com) --- src/VirtualDataRepeater.js | 50 +++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/src/VirtualDataRepeater.js b/src/VirtualDataRepeater.js index 309d29b0b..a5fe032ae 100644 --- a/src/VirtualDataRepeater.js +++ b/src/VirtualDataRepeater.js @@ -86,6 +86,7 @@ module.exports = kind({ var pMod = child.model, pIdx = child.index, sc = child.selectedClass || 'selected', + cbi = this.childrenByIndex, s; if (pMod !== model) { @@ -97,12 +98,10 @@ module.exports = kind({ if (pIdx !== index) { child.set('index', index); - this.childrenByIndex[index] = child; - if (this.childrenByIndex[pIdx] === child) { - this.childrenByIndex[pIdx] = null; - } } + cbi[index] = child; + child.removeClass('enyo-vdr-first'); child.removeClass('enyo-vdr-last'); child.show(); @@ -114,17 +113,24 @@ module.exports = kind({ o = this.orderedChildren, o2 = this.orderedChildren_alt, om = this.orderedModels, - omL = om.length, om2 = this.orderedModels_alt, + len = om.length, n = this.numItems, needed = this.needed, b = this.bullpen, - i, idx, m, ci, c, nNeeded, j, ln; + cbi = this.childrenByIndex, + i, idx, m, ci, c, nNeeded, j, len2; + // Walk up from the first index through the + // last and make sure that a child is assigned + // for each for (i = 0; i < n; ++i) { idx = f + i; + // Get our model m = dd.at(idx); if (m) { - ci = omL ? om.indexOf(m) : -1; + // If we already have a child with the model + // we need, reuse it + ci = len ? om.indexOf(m) : -1; if (ci >= 0) { c = o[ci]; o.splice(ci, 1); @@ -133,6 +139,8 @@ module.exports = kind({ this.assignChild(m, idx, c); } else { + // Otherwise, remember that we need to grab + // or create a child for this model needed.push(i); } om2.push(m); @@ -142,38 +150,58 @@ module.exports = kind({ } } nNeeded = needed.length; + // If we have any models that we need to produce children + // for, do it now for (j = 0; j < nNeeded; ++j) { i = needed[j]; idx = f + i; m = om2[i]; + // Reuse an existing child if we have one, checking + // just-decommissioned children first and then children + // previously placed in the bullpen c = om.pop() && o.pop() || b.pop(); if (c) { this.assignChild(m, idx, c); } else { + // If we don't have a child on hand, we create a new one c = this.createComponent({model: m}); this.assignChild(m, idx, c); // TODO: Rethink child lifecycle hooks (currently deploy / update / retire) if (typeof this.deployChild === 'function') this.deployChild(c); c.render(); } + // Put the newly assigned child in the right place in + // the new orderedChildren array o2.splice(i, 0, c); } - needed.length = 0; + // If we have unused children hanging around, make + // them wait in the bullpen while (o.length) { c = om.pop() && o.pop(); c.set('model', null); c.hide(); b.push(c); } + // And now some cleanup... + len2 = o2.length; + // First, if we have fewer children than we had before, + // we need to remove stale entries from our index + for (i = len2; i < len; i++) { + cbi[i] = null; + } + // Reset our "needed" array, so it's ready for next time + needed.length = 0; + // Swap in our new ordered arrays for the old ones this.orderedChildren = o2; this.orderedChildren_alt = o; this.orderedModels = om2; this.orderedModels_alt = om; - ln = o2.length; - if (ln) { + // If we have any children, tag the first and last ones, + // and then apply positioning if applicable + if (len2) { o2[0].addClass('enyo-vdr-first'); - o2[ln - 1].addClass('enyo-vdr-last'); + o2[len2 - 1].addClass('enyo-vdr-last'); if (typeof this.positionChildren === 'function') this.positionChildren(); } }, From 9d2517380211259aae24705de0422ad64340ca71 Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Fri, 16 Oct 2015 16:24:45 -0700 Subject: [PATCH 139/195] JSDoc cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/Collection.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Collection.js b/src/Collection.js index 097311234..21241f53a 100644 --- a/src/Collection.js +++ b/src/Collection.js @@ -212,9 +212,9 @@ var BaseCollection = kind({ * @callback module:enyo/Collection~Collection~Success * @param {module:enyo/Collection~Collection} collection - The [collection]{@link module:enyo/Collection~Collection} * that is returning successfully. -* @param {module:enyo/Collection~ActionOptions} - opts The original options passed to the action method +* @param {module:enyo/Collection~ActionOptions} opts - The original options passed to the action method * that is returning successfully. -* @param {*} - res The result, if any, returned by the [source]{@link module:enyo/Source~Source} that +* @param {*} res - The result, if any, returned by the [source]{@link module:enyo/Source~Source} that * executed it. * @param {String} source - The name of the [source]{@link module:enyo/Collection~Collection#source} that has * returned successfully. From 704eb8745f7f312947d84450c2e8094d769ce698 Mon Sep 17 00:00:00 2001 From: Stephen Choi Date: Mon, 19 Oct 2015 16:05:35 -0700 Subject: [PATCH 140/195] ENYO-2541: Change function from popPanel to removePanel for LightPanels.replaceAt() Enyo-DCO-1.1-Signed-off-by: Stephen Choi --- src/LightPanels/LightPanels.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index 24cce9f29..04d6e92bf 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -532,7 +532,7 @@ module.exports = kind( // remove existing panels for (idx = start; idx < end; idx++) { - this.popPanel(idx); + this.removePanel(panels[idx]); } // add replacement panels From be8975fd01c591662ec8d86f62b4664b20c62ba2 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Mon, 19 Oct 2015 17:55:00 -0700 Subject: [PATCH 141/195] Update version string to 2.6.0-pre.18.1 --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 2e2d34231..9e8024b5c 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ 'use strict'; exports = module.exports = require('./src/options'); -exports.version = '2.6.0-pre.17.1'; +exports.version = '2.6.0-pre.18.1'; From 88713dbec8875b494aa1cd02cac99e30b5198a88 Mon Sep 17 00:00:00 2001 From: Anish_Ramesan Date: Tue, 20 Oct 2015 13:47:52 +0530 Subject: [PATCH 142/195] user interaction using event delegation Enyo-DCO-1.1-Signed-off-by: Anish Ramesan --- src/AnimationSupport/AnimationSupport.js | 61 ++++++++- src/AnimationSupport/Core.js | 44 +++++-- src/AnimationSupport/EventDelegator.js | 152 ++++++++++++++++++++++ src/AnimationSupport/Frame.js | 11 ++ src/AnimationSupport/KeyFrame.js | 28 +++-- src/AnimationSupport/Tween.js | 66 ++++++++-- src/AnimationSupport/Vector.js | 153 ++++++++++++++++++++++- 7 files changed, 474 insertions(+), 41 deletions(-) create mode 100644 src/AnimationSupport/EventDelegator.js diff --git a/src/AnimationSupport/AnimationSupport.js b/src/AnimationSupport/AnimationSupport.js index de4eefdf6..ac74611b7 100644 --- a/src/AnimationSupport/AnimationSupport.js +++ b/src/AnimationSupport/AnimationSupport.js @@ -2,8 +2,9 @@ require('enyo'); var kind = require('../kind'), - core = require('./Core'), + animation = require('./Core'), activator = require('./KeyFrame'), + delegator = require('./EventDelegator'), frame = require('./Frame'), utils = require('../utils'); @@ -19,10 +20,37 @@ var AnimationSupport = { //name: 'AnimationSupport', animating: false, + /** + * To keep a character active for it to apply some other + * animation at runtime. This gives a preformance boost when on + * character an animation is reapplied. + * @default false - So the once the animation is completed, it has to be retriggered to + * start a new animation. + * @private + */ active: false, + /** + * Holds variouts states of animation. + * Like: 'started' - Character animation has started(within rAF) + * 'paused' - Character animation has paused(within rAF) + * 'completed' - Character animation has finished(within rAF) + * @private + */ animationState: "", + /** + * Holds delta value in the order [x, y, z, rad] + * @private + */ + animDelta: [], + + /** + * Maximum threshold for animation + * @private + */ + animMaxThreshold: [], + /** * Check if the character is suitable for animation * @public @@ -41,7 +69,24 @@ var AnimationSupport = { * @public */ setInitial: function (initial) { - this._startAnim = initial; + this._startAnim = initial ? frame.copy(initial) : {}; + }, + + + /** + * Sets animation distance for this character + * @public + */ + setDistance: function (dist) { + this._distance = dist; + }, + + /** + * Gets animation distance for this character + * @public + */ + getDistance: function () { + return this._distance; }, /** @@ -139,6 +184,9 @@ var AnimationSupport = { sup.apply(this, arguments); this.initiate(); frame.accelerate(this.hasNode(), this.matrix); + if (this.handleAnimationEvents) { + delegator.register(this); + } }; }), @@ -147,7 +195,7 @@ var AnimationSupport = { */ destroy: kind.inherit(function(sup) { return function() { - core.remove(this); + animation.remove(this); sup.apply(this, arguments); }; }), @@ -165,14 +213,17 @@ var sup = kind.concatHandler; */ kind.concatHandler = function (ctor, props, instance) { sup.call(this, ctor, props, instance); - if (props.animate || props.keyFrame || props.pattern) { + if (props.animate || props.keyFrame || props.handleAnimationEvents) { var proto = ctor.prototype || ctor; extend(AnimationSupport, proto); if (props.keyFrame && typeof props.keyFrame != 'function') { activator.animate(proto, props); } if (props.animate && typeof props.animate != 'function') { - activator.trigger(proto); + animation.trigger(proto); + } + if (props.handleAnimationEvents && typeof props.handleAnimationEvents != 'function') { + animation.register(proto); } } }; \ No newline at end of file diff --git a/src/AnimationSupport/Core.js b/src/AnimationSupport/Core.js index 0326a73bd..a30504321 100644 --- a/src/AnimationSupport/Core.js +++ b/src/AnimationSupport/Core.js @@ -115,13 +115,20 @@ module.exports = kind.singleton({ * @public */ register: function (charc) { + this.deRegister(charc); this.evnts.push(charc); + this.remove(charc); + charc.animating = true; - /*if (!this.running) { - this.running = true; - this.start(); - }*/ - this.start(); + if (!this.isTicking) { + this.dummy(); + this.isTicking = true; + } + }, + + deRegister: function (curr) { + var idx = this.evnts.indexOf(curr); + if (idx >= 0) this.evnts.splice(idx, 1); }, /** @@ -144,7 +151,7 @@ module.exports = kind.singleton({ loop: function () { var i, curr, len = this.chracs.length, - ts = utils.perfNow(); + ts; if (len <= 0) { this.cancel(); @@ -155,6 +162,7 @@ module.exports = kind.singleton({ for (i = 0; i < len; i++) { curr = this.chracs[i]; if (curr && curr.ready()) { + ts = utils.perfNow(); tween.update(curr, ts); if (!curr._lastTime || ts >= curr._lastTime) { tween.complete(curr); @@ -165,20 +173,32 @@ module.exports = kind.singleton({ } } } + this.start(); + }, - len = this.evnts.length; - for (i = 0; i < len; i++) { - if (typeof this.evnts[i].commitAnimation === 'function') { - this.evnts[i].commitAnimation(); + /** + * @private + */ + eventLoop: function () { + var i, curr, evlen = this.evnts.length; + for (i = 0; i < evlen; i++) { + curr = this.evnts[i]; + if (curr && curr.ready()) { + tween.updateDelta(curr); + if (!curr.animating) { + tween.complete(curr); + curr.completed(curr); + } } } - this.start(); + this.dummy(); }, /** + * TODO: Merge this implementation with actual start * @private */ dummy: function () { - animation.requestAnimationFrame(function() {}); + animation.requestAnimationFrame(this.eventLoop.bind(this)); } }); \ No newline at end of file diff --git a/src/AnimationSupport/EventDelegator.js b/src/AnimationSupport/EventDelegator.js new file mode 100644 index 000000000..36466649e --- /dev/null +++ b/src/AnimationSupport/EventDelegator.js @@ -0,0 +1,152 @@ +require('enyo'); + +var + dispatcher = require('../dispatcher'); + +/** +* This module handles the animation events for the character. +* If the character has opted to have animations handled by animation framework, +* then it can add "handleAnimationEvents" as true as its property. +* The character can also mention which events he wants to be handled by the framework by +* providing list of animation events in "animationEvents" block like; +* { +* name: "myKind", +* animationEvents: [ +* "scroll", +* "mousewheel", +* "touchstart", +* "touchmove", +* "touchend" +* ] +* } +* +* By default these events are handled within the framework(others for now have to be handled by the application). +* +* This module is here temporarily, need to have a proper mechanism +* like dispatcher to handle animation related events along with application events. +* +* @module enyo/AnimationSupport/EventDelegator +*/ +var EventDelegator = { + + /** + * @private + */ + eventArray: [ + "scroll", + "mousewheel", + "touchstart", + "touchmove", + "touchend" + ], + + /** + * Attaches the evnet handlers to the character either its own events or + * else default events with the framework. As of now only these events are + * supported; + * - scroll + * - touch + * - mousewheel + * @public + */ + register: function (charc) { + var events = charc.animationEvents || this.eventArray; + for (var i = 0, l = events.length; i < l; i++) { + this.addRemoveListener(charc, events[i]); + } + }, + + /** + * Detaches the evnet handlers from the character either its own events or + * else default events from with the framework. As of now only these events are + * supported; + * - scroll + * - touch + * - mousewheel + * @public + */ + deRegister: function (charc) { + var events = charc.animationEvents || this.eventArray; + for (var i = 0, l = events.length; i < l; i++) { + this.addRemoveListener(charc, events[i], true); + } + }, + + /** + * @private + */ + addRemoveListener: function(charc, name, remove) { + var d = remove ? dispatcher.stopListening : dispatcher.listen; + d(charc.hasNode(), name, charc.bindSafely(this[name + 'Event'], charc)); + }, + + /** + * @private + */ + touchstartEvent: function (sender, ev) { + sender.touchX = ev.targetTouches[0].pageX; + sender.touchY = ev.targetTouches[0].pageY; + }, + + /** + * @private + */ + touchmoveEvent: function (sender, ev) { + var x = ev.targetTouches[0].pageX, + y = ev.targetTouches[0].pageY; + + if(x !== 0 || y !== 0) { + sender.animDelta[0] = x - sender.touchX; + sender.animDelta[1] = y - sender.touchY; + sender.animDelta[2] = 0; + sender.touchX = x; + sender.touchY = y; + } + }, + + /** + * @private + */ + touchendEvent: function (sender, ev) { + sender.touchX = 0; + sender.touchY = 0; + }, + + /** + * @private + */ + scrollEvent: function (inSender, inEvent) { + var delta = inSender.deltaY, + scrollTop = inSender.target.scrollTop, + scrollLeft = inSender.target.scrollLeft; + + if (this.scrollValue === 0) { + this.scrollValue = inSender.target.scrollTop; + } + + delta = inSender.target.scrollTop - this.scrollValue; + + this.deltaX = scrollLeft - this.deltaX; + this.deltaY = scrollTop - this.deltaY; + this.scrollValue = scrollTop; + + this.deltaX = scrollLeft; + this.deltaY = scrollTop; + + + this.animDelta[0] = delta; + this.animDelta[1] = 0; + this.animDelta[2] = 0; + }, + + /** + * @private + */ + mousewheelEvent: function (sender, ev) { + sender.animDelta[0] = ev.deltaY; + sender.animDelta[1] = ev.deltaX; + sender.animDelta[2] = 0; + } +}; + +module.exports = EventDelegator; \ No newline at end of file diff --git a/src/AnimationSupport/Frame.js b/src/AnimationSupport/Frame.js index 88f525824..3bfe6fae7 100644 --- a/src/AnimationSupport/Frame.js +++ b/src/AnimationSupport/Frame.js @@ -340,5 +340,16 @@ var frame = module.exports = { eP[k] = tP[k] || dP[k]; } return {_startAnim: sP, _endAnim: eP, _transform: dP, currentState: dP}; + }, + + getComputedDistance: function (prop, initalProp, finalProp) { + var k, sV, eV, dst, tot = 0; + for (k in prop) { + sV = initalProp[k]; + eV = finalProp[k]; + dst = (k == 'rotate' ? Vector.quantDistance : Vector.distance)(eV, sV); + tot += dst; + } + return tot; } }; \ No newline at end of file diff --git a/src/AnimationSupport/KeyFrame.js b/src/AnimationSupport/KeyFrame.js index 32c37265f..e553d63be 100644 --- a/src/AnimationSupport/KeyFrame.js +++ b/src/AnimationSupport/KeyFrame.js @@ -50,11 +50,11 @@ var keyFrame = module.exports = kind.singleton({ charc.keyProps.push(keyframe[prop]); } charc.keyframeCallback = cb; - charc.initialTime = utils.perfNow(); charc.totalDuration = proto.duration; charc.completed = this.bindSafely(this.reframe); this.keyFraming(charc); + this.trigger(charc); }, /** @@ -85,41 +85,43 @@ var keyFrame = module.exports = kind.singleton({ charc.keyProps = []; charc.keyTime = []; charc.animating = false; - animation.trigger(charc); + this.trigger(charc); } }, trigger: function (charc) { - animation.trigger(charc); + if (charc.handleAnimationEvents && typeof charc.handleAnimationEvents != 'function') { + animation.register(charc); + } else + animation.trigger(charc); } }); /** * @private */ -keyFrame.keyFraming = function (charc, callback) { +keyFrame.keyFraming = function (charc) { var index = charc.currentIndex || 0, - old = charc.keyTime[index -1], + old = charc.keyTime[index -1] || 0, next = charc.keyTime[index], total = charc.totalDuration, - time = charc.currentIndex ? total * ((next - old)/100) : "0"; + change = total ? total * ((next - old)/100) : "0"; - charc.setAnimation(charc.keyProps[index]); - charc.setInitial(charc.currentState); - charc.setDuration(time); + charc.addAnimation(charc.keyProps[index]); + if(charc.totalDuration) charc.setDuration(change); charc.animating = false; charc.currentIndex = index; - animation.trigger(charc); }; + /** * @private */ keyFrame.reframe = function (charc) { - charc.currentIndex++; - if (charc.currentIndex < charc.keyTime.length) { + charc.reverse ? charc.currentIndex-- : charc.currentIndex++; + if (charc.currentIndex >= 0 && charc.currentIndex < charc.keyTime.length) { this.keyFraming(charc); - charc.start(); + charc.start(true); } else { //Tigerring callback function at end of animation charc.keyframeCallback && charc.keyframeCallback(this); diff --git a/src/AnimationSupport/Tween.js b/src/AnimationSupport/Tween.js index f80cc7ab2..4f2ebe0b2 100644 --- a/src/AnimationSupport/Tween.js +++ b/src/AnimationSupport/Tween.js @@ -42,16 +42,67 @@ module.exports = { } }, + /** + * Tweens public API which notifies to change current state of + * a character based on its interaction delta. This method is normally trigger by the Animation Core to + * update the animating characters state based on the current delta change. + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * @parameter chrac- Animating character + * dt- An array of delta dimensions like [x,y,z] + * + * @public + */ + updateDelta: function (charc, dt) { + var d, + st, + dst, + inc, + frc = charc.friction || 1, + trD = charc.animThresholdDuration || 1000, + trh = charc.animMaxThreshold || false, + dir = charc.direction || 0, + tot = charc.getDistance() || frame.getComputedDistance( + charc.getAnimation(), + charc._startAnim, + charc._endAnim); + + dt = dt || charc.animDelta[dir]; + if (dt) { + dt = frc * dt; + dst = charc._animCurDistane || 0; + inc = dst + dt * (charc.reverse ? -1 : 1); + st = inc > 0 && inc <= tot; + + if (st) { + d = inc / tot; + if (trh && inc > trh && dt === 0) { + charc.setDuration(trD); + charc.start(true); + } + this.step(charc, d); + } else { + charc.animating = st; + charc.reverse = inc <= 0; + } + + charc._animCurDistane = st ? inc : 0; + charc.animDelta = []; + } + }, + /** * @private */ step: function(charc, t) { - var k, c, d, pts; + var k, c, d, pts, props; node = charc.node; newState = charc._endAnim; d = charc._duration; - props = charc.getAnimation(), + props = charc.getAnimation(); oldState = charc._startAnim; charc.currentState = charc.currentState || {}; @@ -127,8 +178,7 @@ module.exports = { complete: function (charc) { charc.animating = false; - charc._startAnim = undefined; - charc._endAnim = undefined; + charc._prop = undefined; }, lerp: function (vA, vB, t, vR) { @@ -195,8 +245,8 @@ module.exports = { * @params n: order, k: current position */ getCoeff: function (n, k) { - n = parseInt(n); - k = parseInt(k); + n = parseInt(n, 10); + k = parseInt(k, 10); // Credits // https://math.stackexchange.com/questions/202554/how-do-i-compute-binomial-coefficients-efficiently#answer-927064 if (isNaN(n) || isNaN(k)) @@ -220,8 +270,8 @@ module.exports = { * @params t: time, n: order */ getBezierValues: function (t, n) { - t = parseFloat(t), - n = parseInt(n); + t = parseFloat(t, 10), + n = parseInt(n, 10); if (isNaN(t) || isNaN(n)) return void 0; diff --git a/src/AnimationSupport/Vector.js b/src/AnimationSupport/Vector.js index a55df1cb3..8cc918b72 100644 --- a/src/AnimationSupport/Vector.js +++ b/src/AnimationSupport/Vector.js @@ -11,8 +11,142 @@ module.exports = { * Divides vector with a scalar value. * @public */ - divide: function (q, s) { - return [q[0] / s, q[1] / s, q[2] / s]; + divide: function (v, s) { + return [v[0] / s, v[1] / s, v[2] / s]; + }, + + /** + * Add vector/quant with a vector/quant. + * @public + */ + add: function (q1, q2) { + q1[0] += q2[0]; + q1[1] += q2[1]; + q1[2] += q2[2]; + if (q1.length > 3 && q2.length > 3) q1[3] += q2[3]; + return q1; + }, + + /** + * Sub vector/quant with a vector/quant. + * @public + */ + subtract: function (q1, q2) { + q1[0] -= q2[0]; + q1[1] -= q2[1]; + q1[2] -= q2[2]; + if (q1.length > 3 && q2.length > 3) q1[3] -= q2[3]; + return q1; + }, + + /** + * Multiply vector/quant with a vector/quant. + * @public + */ + multiply: function (q, s) { + q[0] *= s; + q[1] *= s; + q[2] *= s; + if (q.length > 3) q[3] *= s; + return q; + }, + + /** + * Limits the vector/quant between a maximum and minimum value. + * @public + */ + range: function (q, max, min) { + for (var i = 0; i < q.length; i++) { + q[i] = q[i] < min ? Math.max(q[i], min) : q[i] > max ? Math.min(q[i], max) : q[i]; + } + }, + + /** + * Compares each vector/qunat with a scalar value to check if its equal. + * Returns true when all the vector/quant values are equal to this scalar value. + * @public + */ + equalS: function(q1, s) { + return (q1.length > 0) && q1.every(function(e, i) { + return e === (s || 0); + }); + }, + + /** + * Compares each vector/qunat with a scalar value to check if its greater. + * Returns true when all the vector/quant values are greater or equal to this scalar value. + * @public + */ + greaterS: function(q1, s) { + return (q1.length > 0) && q1.every(function(e, i) { + return e >= (s || 0); + }); + }, + + /** + * Compares each vector/qunat with a scalar value to check if its lesser. + * Returns true when all the vector/quant values are lesser or equal to this scalar value. + * @public + */ + lesserS: function(q1, s) { + return (q1.length > 0) && q1.every(function(e, i) { + return e < (s || 0); + }); + }, + + /** + * Evaluates the gap between two vector values. + * Returns the absolute distance between two vectors. + * @public + */ + distance: function(q1, q2, d) { + d = Math.sqrt( + (q1[0] - q2[0]) * (q1[0] - q2[0]) + + (q1[1] - q2[1]) * (q1[1] - q2[1]) + + (q1[2] - q2[2]) * (q1[2] - q2[2]) + ); + return (d < 0.01 && d > -0.01) ? 0 : d; + }, + + /** + * Evaluates the gap between two quanterions values + * Returns the absolute distance between two quanterions. + * @public + */ + quantDistance: function (q1, q2, d) { + d = Math.sqrt( + (q1[0] - q2[0]) * (q1[0] - q2[0]) + + (q1[1] - q2[1]) * (q1[1] - q2[1]) + + (q1[2] - q2[2]) * (q1[2] - q2[2]) + + (q1[3] - q2[3]) * (q1[3] - q2[3]) + ); + return (d < 0.0001 && d > -0.0001) ? 0 : d; + }, + + /** + * Gives the direction of motion from one vector to other. + * Returns true if moving towards positive direction. + * @public + */ + direction: function (q1, q2) { + return (q1[0] - q2[0]) < 0 || (q1[1] - q2[1]) < 0 || (q1[2] - q2[2]) < 0; + }, + + /** + * Retunns an inverse of a quanterion. + * @public + */ + quantInverse: function (q) { + // var d = (q[0] * q[0]) + (q[1] * q[1]) + (q[2] * q[2]) + (q[3] * q[3]); + return [-q[0], -q[1], -q[2], q[3]]; + }, + + /** + * Length of 3D vectors + * @public + */ + sumS: function (q, s) { + return q[0] * s + q[1] * s + q[2] * s + q[3] !== undefined ? q[3] * s : 0; }, /** @@ -28,7 +162,20 @@ module.exports = { * @public */ dot: function (q1, q2) { - return (q1[0] * q2[0]) + (q1[1] * q2[1]) + (q1[2] * q2[2]) + q1[3] && q2[3] ? (q1[3] * q2[3]) : 0; + return (q1[0] * q2[0]) + (q1[1] * q2[1]) + (q1[2] * q2[2]) + (q1[3] !== undefined && q2[3] !== undefined ? (q1[3] * q2[3]) : 0); + }, + + /** + * Quant Dot product of 3D vectors + * @public + */ + quantCross: function (q1, q2) { + return [ + q1[3] * q2[0] + q1[0] * q2[3] + q1[1] * q2[2] - q1[2] * q2[1], + q1[3] * q2[1] - q1[0] * q2[2] + q1[1] * q2[3] + q1[2] * q2[0], + q1[3] * q2[2] + q1[0] * q2[1] - q1[1] * q2[0] + q1[2] * q2[3], + q1[3] * q2[3] - q1[0] * q2[0] - q1[1] * q2[1] - q1[2] * q2[2] + ]; }, /** From 95dd0bc2cd690ffa37d6485b5fd9f4aba523774d Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Wed, 21 Oct 2015 13:20:22 -0700 Subject: [PATCH 143/195] JSDoc cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- .../AccessibilitySupport.js | 68 +++++++++---------- 1 file changed, 31 insertions(+), 37 deletions(-) diff --git a/src/AccessibilitySupport/AccessibilitySupport.js b/src/AccessibilitySupport/AccessibilitySupport.js index 1acbe760d..35f301ef4 100644 --- a/src/AccessibilitySupport/AccessibilitySupport.js +++ b/src/AccessibilitySupport/AccessibilitySupport.js @@ -49,8 +49,8 @@ var defaultObservers = [ ]; /** -* Prevents browser-initiated scrolling contained controls into view when those controls are -* explicitly focus()'ed. +* Prevents browser-initiated scrolling of contained controls into view when +* those controls are explicitly focused. * * @private */ @@ -166,8 +166,9 @@ var AccessibilitySupport = { name: 'enyo.AccessibilitySupport', /** - * AccessibilityLabel is used for accessibility voice readout. - * If accessibilityLabel is set, screen reader reads the label when control is focused. + * `accessibilityLabel` is used for accessibility voice readout. If + * `accessibilityLabel` is set, the screen reader will read the label when the + * control is focused. * * @type {String} * @default '' @@ -176,9 +177,9 @@ var AccessibilitySupport = { accessibilityLabel: '', /** - * AccessibilityHint is used for additional information of control. - * If accessibilityHint is set and content exists, screen reader - * reads accessibilityHint with content when control is focused. + * `accessibilityHint` is used to provide additional information regarding the + * control. If `accessibilityHint` is set, the screen reader will read the + * hint content when the control is focused. * * @type {String} * @default '' @@ -187,7 +188,7 @@ var AccessibilitySupport = { accessibilityHint: '', /** - * The `role` of the control. May be superceded by a truthy `accessibilityAlert` value. + * The `role` of the control. May be superseded by a truthy `accessibilityAlert` value. * * @type {String} * @default '' @@ -196,16 +197,12 @@ var AccessibilitySupport = { accessibilityRole: '', /** - * AccessibilityAlert is for alert message or page description. - * If accessibilityAlert is true, aria role will be set to "alert" and - * screen reader will automatically reads content or accessibilityLabel - * regardless focus. - * Note that if you use accessibilityAlert, previous role will be - * replaced with "alert" role. - * - * Range: [`true`, `false`] - * - true: screen reader automatically reads label regardless focus. - * - false: screen reader reads label with focus. + * `accessibilityAlert` affects the handling of alert message or page + * description content. If `true`, aria role will be set to "alert" and the + * screen reader will automatically read the content of `accessibilityLabel`, + * regardless of focus state; if `false` (the default), the label will be read + * when the control receives focus. Note that if you use `accessibilityAlert`, + * the previous role will be replaced with "alert" role. * * @type {Boolean} * @default false @@ -214,13 +211,10 @@ var AccessibilitySupport = { accessibilityAlert: false, /** - * AccessibilityLive is for dynamic content which updates without a page reload. - * If AccessibilityLive is true, screen reader will read content or accessibilityLabel - * when it changed. - * - * Range: [`true`, `false`] - * - true: screen reader reads content when it changed. - * - false: screen reader reads content with focus. + * `accessibilityLive` affects the handling of dynamic content that updates + * without a page reload. If `true`, the screen reader will read the content of + * `accessibilityLabel` when the content changes; if `false` (the default), the + * label will be read when the control gains focus. * * @type {Boolean} * @default false @@ -229,11 +223,9 @@ var AccessibilitySupport = { accessibilityLive: false, /** - * AccessibilityDisabled prevents VoiceReadout. - * If accessibilityDisabled is true, screen reader doesn't read any label for the control. - * Note that this is not working on HTML form elements which can get focus without tabindex. - * - * Range: [`true`, `false`] + * `accessibilityDisabled` is used to prevent voice readout. If `true`, the + * screen reader will not read the label for the control. Note that this is not + * working on HTML form elements which can get focus without tabindex. * * @type {Boolean} * @default false @@ -242,9 +234,10 @@ var AccessibilitySupport = { accessibilityDisabled: false, /** - * When true, `onscroll` events will be observed and scrolling prevented by resetting the - * `scrollTop` and `scrollLeft` of the node. This prevents inadvertent layout issues introduced - * by the browser scrolling contained controls into view when `focus()`'ed. + * When `true`, `onscroll` events will be observed and scrolling will be + * prevented by resetting the node's `scrollTop` and `scrollLeft` values. This + * prevents inadvertent layout issues introduced by the browser's scrolling + * contained controls into view when focused. * * @type {Boolean} * @default false @@ -253,9 +246,9 @@ var AccessibilitySupport = { accessibilityPreventScroll: false, /** - * Sets the `tabindex` of the control. When `undefined` on webOS, it will be set to -1 to enable - * screen reading. A value of `null` (or `undefined` on non-webOS) ensures that no `tabindex` is - * set. + * The `tabindex` of the control. When `undefined` on webOS, it will be set to + * `-1` to enable screen reading. A value of `null` (or `undefined` on + * non-webOS) ensures that no `tabindex` will be set. * * @type {Number} * @default undefined @@ -274,7 +267,8 @@ var AccessibilitySupport = { }), /** - * If accessibilityDisabled is `false`, sets the node attribute. Otherwise, removes it. + * If `accessibilityDisabled` is `false`, sets the specified node attribute; + * otherwise, removes it. * * @param {String} name Attribute name * @param {String} value Attribute value From 302b81f81dff56a7f2d6097e18c85b3401f3b4c9 Mon Sep 17 00:00:00 2001 From: Stephen Choi Date: Wed, 21 Oct 2015 15:22:51 -0700 Subject: [PATCH 144/195] ENYO-2719: Remove panel in reverse order Enyo-DCO-1.1-Signed-off-by: Stephen Choi --- src/LightPanels/LightPanels.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index 04d6e92bf..aa4f3eb8b 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -531,7 +531,7 @@ module.exports = kind( commonInfo = {addBefore: insertBefore}; // remove existing panels - for (idx = start; idx < end; idx++) { + for (idx = end - 1; idx >= start; idx--) { this.removePanel(panels[idx]); } From 327fdef89eb6c5a6f57aafc5df32a45cf7417ecc Mon Sep 17 00:00:00 2001 From: Gray Norton Date: Fri, 23 Oct 2015 09:45:12 -0700 Subject: [PATCH 145/195] ENYO-2696: Restore async to VirtualDataRepeater.refresh() In the original fix for ENYO-2514, we modified VDR.refresh() to make it work synchronously, as the historical reasons for making it async (inherited from DataRepeater) seemed not to apply, and the async behavior was causing a problem in one case: when the refresh was deferred because the repeater was hidden, the "stale" state of the repeater would flash momentarily on screen after the repeater was re-shown but before the refresh occurred. Making refresh() synchronous fixed that case, but it appears that there are still some cases where async refresh has significant perf benefits, so in this change we're restoring the async behavior and taking a different approach to fixing ENYO-2514. The new issue (ENYO-2696) that prompted the change back to async was one in which the collection underlying the repeater was emptied and immediately repopulated. In this case, the async approach results in debouncing the refresh; it only refreshes once, after repopulating, rather than once after emptying and again after repopulating. To solve the flashing-stale-content issue, we now use the CSS visibility property to make the repeater invisible while it's in the process of performing a deferred refresh. While making this change, also did some cleanup and added some inline comments. Finally, note that the logic for deferring refresh() and reset() originally lived in NewDataList, not in VirtualDataRepeater. In the process of reviewing and integrating the original fix for ENYO-2514, we determined that it made sense to move it into VDR and wrote up a new issue (ENYO-2533) to track that work. Since we're back in the same code now, we're making that change, too. Enyo-DCO-1.1-Signed-Off-By: Gray Norton (gray.norton@lge.com) --- src/VirtualDataRepeater.js | 69 +++++++++++++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 9 deletions(-) diff --git a/src/VirtualDataRepeater.js b/src/VirtualDataRepeater.js index d09b4f2db..ffc4837b0 100644 --- a/src/VirtualDataRepeater.js +++ b/src/VirtualDataRepeater.js @@ -25,13 +25,23 @@ module.exports = kind({ // reorderNodes: false, reset: function () { + // If we are showing, go ahead and reset... if (this.getAbsoluteShowing()) { this.init(); this.destroyClientControls(); this.setExtent(); - this._needsReset = false; + if (this._needsReset) { + // Now that we've done our deferred reset, we + // can become visible again + this.applyStyle('visibility', 'showing'); + this._needsReset = false; + } } + // If we aren't showing, defer the reset until we are shown again else { + // Because our state will be stale by the time we reset, become invisible + // to avoid briefly "flashing" the stale state when we are shown + this.applyStyle('visibility', 'hidden'); this._needsReset = true; } }, @@ -71,19 +81,58 @@ module.exports = kind({ } }, - refresh: function () { + refresh: function (immediate) { + // If we haven't initialized, we need to reset instead if (!this.hasInitialized) return this.reset(); - if (this.getAbsoluteShowing()) { - if (arguments[1] === 'reset' && typeof this.collectionResetHandler === 'function') { - this.collectionResetHandler(); + // If we're being called as the handler for a collection reset, + // note that so we can do extra stuff as needed + if (arguments[1] === 'reset' && typeof this.collectionResetHandler === 'function') { + this._needsToHandleCollectionReset = true; + } + + // For performance reasons, it's better to refresh asynchronously most + // of the time, so we put the refresh logic in a function we can async + var refresh = this.bindSafely(function () { + // If we are showing, go ahead and refresh... + if (this.getAbsoluteShowing()) { + this.stabilizeExtent(); + this.doIt(); + // If we need to do anything to respond to the collection + // resetting, now's the time + if (this._needsToHandleCollectionReset) { + this.collectionResetHandler(); + this._needsToHandleCollectionReset = false; + } + if (this._needsRefresh) { + // Now that we've done our deferred refresh, we + // can become visible again + this.applyStyle('visibility', 'visible'); + this._needsRefresh = false; + } } - this.stabilizeExtent(); - this.doIt(); - this._needsRefresh = false; + // If we aren't showing, defer the refresh until we are shown again + else { + // Because our state will be stale by the time we refresh, become invisible + // to avoid briefly "flashing" the stale state when we are shown + this.applyStyle('visibility', 'hidden'); + this._needsRefresh = true; + } + }); + + // refresh is used as the event handler for + // collection resets so checking for truthy isn't + // enough. it must be true. + if (immediate === true) { + refresh(); } else { - this._needsRefresh = true; + // TODO: Consider the use cases and the reasons why + // we're making this async, and decide whether it makes + // sense to delay more than 16ms. In particular, in cases + // where the benefit of async'ing comes from debouncing, it + // may be that 16ms is not enough on slower hardware. + this.startJob('refreshing', refresh, 16); } }, @@ -92,6 +141,8 @@ module.exports = kind({ */ showingChangedHandler: kind.inherit(function (sup) { return function () { + // If we have deferred a reset or a refresh, + // take care of it now that we're showing again if (this._needsReset) { this.reset(); } From 641e05004aefa1dff7adfb6fd0e29b17110d21f7 Mon Sep 17 00:00:00 2001 From: Gray Norton Date: Mon, 26 Oct 2015 16:21:20 -0700 Subject: [PATCH 146/195] ENYO-2734: Remove transparent-image placeholder support The placeholder feature in enyo/Image currently has logic to remove the placeholder when the image itself loads. This logic was intended to provide support for images that contain transparent or semi-transparent pixels, by preventing the placeholder from "showing through" those pixels. However, supporting this use case can cause expensive style recalculation under some circumstances -- specifically, it was causing grid list regeneration on mid-range TV hardware (M16) to be half a second slower. Since the 80+% use case for placeholder images involves traditional opaque, rectangular images, we'll simply remove the sometimes- expensive logic and document the limitation. We can consider reintroducing placeholder support for transparent images at some future point, either with a flag to explicitly turn it on or with an as-yet-not-thought-of, better-performing implementation. Enyo-DCO-1.1-Signed-Off-By: Gray Norton (gray.norton@lge.com) --- src/Image/Image.js | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/Image/Image.js b/src/Image/Image.js index 9ce18fe57..7105482d7 100644 --- a/src/Image/Image.js +++ b/src/Image/Image.js @@ -156,6 +156,11 @@ module.exports = kind( * Provides a default image displayed while the URL specified by `src` is loaded or when that * image fails to load. * + * Note that the placeholder feature is not designed for use with images that contain transparent + * or semi-transparent pixels. Specifically, for performance reasons, the placeholder image is not + * removed when the image itself loads, but is simply covered by the image. This means that the + * placeholder will show through any transparent or semi-transparent pixels in the image. + * * @type {String} * @default '' * @public @@ -195,7 +200,6 @@ module.exports = kind( * @private */ handlers: { - onload: 'handleLoad', onerror: 'handleError' }, @@ -267,19 +271,6 @@ module.exports = kind( } }, - /** - * When the image is loaded successfully, we want to clear out the background image so it doesn't - * show through the transparency of the image. This only works when not using `sizing` because we - * do not get load/error events for failed background-image's. - * - * @private - */ - handleLoad: function () { - if (!this.sizing && this.placeholder) { - this.applyStyle('background-image', null); - } - }, - /** * @private */ @@ -305,10 +296,10 @@ module.exports = kind( url = srcUrl && plUrl && (srcUrl + ',' + plUrl) || srcUrl || plUrl || 'none'; this.applyStyle('background-image', url); } - // if we've haven't failed to load src (this.src && this._src == this.src), we don't want to - // add the bg image that may have already been removed by handleLoad - else if (!(prop == 'placeholder' && this.src && this._src == this.src)) { + else if (prop === 'placeholder') { this.applyStyle('background-image', plUrl); + } + else { this.setAttribute('src', src); } }, From e4e5067c634ce3fb726b110ff2fe6ae70176ba90 Mon Sep 17 00:00:00 2001 From: Anish_Ramesan Date: Tue, 27 Oct 2015 23:00:37 +0530 Subject: [PATCH 147/195] interpolation of quanternions Enyo-DCO-1.1-Signed-off-by: Anish Ramesan(anish.ramesan@lge.com) --- src/AnimationSupport/AnimationSupport.js | 2 +- src/AnimationSupport/Tween.js | 719 ++++++++++++++--------- src/AnimationSupport/Vector.js | 8 + 3 files changed, 436 insertions(+), 293 deletions(-) diff --git a/src/AnimationSupport/AnimationSupport.js b/src/AnimationSupport/AnimationSupport.js index ac74611b7..30856ccb6 100644 --- a/src/AnimationSupport/AnimationSupport.js +++ b/src/AnimationSupport/AnimationSupport.js @@ -135,7 +135,7 @@ var AnimationSupport = { * @public */ getDuration: function() { - return this._duration || (this._duration = this.duration); + return this._duration || this.duration; }, /** diff --git a/src/AnimationSupport/Tween.js b/src/AnimationSupport/Tween.js index 4f2ebe0b2..7db3c15ff 100644 --- a/src/AnimationSupport/Tween.js +++ b/src/AnimationSupport/Tween.js @@ -1,298 +1,433 @@ require('enyo'); -var - frame = require('./Frame'), - matrixUtil = require('./Matrix'), - Vector = require('./Vector'); +var + frame = require('./Frame'), + matrixUtil = require('./Matrix'), + Vector = require('./Vector'); var oldState, newState, node, matrix, cState = []; /** -* Tween is a module responsible for creating intermediate frames for an animation. -* The responsibilities of this module is to; -* - Interpolating current state of character. -* - Update DOM based on current state, using matrix for tranform and styles for others. -* -* @module enyo/AnimationSupport/Tween -*/ + * Tween is a module responsible for creating intermediate frames for an animation. + * The responsibilities of this module is to; + * - Interpolating current state of character. + * - Update DOM based on current state, using matrix for tranform and styles for others. + * + * @module enyo/AnimationSupport/Tween + */ module.exports = { - /** - * Tweens public API which notifies to change current state of - * a character. This method is normally trigger by the Animation Core to - * update the animating characters state based on the current timestamp. - * - * As of now this method is provided as an interface for application - * to directly trigger an animation. However, this will be later made private - * and will be accessible only by the interfaces exposed by framework. - * @parameter chrac- Animating character - * ts- DOMHighResTimeStamp - * - * @public - */ - update: function (charc, ts) { - var t, - dur = charc._duration, - since = ts - charc._startTime; - - if (since < 0) return; - if (since <= dur) { - t = since / dur; - this.step(charc, t); - } else { - this.step(charc, 1); - } - }, - - /** - * Tweens public API which notifies to change current state of - * a character based on its interaction delta. This method is normally trigger by the Animation Core to - * update the animating characters state based on the current delta change. - * - * As of now this method is provided as an interface for application - * to directly trigger an animation. However, this will be later made private - * and will be accessible only by the interfaces exposed by framework. - * @parameter chrac- Animating character - * dt- An array of delta dimensions like [x,y,z] - * - * @public - */ - updateDelta: function (charc, dt) { - var d, - st, - dst, - inc, - frc = charc.friction || 1, - trD = charc.animThresholdDuration || 1000, - trh = charc.animMaxThreshold || false, - dir = charc.direction || 0, - tot = charc.getDistance() || frame.getComputedDistance( - charc.getAnimation(), - charc._startAnim, - charc._endAnim); - - dt = dt || charc.animDelta[dir]; - if (dt) { - dt = frc * dt; - dst = charc._animCurDistane || 0; - inc = dst + dt * (charc.reverse ? -1 : 1); - st = inc > 0 && inc <= tot; - - if (st) { - d = inc / tot; - if (trh && inc > trh && dt === 0) { - charc.setDuration(trD); - charc.start(true); - } - this.step(charc, d); - } else { - charc.animating = st; - charc.reverse = inc <= 0; - } - - charc._animCurDistane = st ? inc : 0; - charc.animDelta = []; - } - }, - - /** - * @private - */ - step: function(charc, t) { - var k, c, d, pts, props; - - node = charc.node; - newState = charc._endAnim; - d = charc._duration; - props = charc.getAnimation(); - oldState = charc._startAnim; - charc.currentState = charc.currentState || {}; - - for (k in props) { - cState = frame.copy(charc.currentState[k] || []); - if (charc.ease && (typeof charc.ease !== 'function') && (k !== 'rotate')) { - pts = this.calculateEase(charc.ease, frame.copy(oldState[k]), frame.copy(newState[k])); - cState = this.getBezier(t, pts, cState); - } else { - c = k == 'rotate' ? this.slerp : this.lerp; - cState = t ? c(oldState[k], newState[k], ((typeof charc.ease === 'function') ? charc.ease : this.ease)(t, d), cState) : newState[k]; - } - - if (!frame.isTransform(k)) { - frame.setProperty(node, k, cState); - } - charc.currentState[k] = cState; - } - - if(charc._transform) { - matrix = frame.recomposeMatrix( - charc.currentState.translate, - charc.currentState.rotate, - charc.currentState.scale, - charc.currentState.skew, - charc.currentState.perspective - ); - frame.accelerate(node, matrix); - } - - charc.animationStep && charc.animationStep(t); - }, - - /** - * @private - */ - ease: function (t) { - return t; - }, - - calculateEase: function(easeObj, startPoint, endPoint) { - var order = (easeObj && Object.keys(easeObj).length) ? (Object.keys(easeObj).length + 1) : 0; - var controlPoints = [startPoint], - bValues = [], - m1 = [], - m2 = [], - m3 = [], - m4 = [], - l = 0; - - var t, a; - for (var key in easeObj) { - t = parseFloat(key) / 100; - a = parseFloat(easeObj[key]) / 100; - bValues = this.getBezierValues(t, order); - bValues.shift(); - m1.push(a - bValues.pop()); - m2.push(bValues); - } - - m3 = matrixUtil.inverseN(m2, bValues.length); - - m4 = matrixUtil.multiplyN(m3, m1); - l = m4.length; - for (var i = 0; i < l; i++) { - controlPoints.push([m4[i], m4[i], m4[i]]); - } - - controlPoints.push(endPoint); - - return controlPoints; - }, - - complete: function (charc) { - charc.animating = false; - charc._prop = undefined; - }, - - lerp: function (vA, vB, t, vR) { - if (!vR) vR = []; - var i, l = vA.length; - - for (i = 0; i < l; i++) { - vR[i] = (1 - t) * vA[i] + t * vB[i]; - } - return vR; - }, - - slerp: function (qA, qB, t, qR) { - if (!qR) qR = []; - var a, b, w, theta, dot = Vector.dot(qA, qB); - - dot = Math.min(Math.max(dot, -1.0), 1.0); - if (dot == 1.0) { - qR = frame.copy(qA); - return qR; - } - - theta = Math.acos(dot); - w = Math.sin(t * theta) * 1 / Math.sqrt(1 - dot * dot); - for (var i = 0; i < 4; i++) { - a = qA[i] * (Math.cos(t * theta) - dot * w); - b = qB[i] * w; - qR[i] = a + b; - } - return qR; - }, - - /** - * @public - * @params t: time, points: knot and control points, vR: resulting point - */ - getBezier: function (t, points, vR) { - if (!vR) vR = []; - - var i, j, - c = points.length, - l = points[0].length, - lastIndex = (c - 1), - startPoint = points[0], - endPoint = points[lastIndex], - values = this.getBezierValues(t, lastIndex); - - for (i = 0; i < l; i++) { - vR[i] = 0; - for (j = 0; j < c; j++) { - if ((j > 0) && (j < (c - 1))) { - vR[i] = vR[i] + ((startPoint[i] + (points[j][i] * (endPoint[i] - startPoint[i]))) * values[j]); - } else { - vR[i] = vR[i] + (points[j][i] * values[j]); - } - } - } - - return vR; - }, - - /** - * @private - * @params n: order, k: current position - */ - getCoeff: function (n, k) { - n = parseInt(n, 10); - k = parseInt(k, 10); - // Credits - // https://math.stackexchange.com/questions/202554/how-do-i-compute-binomial-coefficients-efficiently#answer-927064 - if (isNaN(n) || isNaN(k)) - return void 0; - if ((n < 0) || (k < 0)) - return void 0; - if (k > n) - return void 0; - if (k === 0) - return 1; - if (k === n) - return 1; - if (k > n / 2) - return this.getCoeff(n, n - k); - - return n * this.getCoeff(n - 1, k - 1) / k; - }, - - /** - * @public - * @params t: time, n: order - */ - getBezierValues: function (t, n) { - t = parseFloat(t, 10), - n = parseInt(n, 10); - - if (isNaN(t) || isNaN(n)) - return void 0; - if ((t < 0) || (n < 0)) - return void 0; - if (t > 1) - return void 0; - - var c, - values = [], - x = (1 - t), - y = t; - - // - // Binomial theorem to expand (x+y)^n - // - for (var k = 0; k <= n; k++) { - c = this.getCoeff(n, k) * Math.pow(x, (n - k)) * Math.pow(y, k); - values.push(c); - } - - return values; - } -}; \ No newline at end of file + /** + * Tweens public API which notifies to change current state of + * a character. This method is normally trigger by the Animation Core to + * update the animating characters state based on the current timestamp. + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * @parameter chrac- Animating character + * ts- DOMHighResTimeStamp + * + * @public + */ + update: function(charc, ts) { + var t, + dur = charc._duration, + since = ts - charc._startTime; + + if (since < 0) return; + if (since <= dur) { + t = since / dur; + this.step(charc, t); + } else { + this.step(charc, 1); + } + }, + + /** + * Tweens public API which notifies to change current state of + * a character based on its interaction delta. This method is normally trigger by the Animation Core to + * update the animating characters state based on the current delta change. + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * @parameter chrac- Animating character + * dt- An array of delta dimensions like [x,y,z] + * + * @public + */ + updateDelta: function(charc, dt) { + var d, + st, + dst, + inc, + frc = charc.friction || 1, + trD = charc.animThresholdDuration || 1000, + trh = charc.animMaxThreshold || false, + dir = charc.direction || 0, + tot = charc.getDistance() || frame.getComputedDistance( + charc.getAnimation(), + charc._startAnim, + charc._endAnim); + + dt = dt || charc.animDelta[dir]; + if (dt) { + dt = frc * dt; + dst = charc._animCurDistane || 0; + inc = dst + dt * (charc.reverse ? -1 : 1); + st = inc > 0 && inc <= tot; + + if (st) { + d = inc / tot; + if (trh && inc > trh && dt === 0) { + charc.setDuration(trD); + charc.start(true); + } + this.step(charc, d); + } else { + charc.animating = st; + charc.reverse = inc <= 0; + } + + charc._animCurDistane = st ? inc : 0; + charc.animDelta = []; + } + }, + + /** + * @private + */ + step: function(charc, t) { + var k, c, d, pts, props; + + node = charc.node; + newState = charc._endAnim; + d = charc._duration; + props = charc.getAnimation(); + oldState = charc._startAnim; + charc.currentState = charc.currentState || {}; + + + for (k in props) { + cState = frame.copy(charc.currentState[k] || []); + if (charc.ease && (typeof charc.ease !== 'function')) { + if((k == 'rotate')) { + //Without control points + // pts = this.beizerSlerpPoints(charc.ease, frame.copy(oldState[k]), frame.copy(newState[k]), props[k]); + // cState = this.beizerSlerp(t, pts, cState); + + //With control points process + pts = this.beizerSPoints(charc.ease, frame.copy(oldState[k]), frame.copy(newState[k]), props[k]); + cState = this.beizerSpline(t, pts, cState); + } else { + pts = this.calculateEase(charc.ease, frame.copy(oldState[k]), frame.copy(newState[k])); + cState = this.getBezier(t, pts, cState); + } + } else { + c = k == 'rotate' ? this.slerp : this.lerp; + cState = t ? c(oldState[k], newState[k], ((typeof charc.ease === 'function') ? charc.ease : this.ease)(t, d), cState) : newState[k]; + } + + if (!frame.isTransform(k)) { + frame.setProperty(node, k, cState); + } + charc.currentState[k] = cState; + } + + if (charc._transform) { + matrix = frame.recomposeMatrix( + charc.currentState.translate, + charc.currentState.rotate, + charc.currentState.scale, + charc.currentState.skew, + charc.currentState.perspective + ); + frame.accelerate(node, matrix); + } + + charc.animationStep && charc.animationStep(t); + }, + + + /** + * @private + */ + ease: function(t) { + return t; + }, + + //Without control points + beizerSlerpPoints: function(ease, startQuat, endQuat, endPoint) { + var tm, ag, q, key, + splinePoints = {}, + eD = frame.parseValue(endPoint), + aN = startQuat; + + if(ease && Object.keys(ease).length > 0) { + for (key in ease) { + tm = parseFloat(key) / 100; + ag = parseFloat(ease[key]); + eD.pop(); // remove angle from end point. + eD[eD.length] = ag; + q = Vector.toQuant(frame.copy(eD)); + splinePoints[tm] = [aN, q]; + aN = q; + } + splinePoints[1] = [aN, endQuat]; + } + return splinePoints; + }, + + //Without control points + beizerSlerp: function(t, points, vR) { + var p, key; + for(p in points) { + if(p >= t) key = p; + } + vR = this.slerp(points[key][0] , points[key][1], t); + return vR; + }, + + //With control points + beizerSPoints: function(ease, startQuat, endQuat, endPoint) { + var splinePoints = {}, + time = [0], + quats = [startQuat]; + + var t, a, q, n, _a, aI, bN, + eD = frame.parseValue(endPoint); + + if(ease && Object.keys(ease).length > 0) { + for (var key in ease) { + t = parseFloat(key) / 100; + a = parseFloat(ease[key]); + eD.pop(); // remove angle from end point. + eD[eD.length] = a; + q = Vector.toQuant(frame.copy(eD)); + quats.push(q); + time.push(t); + } + + quats.push(endQuat); + time.push(1); + + n = quats.length -1; + for (var i = 0, j = 1; i < n; i++, j++) { + if(i === 0) { + aI = this.slerp(quats[0], this.slerp(quats[2], quats[1], 2.0), 1.0 / 3); + } else { + _a = this.slerp(this.slerp(quats[i-1], quats[i], 2.0), quats[i+1], 0.5); + aI = this.slerp(quats[j] , _a , 1.0 / 3); + } + if(j === n) { + bN = this.slerp(quats[j], this.slerp(quats[j-2], quats[j-1], 2.0), 1.0 / 3); + } else { + _a = this.slerp(this.slerp(quats[j-1], quats[j], 2.0), quats[j+1], 0.5); + bN = this.slerp(quats[j] , _a , -1.0 / 3); + } + splinePoints[time[j]] = [quats[i], aI, bN, quats[i+1]]; + } + } + return splinePoints; + }, + + //With control points + beizerSpline: function (t, points, vR) { + if(!vR) vR = []; + var Q0, Q1, Q2, R0, R1; + + var p, key, pts; + for(p in points) { + if(p >= t) key = p; + } + pts = points[key]; + + if(pts.length >= 4) { + Q0 = this.slerp(pts[0],pts[1],t); + Q1 = this.slerp(pts[1],pts[2],t); + Q2 = this.slerp(pts[2],pts[3],t); + R0 = this.slerp(Q0,Q1,t); + R1 = this.slerp(Q1,Q2,t); + vR = this.slerp(R0,R1,t); + } + else + vR = this.slerp(pts[0],pts[1],t); + + return vR; + }, + + calculateEase: function(easeObj, startPoint, endPoint) { + var order = (easeObj && Object.keys(easeObj).length) ? (Object.keys(easeObj).length + 1) : 0; + var controlPoints = [startPoint], + bValues = [], + m1 = [], + m2 = [], + m3 = [], + m4 = [], + l = 0; + + var t, a; + for (var key in easeObj) { + t = parseFloat(key) / 100; + a = parseFloat(easeObj[key]) / 100; + bValues = this.getBezierValues(t, order); + bValues.shift(); + m1.push(a - bValues.pop()); + m2.push(bValues); + } + + m3 = matrixUtil.inverseN(m2, bValues.length); + + m4 = matrixUtil.multiplyN(m3, m1); + l = m4.length; + for (var i = 0; i < l; i++) { + controlPoints.push([m4[i], m4[i], m4[i], m4[i]]); + } + + controlPoints.push(endPoint); + return controlPoints; + }, + + complete: function(charc) { + charc.animating = false; + charc._prop = undefined; + }, + + lerp: function(vA, vB, t, vR) { + if (!vR) vR = []; + var i, l = vA.length; + + for (i = 0; i < l; i++) { + vR[i] = (1 - t) * vA[i] + t * vB[i]; + } + return vR; + }, + + slerp: function(qA, qB, t, qR) { + if (!qR) qR = []; + var a, + b, + theta, + dot = Vector.quantDot(qA, qB), + l = qA.length; + + dot = Math.min(Math.max(dot, -1.0), 1.0); + if (dot == 1.0) { + qR = frame.copy(qA); + return qR; + } + + theta = Math.acos(dot); + for (var i = 0; i < l; i++) { + a = (Math.sin((1 - t) * theta) / Math.sin(theta)) * qA[i]; + b = (Math.sin(t * theta) / Math.sin(theta)) * qB[i]; + qR[i] = a + b; + } + return qR; + }, + + // Old implementation of Slerp + // slerp: function (qA, qB, t, qR) { + // if (!qR) qR = []; + // var a, b, w, theta, dot = Vector.dot(qA, qB); + + // dot = Math.min(Math.max(dot, -1.0), 1.0); + // if (dot == 1.0) { + // qR = frame.copy(qA); + // return qR; + // } + + // theta = Math.acos(dot); + // w = Math.sin(t * theta) * 1 / Math.sqrt(1 - dot * dot); + // for (var i = 0; i < 4; i++) { + // a = qA[i] * (Math.cos(t * theta) - dot * w); + // b = qB[i] * w; + // qR[i] = a + b; + // } + // return qR; + // }, + + /** + * @public + * @params t: time, points: knot and control points, vR: resulting point + */ + getBezier: function(t, points, vR) { + + if (!vR) vR = []; + + var i, j, + c = points.length, + l = points[0].length, + lastIndex = (c - 1), + startPoint = points[0], + endPoint = points[lastIndex], + values = this.getBezierValues(t, lastIndex); + + for (i = 0; i < l; i++) { + vR[i] = 0; + for (j = 0; j < c; j++) { + if ((j > 0) && (j < (c - 1))) { + vR[i] = vR[i] + ((startPoint[i] + (points[j][i] * (endPoint[i] - startPoint[i]))) * values[j]); + } else { + vR[i] = vR[i] + (points[j][i] * values[j]); + } + } + } + return vR; + }, + + /** + * @private + * @params n: order, k: current position + */ + getCoeff: function(n, k) { + n = parseInt(n, 10); + k = parseInt(k, 10); + // Credits + // https://math.stackexchange.com/questions/202554/how-do-i-compute-binomial-coefficients-efficiently#answer-927064 + if (isNaN(n) || isNaN(k)) + return void 0; + if ((n < 0) || (k < 0)) + return void 0; + if (k > n) + return void 0; + if (k === 0) + return 1; + if (k === n) + return 1; + if (k > n / 2) + return this.getCoeff(n, n - k); + + return n * this.getCoeff(n - 1, k - 1) / k; + }, + + /** + * @public + * @params t: time, n: order + */ + getBezierValues: function(t, n) { + t = parseFloat(t, 10), + n = parseInt(n, 10); + + if (isNaN(t) || isNaN(n)) + return void 0; + if ((t < 0) || (n < 0)) + return void 0; + if (t > 1) + return void 0; + + var c, + values = [], + x = Math.sin((1 - t) * 180) / Math.sin(180), + y = Math.sin(t * 180) / Math.sin(180); + + // + // Binomial theorem to expand (x+y)^n + // + for (var k = 0; k <= n; k++) { + c = this.getCoeff(n, k) * Math.pow(x, (n - k)) * Math.pow(y, k); + values.push(c); + } + + return values; + } +}; diff --git a/src/AnimationSupport/Vector.js b/src/AnimationSupport/Vector.js index 8cc918b72..4937dde34 100644 --- a/src/AnimationSupport/Vector.js +++ b/src/AnimationSupport/Vector.js @@ -165,6 +165,14 @@ module.exports = { return (q1[0] * q2[0]) + (q1[1] * q2[1]) + (q1[2] * q2[2]) + (q1[3] !== undefined && q2[3] !== undefined ? (q1[3] * q2[3]) : 0); }, + /** + * Dot product of 3D vectors + * @public + */ + quantDot: function (q1, q2) { + return (q1[0] * q2[0]) + (q1[1] * q2[1]) + (q1[2] * q2[2]) + (q1[3] * q2[3]); + }, + /** * Quant Dot product of 3D vectors * @public From 7b43e1daaaa157791f26e87dd822aeb6c0d1dd28 Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Tue, 27 Oct 2015 16:29:17 -0700 Subject: [PATCH 148/195] JSDoc cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/resolution.js | 91 ++++++++++++++++++++++++++--------------------- 1 file changed, 50 insertions(+), 41 deletions(-) diff --git a/src/resolution.js b/src/resolution.js index 2c82e4a87..7d197acc7 100644 --- a/src/resolution.js +++ b/src/resolution.js @@ -26,13 +26,15 @@ var getScreenTypeObject = function (type) { */ var ri = module.exports = { /** - * Setup screen resolution scaling capabilities by defining all of the screens you're working - * with. These should be in the order of smallest to largest (according to width). Running - * this also initializes the rest of this resolution code. + * Sets up screen resolution scaling capabilities by defining an array of all the screens + * being used. These should be listed in order from smallest to largest, according to + * width. * - * In the arguments, the following properties are required: 'name', 'pxPerRem', 'width', - * 'aspectRatioName'. The property 'base' defines the primary or default resoultion that - * everything else will be based upon. + * The `name`, `pxPerRem`, `width`, and `aspectRatioName` properties are required for + * each screen type in the array. Setting `base: true` on a screen type marks it as the + * default resolution, upon which everything else will be based. + * + * Executing this method also initializes the rest of the resolution-independence code. * * ``` * ri.defineScreenTypes([ @@ -45,7 +47,8 @@ var ri = module.exports = { * ]); * ``` * - * @param {Array} types An array of objects with arguments like the example + * @param {Array} types - An array of objects containing screen configuration data, as in the + * preceding example. * @public */ defineScreenTypes: function (types) { @@ -57,12 +60,12 @@ var ri = module.exports = { }, /** - * Fetches the best-matching screen type name for the current screen size. The "best" screen type - * is determined by the screen type name that is the closest to the screen resolution without + * Fetches the name of the screen type that best matches the current screen size. The best + * match is defined as the screen type that is the closest to the screen resolution without * going over. ("The Price is Right" style.) * - * @param {Object} [rez] - Optional measurement scheme. Must have "height" and "width" properties. - * @returns {String} Screen type, like "fhd", "uhd", etc. + * @param {Object} [rez] - Optional measurement scheme. Must include `height` and `width` properties. + * @returns {String} Screen type (e.g., `'fhd'`, `'uhd'`, etc.) * @public */ getScreenType: function (rez) { @@ -135,12 +138,12 @@ var ri = module.exports = { }, /** - * Calculates the aspect ratio of the screen type provided. If none is provided the current - * screen type is used. + * Calculates the aspect ratio of the specified screen type. If no screen type is provided, + * the current screen type is used. * - * @param {String} type Screen type to get the aspect ratio of. Providing nothing uses the - * current screen type. - * @returns {Number} The calculated screen ratio (1.333, 1.777, 2.333, etc) + * @param {String} type - Screen type whose aspect ratio will be calculated. If no screen + * type is provided, the current screen type is used. + * @returns {Number} The calculated screen ratio (e.g., `1.333`, `1.777`, `2.333`, etc.) * @public */ getAspectRatio: function (type) { @@ -152,12 +155,12 @@ var ri = module.exports = { }, /** - * Returns the name of the aspect ration given the screen type or the default screen type if - * none is proided. + * Returns the name of the aspect ratio for a specified screen type, or for the default + * screen type if none is provided. * - * @param {String} type Screen type to get the aspect ratio of. Providing nothing uses the - * current screen type. - * @returns {String} The name of the type of screen ratio + * @param {String} type - Screen type whose aspect ratio name will be returned. If no + * screen type is provided, the current screen type will be used. + * @returns {String} The name of the screen type's aspect ratio * @public */ getAspectRatioName: function (type) { @@ -166,12 +169,12 @@ var ri = module.exports = { }, /** - * Takes a provided pixel value and preforms a scaling operation on the number based on the - * current screen type. + * Takes a provided pixel value and performs a scaling operation based on the current + * screen type. * - * @param {Number} px The amount of standard-resolution pixels to scale to the current screen - * resolution. - * @returns {Number} The scaled value based on the current screen scaling factor. + * @param {Number} px - The quantity of standard-resolution pixels to scale to the + * current screen resolution. + * @returns {Number} The scaled value based on the current screen scaling factor * @public */ scale: function (px) { @@ -190,29 +193,35 @@ var ri = module.exports = { */ /** - * Image src chooser. A simple utility method to select the ideal image asset from a set of - * assets, based on various screen resolutions: HD (720p), FHD (1080p), UHD (4k). When provided - * with a src argument, multiResSrc will choose the best image with respect to the current screen - * resolution. `src` may be either the traditional string, which will pass straight through, or a - * hash/object of screen types and their asset sources (keys:screen and values:src). The image - * sources will be used chosen when the screen resolution is less than or equal to the provided - * screen types. + * Selects the ideal image asset from a set of assets, based on various screen + * resolutions: HD (720p), FHD (1080p), UHD (4k). When a `src` argument is + * provided, `selectSrc()` will choose the best image with respect to the current + * screen resolution. `src` may be either the traditional string, which will pass + * straight through, or a hash/object of screen types and their asset sources + * (keys:screen and values:src). The image sources will be used when the screen + * resolution is less than or equal to the provided screen types. * * ``` - * // Take advantage of the multi-rez mode - * {kind: 'moon.Image', src: { + * // Take advantage of the multi-res mode + * var + * kind = require('enyo/kind'), + * Image = require('enyo/Image'); + * + * {kind: Image, src: { * 'hd': 'http://lorempixel.com/64/64/city/1/', * 'fhd': 'http://lorempixel.com/128/128/city/1/', * 'uhd': 'http://lorempixel.com/256/256/city/1/' - * }, alt: 'Multi-rez'}, + * }, alt: 'Multi-res'}, + * * // Standard string `src` - * {kind: 'moon.Image', src: http://lorempixel.com/128/128/city/1/', alt: 'Large'}, + * {kind: Image, src: http://lorempixel.com/128/128/city/1/', alt: 'Large'}, * ``` * - * @param {(String|moon.ri.selectSrc~src)} src A string containing a single image src or a - * key/value hash/object containing keys representing screen types (hd, fhd, uhd, etc) and - * values containing the asset src for that target screen resolution. - * @returns {String} The choosen src given the string or list provided. + * @param {(String|module:enyo/resolution#selectSrc~src)} src - A string containing + * a single image source or a key/value hash/object containing keys representing screen + * types (`'hd'`, `'fhd'`, `'uhd'`, etc.) and values containing the asset source for + * that target screen resolution. + * @returns {String} The chosen source, given the string or list provided * @public */ selectSrc: function (src) { From 9b217f1282834a96c0cae07f39f612e853d7cbf6 Mon Sep 17 00:00:00 2001 From: Ankur Mishra Date: Thu, 29 Oct 2015 16:23:40 +0530 Subject: [PATCH 149/195] add drag and modify touch events Enyo-DCO-1.1-Signed-off-by:Ankur Mishra --- src/AnimationSupport/EventDelegator.js | 33 ++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/AnimationSupport/EventDelegator.js b/src/AnimationSupport/EventDelegator.js index 36466649e..fbec79f05 100644 --- a/src/AnimationSupport/EventDelegator.js +++ b/src/AnimationSupport/EventDelegator.js @@ -33,7 +33,9 @@ var EventDelegator = { * @private */ eventArray: [ + "drag", "scroll", + "dragstart", "mousewheel", "touchstart", "touchmove", @@ -96,8 +98,8 @@ var EventDelegator = { y = ev.targetTouches[0].pageY; if(x !== 0 || y !== 0) { - sender.animDelta[0] = x - sender.touchX; - sender.animDelta[1] = y - sender.touchY; + sender.animDelta[0] = sender.touchX - x; + sender.animDelta[1] = sender.touchY - y; sender.animDelta[2] = 0; sender.touchX = x; sender.touchY = y; @@ -139,6 +141,33 @@ var EventDelegator = { this.animDelta[2] = 0; }, + /** + * @private + */ + dragstartEvent: function (inSender, inEvent) { + this.dragLeft = inEvent.offsetX, + this.dragTop = inEvent.offsetY; + }, + + /** + * @private + */ + dragEvent: function (inSender, inEvent) { + var dragLeft = inEvent.offsetX, + dragTop = inEvent.offsetY; + if (dragLeft && dragTop) { + this.deltaX = this.dragLeft - dragLeft; + this.deltaY = this.dragTop - dragTop; + + this.dragLeft = dragLeft, + this.dragTop = dragTop; + + this.animDelta[0] = this.deltaX; + this.animDelta[1] = this.deltaY; + this.animDelta[2] = 0; + } + }, + /** * @private */ From 4174f703fba4f3e4f7f2bce7134696ce3ae51209 Mon Sep 17 00:00:00 2001 From: Madala Cholan Satyanarayana Date: Fri, 30 Oct 2015 19:00:06 +0530 Subject: [PATCH 150/195] Added easing in Keyframes and code refactor Enyo-DCO-1.1-Signed-off-by: Madala Satyanarayana --- src/AnimationSupport/Easings.js | 311 +++++++++++++++++++ src/AnimationSupport/KeyFrame.js | 208 ++++++------- src/AnimationSupport/Tween.js | 107 +++---- src/AnimationSupport/Vector.js | 500 +++++++++++++++---------------- 4 files changed, 710 insertions(+), 416 deletions(-) create mode 100644 src/AnimationSupport/Easings.js diff --git a/src/AnimationSupport/Easings.js b/src/AnimationSupport/Easings.js new file mode 100644 index 000000000..10143068d --- /dev/null +++ b/src/AnimationSupport/Easings.js @@ -0,0 +1,311 @@ +/** + * Interface to achieve Easings in various animations + * + * @module enyo/AnimationSupport/Easings + * @public + */ + +var b = 0, + c = 1; + +var easings = { + + timeCheck: function(t, d) { + t = t * d; + return t; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeInQuad: function(t, d) { + t = easings.timeCheck(t, d); + return c * (t /= d) * t + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeOutQuad: function(t, d) { + t = easings.timeCheck(t, d); + return -c * (t /= d) * (t - 2) + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeInOutQuad: function(t, d) { + t = easings.timeCheck(t, d); + if ((t /= d / 2) < 1) return c / 2 * t * t + b; + return -c / 2 * ((--t) * (t - 2) - 1) + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeInCubic: function(t, d) { + t = easings.timeCheck(t, d); + return c * (t /= d) * t * t + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeOutCubic: function(t, d) { + t = easings.timeCheck(t, d); + return c * ((t = t / d - 1) * t * t + 1) + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeInOutCubic: function(t, d) { + t = easings.timeCheck(t, d); + if ((t /= d / 2) < 1) return c / 2 * t * t * t + b; + return c / 2 * ((t -= 2) * t * t + 2) + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeInQuart: function(t, d) { + t = easings.timeCheck(t, d); + return c * (t /= d) * t * t * t + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeOutQuart: function(t, d) { + t = easings.timeCheck(t, d); + return -c * ((t = t / d - 1) * t * t * t - 1) + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeInOutQuart: function(t, d) { + t = easings.timeCheck(t, d); + if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b; + return -c / 2 * ((t -= 2) * t * t * t - 2) + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeInQuint: function(t, d) { + t = easings.timeCheck(t, d); + return c * (t /= d) * t * t * t * t + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeOutQuint: function(t, d) { + t = easings.timeCheck(t, d); + return c * ((t = t / d - 1) * t * t * t * t + 1) + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeInOutQuint: function(t, d) { + t = easings.timeCheck(t, d); + if ((t /= d / 2) < 1) return c / 2 * t * t * t * t * t + b; + return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeInSine: function(t, d) { + t = easings.timeCheck(t, d); + return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeOutSine: function(t, d) { + t = easings.timeCheck(t, d); + return c * Math.sin(t / d * (Math.PI / 2)) + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeInOutSine: function(t, d) { + t = easings.timeCheck(t, d); + return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeInExpo: function(t, d) { + t = easings.timeCheck(t, d); + return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeOutExpo: function(t, d) { + t = easings.timeCheck(t, d); + return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeInOutExpo: function(t, d) { + t = easings.timeCheck(t, d); + if (t === 0) return b; + if (t === d) return b + c; + if ((t /= d / 2) < 1) return c / 2 * Math.pow(2, 10 * (t - 1)) + b; + return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeInCirc: function(t, d) { + t = easings.timeCheck(t, d); + return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeOutCirc: function(t, d) { + t = easings.timeCheck(t, d); + return c * Math.sqrt(1 - (t = t / d - 1) * t) + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeInOutCirc: function(t, d) { + t = easings.timeCheck(t, d); + if ((t /= d / 2) < 1) return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b; + return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeInElastic: function(t, d) { + var a = c, + p = 0, + s = 1.70158; + t = easings.timeCheck(t, d); + if (t === 0) return b; + if ((t /= d) === 1) return b + c; + if (!p) p = d * 0.3; + if (a < Math.abs(c)) { + a = c; + s = p / 4; + } else s = p / (2 * Math.PI) * Math.asin(c / a); + return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeOutElastic: function(t, d) { + var a = c, + p = 0, + s = 1.70158; + t = easings.timeCheck(t, d); + if (t === 0) return b; + if ((t /= d) === 1) return b + c; + if (!p) p = d * 0.3; + if (a < Math.abs(c)) { + a = c; + s = p / 4; + } else s = p / (2 * Math.PI) * Math.asin(c / a); + return a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeInOutElastic: function(t, d) { + var a = c, + p = 0, + s = 1.70158; + t = easings.timeCheck(t, d); + if (t === 0) return b; + if ((t /= d / 2) === 2) return b + c; + if (!p) p = d * (0.3 * 1.5); + if (a < Math.abs(c)) { + a = c; + s = p / 4; + } else s = p / (2 * Math.PI) * Math.asin(c / a); + if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; + return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * 0.5 + c + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeInBack: function(t, d, s) { + t = easings.timeCheck(t, d); + if (!s) s = 1.70158; + return c * (t /= d) * t * ((s + 1) * t - s) + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeOutBack: function(t, d, s) { + t = easings.timeCheck(t, d); + if (s === undefined) s = 1.70158; + return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeInOutBack: function(t, d, s) { + t = easings.timeCheck(t, d); + if (s === undefined) s = 1.70158; + if ((t /= d / 2) < 1) return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; + return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeInBounce: function(t, d) { + t = easings.timeCheck(t, d); + return c - easings.easeOutBounce((d - t) / d, d) + b; + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeOutBounce: function(t, d) { + t = easings.timeCheck(t, d); + if ((t /= d) < (1 / 2.75)) { + return c * (7.5625 * t * t) + b; + } else if (t < (2 / 2.75)) { + return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b; + } else if (t < (2.5 / 2.75)) { + return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b; + } else { + return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b; + } + }, + /** + * @public + * apply the below type of ease to the DOM element + */ + easeInOutBounce: function(t, d) { + t = easings.timeCheck(t, d); + if (t < d / 2) return easings.easeInBounce((t * 2) / d, d) * 0.5 + b; + return easings.easeOutBounce((t * 2 - d) / d, d) * 0.5 + c * 0.5 + b; + } + +}; +module.exports = easings; diff --git a/src/AnimationSupport/KeyFrame.js b/src/AnimationSupport/KeyFrame.js index e553d63be..61b904498 100644 --- a/src/AnimationSupport/KeyFrame.js +++ b/src/AnimationSupport/KeyFrame.js @@ -1,129 +1,133 @@ require('enyo'); var - kind = require('../kind'), - animation = require('./Core'), - utils = require('../utils'), - CoreObject = require('../CoreObject'); + kind = require('../kind'), + animation = require('./Core'), + utils = require('../utils'), + CoreObject = require('../CoreObject'); /** -* This module returns the Loop singleton -* @module enyo/KeyFrame -*/ + * This module returns the Loop singleton + * @module enyo/KeyFrame + */ var keyFrame = module.exports = kind.singleton({ - /** @lends module:enyo/KeyFrame */ + /** @lends module:enyo/KeyFrame */ - /** - * @private - */ - name: 'enyo.KeyFrame', - /** - * @private - */ - kind: CoreObject, + /** + * @private + */ + name: 'enyo.KeyFrame', + /** + * @private + */ + kind: CoreObject, - /** - * KeyFrame base API to perform animation on any document element - * repersented as a Character. The purpose of this method is to add a new - * character to Animation Core based on animation properties passed as - * parameter to this function and also to manage the frames allocated to - * each of individual poses. - * - * As of now this method is provided as an interface for application - * to directly trigger an animation. However, this will be later made private - * and will be accessible only by the interfaces exposed by framework. - * @parameter charc- Character responsible for animation. - * keyframe- Key frame Animation propeties represented as CSS objects. - * like: {0: {"rotateX": "0"}, 50: {"rotateX": "90"}, 100: {"rotateX": "180"}} - * @public - */ - animate: function (charc, proto) { - var prop, - cb = proto.completed, - keyframe = proto.keyFrame; + /** + * KeyFrame base API to perform animation on any document element + * repersented as a Character. The purpose of this method is to add a new + * character to Animation Core based on animation properties passed as + * parameter to this function and also to manage the frames allocated to + * each of individual poses. + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * @parameter charc- Character responsible for animation. + * keyframe- Key frame Animation propeties represented as CSS objects. + * like: {0: {"rotateX": "0"}, 50: {"rotateX": "90"}, 100: {"rotateX": "180"}} + * @public + */ + animate: function(charc, proto) { + var prop, easeInd, + cb = proto.completed, + keyframe = proto.keyFrame; + charc.keyProps = []; + charc.keyTime = []; + charc.currentIndex = 0; + for (prop in keyframe) { + charc.keyTime.push(prop); + charc.keyProps.push(keyframe[prop]); + } + charc.keyframeCallback = cb; + charc.totalDuration = proto.duration; + this.keyFraming(charc); + charc.completed = this.bindSafely(this.reframe); + //this.keyFraming(charc); + this.trigger(charc); + }, - charc.keyProps = []; - charc.keyTime = []; - charc.currentIndex = 0; - for (prop in keyframe) { - charc.keyTime.push(prop); - charc.keyProps.push(keyframe[prop]); - } - charc.keyframeCallback = cb; - charc.totalDuration = proto.duration; - charc.completed = this.bindSafely(this.reframe); - - this.keyFraming(charc); - this.trigger(charc); - }, + /** + * KeyFrame's public API to reverse an animation. + * The purpose of this method is to find the animating character based on + * the DOM provided and reversing a keyframe animation by interchanging its intial + * state with final state and final state with current state + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * @parameter dom- Document element on which animation will be reversed. + * + * @public + */ + reverse: function(dom) { + var charc = animation.exists(dom), + finalState, duration; + if (charc) { + finalState = charc._startAnim; + duration = utils.perfNow() - charc.initialTime; + animation.remove(charc); - /** - * KeyFrame's public API to reverse an animation. - * The purpose of this method is to find the animating character based on - * the DOM provided and reversing a keyframe animation by interchanging its intial - * state with final state and final state with current state - * - * As of now this method is provided as an interface for application - * to directly trigger an animation. However, this will be later made private - * and will be accessible only by the interfaces exposed by framework. - * @parameter dom- Document element on which animation will be reversed. - * - * @public - */ - reverse: function (dom) { - var charc = animation.exists(dom), - finalState, duration; - if (charc) { - finalState = charc._startAnim; - duration = utils.perfNow() - charc.initialTime; - animation.remove(charc); - - charc.setAnimation(finalState); + charc.setAnimation(finalState); charc.setInitial(charc.currentState); charc.setDuration(duration); charc.totalDuration = duration; charc.keyProps = []; - charc.keyTime = []; + charc.keyTime = []; charc.animating = false; this.trigger(charc); - } - }, + } + }, - trigger: function (charc) { - if (charc.handleAnimationEvents && typeof charc.handleAnimationEvents != 'function') { - animation.register(charc); - } else - animation.trigger(charc); - } + trigger: function(charc) { + if (charc.handleAnimationEvents && typeof charc.handleAnimationEvents != 'function') { + animation.register(charc); + } else + animation.trigger(charc); + } }); /** -* @private -*/ -keyFrame.keyFraming = function (charc) { - var index = charc.currentIndex || 0, - old = charc.keyTime[index -1] || 0, - next = charc.keyTime[index], - total = charc.totalDuration, - change = total ? total * ((next - old)/100) : "0"; + * @private + */ +keyFrame.keyFraming = function(charc) { + var index = charc.currentIndex || 0, + old = charc.keyTime[index - 1] || 0, + next = charc.keyTime[index], + total = charc.totalDuration, + change = total ? total * ((next - old) / 100) : "0"; + charc.addAnimation(charc.keyProps[index]); - charc.addAnimation(charc.keyProps[index]); - if(charc.totalDuration) charc.setDuration(change); - charc.animating = false; - charc.currentIndex = index; + // code to separate the ease component from keyframe and making it available for animation + if (charc.keyProps[index].hasOwnProperty('ease')) { + charc.ease = charc.keyProps[index].ease; + delete charc.keyProps[index].ease; + } + + if (charc.totalDuration) charc.setDuration(change); + charc.animating = false; + charc.currentIndex = index; }; - /** -* @private -*/ -keyFrame.reframe = function (charc) { - charc.reverse ? charc.currentIndex-- : charc.currentIndex++; - if (charc.currentIndex >= 0 && charc.currentIndex < charc.keyTime.length) { - this.keyFraming(charc); - charc.start(true); - } else { - //Tigerring callback function at end of animation + * @private + */ +keyFrame.reframe = function(charc) { + charc.reverse ? charc.currentIndex-- : charc.currentIndex++; + if (charc.currentIndex >= 0 && charc.currentIndex < charc.keyTime.length) { + this.keyFraming(charc); + charc.start(true); + } else { + //Tigerring callback function at end of animation charc.keyframeCallback && charc.keyframeCallback(this); } }; diff --git a/src/AnimationSupport/Tween.js b/src/AnimationSupport/Tween.js index 7db3c15ff..100ccaf21 100644 --- a/src/AnimationSupport/Tween.js +++ b/src/AnimationSupport/Tween.js @@ -23,8 +23,8 @@ module.exports = { * As of now this method is provided as an interface for application * to directly trigger an animation. However, this will be later made private * and will be accessible only by the interfaces exposed by framework. - * @parameter chrac- Animating character - * ts- DOMHighResTimeStamp + * @parameter chrac- Animating character + * ts- DOMHighResTimeStamp * * @public */ @@ -50,8 +50,8 @@ module.exports = { * As of now this method is provided as an interface for application * to directly trigger an animation. However, this will be later made private * and will be accessible only by the interfaces exposed by framework. - * @parameter chrac- Animating character - * dt- An array of delta dimensions like [x,y,z] + * @parameter chrac- Animating character + * dt- An array of delta dimensions like [x,y,z] * * @public */ @@ -110,7 +110,7 @@ module.exports = { for (k in props) { cState = frame.copy(charc.currentState[k] || []); if (charc.ease && (typeof charc.ease !== 'function')) { - if((k == 'rotate')) { + if ((k == 'rotate')) { //Without control points // pts = this.beizerSlerpPoints(charc.ease, frame.copy(oldState[k]), frame.copy(newState[k]), props[k]); // cState = this.beizerSlerp(t, pts, cState); @@ -118,6 +118,8 @@ module.exports = { //With control points process pts = this.beizerSPoints(charc.ease, frame.copy(oldState[k]), frame.copy(newState[k]), props[k]); cState = this.beizerSpline(t, pts, cState); + + } else { pts = this.calculateEase(charc.ease, frame.copy(oldState[k]), frame.copy(newState[k])); cState = this.getBezier(t, pts, cState); @@ -147,7 +149,7 @@ module.exports = { charc.animationStep && charc.animationStep(t); }, - + /** * @private */ @@ -161,8 +163,8 @@ module.exports = { splinePoints = {}, eD = frame.parseValue(endPoint), aN = startQuat; - - if(ease && Object.keys(ease).length > 0) { + + if (ease && Object.keys(ease).length > 0) { for (key in ease) { tm = parseFloat(key) / 100; ag = parseFloat(ease[key]); @@ -180,10 +182,10 @@ module.exports = { //Without control points beizerSlerp: function(t, points, vR) { var p, key; - for(p in points) { - if(p >= t) key = p; + for (p in points) { + if (p >= t) key = p; } - vR = this.slerp(points[key][0] , points[key][1], t); + vR = this.slerp(points[key][0], points[key][1], t); return vR; }, @@ -196,7 +198,7 @@ module.exports = { var t, a, q, n, _a, aI, bN, eD = frame.parseValue(endPoint); - if(ease && Object.keys(ease).length > 0) { + if (ease && Object.keys(ease).length > 0) { for (var key in ease) { t = parseFloat(key) / 100; a = parseFloat(ease[key]); @@ -206,52 +208,49 @@ module.exports = { quats.push(q); time.push(t); } - quats.push(endQuat); time.push(1); - n = quats.length -1; + n = quats.length - 1; for (var i = 0, j = 1; i < n; i++, j++) { - if(i === 0) { + if (i === 0) { aI = this.slerp(quats[0], this.slerp(quats[2], quats[1], 2.0), 1.0 / 3); } else { - _a = this.slerp(this.slerp(quats[i-1], quats[i], 2.0), quats[i+1], 0.5); - aI = this.slerp(quats[j] , _a , 1.0 / 3); + _a = this.slerp(this.slerp(quats[i - 1], quats[i], 2.0), quats[i + 1], 0.5); + aI = this.slerp(quats[j], _a, 1.0 / 3); } - if(j === n) { - bN = this.slerp(quats[j], this.slerp(quats[j-2], quats[j-1], 2.0), 1.0 / 3); + if (j === n) { + bN = this.slerp(quats[j], this.slerp(quats[j - 2], quats[j - 1], 2.0), 1.0 / 3); } else { - _a = this.slerp(this.slerp(quats[j-1], quats[j], 2.0), quats[j+1], 0.5); - bN = this.slerp(quats[j] , _a , -1.0 / 3); + _a = this.slerp(this.slerp(quats[j - 1], quats[j], 2.0), quats[j + 1], 0.5); + bN = this.slerp(quats[j], _a, -1.0 / 3); } - splinePoints[time[j]] = [quats[i], aI, bN, quats[i+1]]; + splinePoints[time[j]] = [quats[i], aI, bN, quats[i + 1]]; } } return splinePoints; }, //With control points - beizerSpline: function (t, points, vR) { - if(!vR) vR = []; + beizerSpline: function(t, points, vR) { + if (!vR) vR = []; var Q0, Q1, Q2, R0, R1; var p, key, pts; - for(p in points) { - if(p >= t) key = p; + for (p in points) { + if (p >= t) key = p; } pts = points[key]; - if(pts.length >= 4) { - Q0 = this.slerp(pts[0],pts[1],t); - Q1 = this.slerp(pts[1],pts[2],t); - Q2 = this.slerp(pts[2],pts[3],t); - R0 = this.slerp(Q0,Q1,t); - R1 = this.slerp(Q1,Q2,t); - vR = this.slerp(R0,R1,t); - } - else - vR = this.slerp(pts[0],pts[1],t); - + if (pts.length >= 4) { + Q0 = this.slerp(pts[0], pts[1], t); + Q1 = this.slerp(pts[1], pts[2], t); + Q2 = this.slerp(pts[2], pts[3], t); + R0 = this.slerp(Q0, Q1, t); + R1 = this.slerp(Q1, Q2, t); + vR = this.slerp(R0, R1, t); + } else + vR = this.slerp(pts[0], pts[1], t); return vR; }, @@ -280,7 +279,7 @@ module.exports = { m4 = matrixUtil.multiplyN(m3, m1); l = m4.length; for (var i = 0; i < l; i++) { - controlPoints.push([m4[i], m4[i], m4[i], m4[i]]); + controlPoints.push([m4[i], m4[i], m4[i]]); } controlPoints.push(endPoint); @@ -314,8 +313,12 @@ module.exports = { if (dot == 1.0) { qR = frame.copy(qA); return qR; + } else if (dot < 0) { + qB[0] = -qB[0]; + qB[1] = -qB[1]; + qB[2] = -qB[2]; + qB[3] = -qB[3]; } - theta = Math.acos(dot); for (var i = 0; i < l; i++) { a = (Math.sin((1 - t) * theta) / Math.sin(theta)) * qA[i]; @@ -325,27 +328,6 @@ module.exports = { return qR; }, - // Old implementation of Slerp - // slerp: function (qA, qB, t, qR) { - // if (!qR) qR = []; - // var a, b, w, theta, dot = Vector.dot(qA, qB); - - // dot = Math.min(Math.max(dot, -1.0), 1.0); - // if (dot == 1.0) { - // qR = frame.copy(qA); - // return qR; - // } - - // theta = Math.acos(dot); - // w = Math.sin(t * theta) * 1 / Math.sqrt(1 - dot * dot); - // for (var i = 0; i < 4; i++) { - // a = qA[i] * (Math.cos(t * theta) - dot * w); - // b = qB[i] * w; - // qR[i] = a + b; - // } - // return qR; - // }, - /** * @public * @params t: time, points: knot and control points, vR: resulting point @@ -417,9 +399,8 @@ module.exports = { var c, values = [], - x = Math.sin((1 - t) * 180) / Math.sin(180), - y = Math.sin(t * 180) / Math.sin(180); - + x = (1 - t), + y = t; // // Binomial theorem to expand (x+y)^n // diff --git a/src/AnimationSupport/Vector.js b/src/AnimationSupport/Vector.js index 4937dde34..a1094d950 100644 --- a/src/AnimationSupport/Vector.js +++ b/src/AnimationSupport/Vector.js @@ -1,255 +1,253 @@ require('enyo'); /** -* Vector module for vector related calculations. -* Also provides API's for Quaternion vectors for rotation. -* -* @module enyo/AnimationSupport/Vector -*/ + * Vector module for vector related calculations. + * Also provides API's for Quaternion vectors for rotation. + * + * @module enyo/AnimationSupport/Vector + */ module.exports = { - /** - * Divides vector with a scalar value. - * @public - */ - divide: function (v, s) { - return [v[0] / s, v[1] / s, v[2] / s]; - }, - - /** - * Add vector/quant with a vector/quant. - * @public - */ - add: function (q1, q2) { - q1[0] += q2[0]; - q1[1] += q2[1]; - q1[2] += q2[2]; - if (q1.length > 3 && q2.length > 3) q1[3] += q2[3]; - return q1; - }, - - /** - * Sub vector/quant with a vector/quant. - * @public - */ - subtract: function (q1, q2) { - q1[0] -= q2[0]; - q1[1] -= q2[1]; - q1[2] -= q2[2]; - if (q1.length > 3 && q2.length > 3) q1[3] -= q2[3]; - return q1; - }, - - /** - * Multiply vector/quant with a vector/quant. - * @public - */ - multiply: function (q, s) { - q[0] *= s; - q[1] *= s; - q[2] *= s; - if (q.length > 3) q[3] *= s; - return q; - }, - - /** - * Limits the vector/quant between a maximum and minimum value. - * @public - */ - range: function (q, max, min) { - for (var i = 0; i < q.length; i++) { - q[i] = q[i] < min ? Math.max(q[i], min) : q[i] > max ? Math.min(q[i], max) : q[i]; - } - }, - - /** - * Compares each vector/qunat with a scalar value to check if its equal. - * Returns true when all the vector/quant values are equal to this scalar value. - * @public - */ - equalS: function(q1, s) { - return (q1.length > 0) && q1.every(function(e, i) { - return e === (s || 0); - }); - }, - - /** - * Compares each vector/qunat with a scalar value to check if its greater. - * Returns true when all the vector/quant values are greater or equal to this scalar value. - * @public - */ - greaterS: function(q1, s) { - return (q1.length > 0) && q1.every(function(e, i) { - return e >= (s || 0); - }); - }, - - /** - * Compares each vector/qunat with a scalar value to check if its lesser. - * Returns true when all the vector/quant values are lesser or equal to this scalar value. - * @public - */ - lesserS: function(q1, s) { - return (q1.length > 0) && q1.every(function(e, i) { - return e < (s || 0); - }); - }, - - /** - * Evaluates the gap between two vector values. - * Returns the absolute distance between two vectors. - * @public - */ - distance: function(q1, q2, d) { - d = Math.sqrt( - (q1[0] - q2[0]) * (q1[0] - q2[0]) + - (q1[1] - q2[1]) * (q1[1] - q2[1]) + - (q1[2] - q2[2]) * (q1[2] - q2[2]) - ); - return (d < 0.01 && d > -0.01) ? 0 : d; - }, - - /** - * Evaluates the gap between two quanterions values - * Returns the absolute distance between two quanterions. - * @public - */ - quantDistance: function (q1, q2, d) { - d = Math.sqrt( - (q1[0] - q2[0]) * (q1[0] - q2[0]) + - (q1[1] - q2[1]) * (q1[1] - q2[1]) + - (q1[2] - q2[2]) * (q1[2] - q2[2]) + - (q1[3] - q2[3]) * (q1[3] - q2[3]) - ); - return (d < 0.0001 && d > -0.0001) ? 0 : d; - }, - - /** - * Gives the direction of motion from one vector to other. - * Returns true if moving towards positive direction. - * @public - */ - direction: function (q1, q2) { - return (q1[0] - q2[0]) < 0 || (q1[1] - q2[1]) < 0 || (q1[2] - q2[2]) < 0; - }, - - /** - * Retunns an inverse of a quanterion. - * @public - */ - quantInverse: function (q) { - // var d = (q[0] * q[0]) + (q[1] * q[1]) + (q[2] * q[2]) + (q[3] * q[3]); - return [-q[0], -q[1], -q[2], q[3]]; - }, - - /** - * Length of 3D vectors - * @public - */ - sumS: function (q, s) { - return q[0] * s + q[1] * s + q[2] * s + q[3] !== undefined ? q[3] * s : 0; - }, - - /** - * Length of 3D vectors - * @public - */ - len: function (q) { - return Math.sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2]); - }, - - /** - * Dot product of 3D vectors - * @public - */ - dot: function (q1, q2) { - return (q1[0] * q2[0]) + (q1[1] * q2[1]) + (q1[2] * q2[2]) + (q1[3] !== undefined && q2[3] !== undefined ? (q1[3] * q2[3]) : 0); - }, - - /** - * Dot product of 3D vectors - * @public - */ - quantDot: function (q1, q2) { - return (q1[0] * q2[0]) + (q1[1] * q2[1]) + (q1[2] * q2[2]) + (q1[3] * q2[3]); - }, - - /** - * Quant Dot product of 3D vectors - * @public - */ - quantCross: function (q1, q2) { - return [ - q1[3] * q2[0] + q1[0] * q2[3] + q1[1] * q2[2] - q1[2] * q2[1], - q1[3] * q2[1] - q1[0] * q2[2] + q1[1] * q2[3] + q1[2] * q2[0], - q1[3] * q2[2] + q1[0] * q2[1] - q1[1] * q2[0] + q1[2] * q2[3], - q1[3] * q2[3] - q1[0] * q2[0] - q1[1] * q2[1] - q1[2] * q2[2] - ]; - }, - - /** - * Cross product of two vectors - * @public - */ - cross: function (q1, q2) { - return [ - q1[1] * q2[2] - q1[2] * q2[1], - q1[2] * q2[0] - q1[0] * q2[2], - q1[0] * q2[1] - q1[1] * q2[0] - ]; - }, - - /** - * Normalizing a vector is obtaining another unit vector in the same direction. - * To normalize a vector, divide the vector by its magnitude. - * @public - */ - normalize: function (q) { - return this.divide(q, this.len(q)); - }, - - /** - * Combine scalar values with two vectors. - * Required during parsing scaler values matrix. - * @public - */ - combine: function (a, b, ascl, bscl) { - return [ - (ascl * a[0]) + (bscl * b[0]), - (ascl * a[1]) + (bscl * b[1]), - (ascl * a[2]) + (bscl * b[2]) - ]; - }, - - /** - * Converts a quaternion vector to a rotation vector. - * @public - */ - toVector: function (rv) { - var r = 2 * Math.acos(rv[3]); - var sA = Math.sqrt(1.0 - rv[3] * rv[3]); - if (Math.abs(sA) < 0.0005) sA = 1; - return [rv[0] / sA, rv[1] / sA, rv[2] / sA, r * 180 / Math.PI]; - }, - - /** - * Converts a rotation vector to a quaternion vector. - * @public - */ - toQuant: function (q) { - if (!q) q = []; - - var x = q[0] || 0, - y = q[1] || 0, - z = q[2] || 0, - deg = q[3] || 0, - r = deg * (Math.PI / 360), - sR = Math.sin(r), - cR = Math.cos(r); - - q[0] = x * sR; - q[1] = y * sR; - q[2] = z * sR; - q[3] = cR; - return q; - } -}; \ No newline at end of file + /** + * Divides vector with a scalar value. + * @public + */ + divide: function(v, s) { + return [v[0] / s, v[1] / s, v[2] / s]; + }, + + /** + * Add vector/quant with a vector/quant. + * @public + */ + add: function(q1, q2) { + q1[0] += q2[0]; + q1[1] += q2[1]; + q1[2] += q2[2]; + if (q1.length > 3 && q2.length > 3) q1[3] += q2[3]; + return q1; + }, + + /** + * Sub vector/quant with a vector/quant. + * @public + */ + subtract: function(q1, q2) { + q1[0] -= q2[0]; + q1[1] -= q2[1]; + q1[2] -= q2[2]; + if (q1.length > 3 && q2.length > 3) q1[3] -= q2[3]; + return q1; + }, + + /** + * Multiply vector/quant with a vector/quant. + * @public + */ + multiply: function(q, s) { + q[0] *= s; + q[1] *= s; + q[2] *= s; + if (q.length > 3) q[3] *= s; + return q; + }, + + /** + * Limits the vector/quant between a maximum and minimum value. + * @public + */ + range: function(q, max, min) { + for (var i = 0; i < q.length; i++) { + q[i] = q[i] < min ? Math.max(q[i], min) : q[i] > max ? Math.min(q[i], max) : q[i]; + } + }, + + /** + * Compares each vector/qunat with a scalar value to check if its equal. + * Returns true when all the vector/quant values are equal to this scalar value. + * @public + */ + equalS: function(q1, s) { + return (q1.length > 0) && q1.every(function(e, i) { + return e === (s || 0); + }); + }, + + /** + * Compares each vector/qunat with a scalar value to check if its greater. + * Returns true when all the vector/quant values are greater or equal to this scalar value. + * @public + */ + greaterS: function(q1, s) { + return (q1.length > 0) && q1.every(function(e, i) { + return e >= (s || 0); + }); + }, + + /** + * Compares each vector/qunat with a scalar value to check if its lesser. + * Returns true when all the vector/quant values are lesser or equal to this scalar value. + * @public + */ + lesserS: function(q1, s) { + return (q1.length > 0) && q1.every(function(e, i) { + return e < (s || 0); + }); + }, + + /** + * Evaluates the gap between two vector values. + * Returns the absolute distance between two vectors. + * @public + */ + distance: function(q1, q2, d) { + d = Math.sqrt( + (q1[0] - q2[0]) * (q1[0] - q2[0]) + + (q1[1] - q2[1]) * (q1[1] - q2[1]) + + (q1[2] - q2[2]) * (q1[2] - q2[2]) + ); + return (d < 0.01 && d > -0.01) ? 0 : d; + }, + + /** + * Evaluates the gap between two quanterions values + * Returns the absolute distance between two quanterions. + * @public + */ + quantDistance: function(q1, q2, d) { + d = Math.sqrt( + (q1[0] - q2[0]) * (q1[0] - q2[0]) + + (q1[1] - q2[1]) * (q1[1] - q2[1]) + + (q1[2] - q2[2]) * (q1[2] - q2[2]) + + (q1[3] - q2[3]) * (q1[3] - q2[3]) + ); + return (d < 0.0001 && d > -0.0001) ? 0 : d; + }, + + /** + * Gives the direction of motion from one vector to other. + * Returns true if moving towards positive direction. + * @public + */ + direction: function(q1, q2) { + return (q1[0] - q2[0]) < 0 || (q1[1] - q2[1]) < 0 || (q1[2] - q2[2]) < 0; + }, + + /** + * Retunns an inverse of a quanterion. + * @public + */ + quantInverse: function(q) { + // var d = (q[0] * q[0]) + (q[1] * q[1]) + (q[2] * q[2]) + (q[3] * q[3]); + return [-q[0], -q[1], -q[2], q[3]]; + }, + + /** + * Length of 3D vectors + * @public + */ + sumS: function(q, s) { + return q[0] * s + q[1] * s + q[2] * s + q[3] !== undefined ? q[3] * s : 0; + }, + + /** + * Length of 3D vectors + * @public + */ + len: function(q) { + return Math.sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2]); + }, + + /** + * Dot product of 3D vectors + * @public + */ + dot: function(q1, q2) { + return (q1[0] * q2[0]) + (q1[1] * q2[1]) + (q1[2] * q2[2]) + (q1[3] !== undefined && q2[3] !== undefined ? (q1[3] * q2[3]) : 0); + }, + + /** + * Dot product of 3D vectors + * @public + */ + quantDot: function(q1, q2) { + return (q1[0] * q2[0]) + (q1[1] * q2[1]) + (q1[2] * q2[2]) + (q1[3] * q2[3]); + }, + + /** + * Quant Dot product of 3D vectors + * @public + */ + quantCross: function(q1, q2) { + return [ + q1[3] * q2[0] + q1[0] * q2[3] + q1[1] * q2[2] - q1[2] * q2[1], + q1[3] * q2[1] - q1[0] * q2[2] + q1[1] * q2[3] + q1[2] * q2[0], + q1[3] * q2[2] + q1[0] * q2[1] - q1[1] * q2[0] + q1[2] * q2[3], + q1[3] * q2[3] - q1[0] * q2[0] - q1[1] * q2[1] - q1[2] * q2[2] + ]; + }, + + /** + * Cross product of two vectors + * @public + */ + cross: function(q1, q2) { + return [ + q1[1] * q2[2] - q1[2] * q2[1], + q1[2] * q2[0] - q1[0] * q2[2], + q1[0] * q2[1] - q1[1] * q2[0] + ]; + }, + + /** + * Normalizing a vector is obtaining another unit vector in the same direction. + * To normalize a vector, divide the vector by its magnitude. + * @public + */ + normalize: function(q) { + return this.divide(q, this.len(q)); + }, + + /** + * Combine scalar values with two vectors. + * Required during parsing scaler values matrix. + * @public + */ + combine: function(a, b, ascl, bscl) { + return [ + (ascl * a[0]) + (bscl * b[0]), (ascl * a[1]) + (bscl * b[1]), (ascl * a[2]) + (bscl * b[2]) + ]; + }, + + /** + * Converts a quaternion vector to a rotation vector. + * @public + */ + toVector: function(rv) { + var r = 2 * Math.acos(rv[3]); + var sA = Math.sqrt(1.0 - rv[3] * rv[3]); + if (Math.abs(sA) < 0.0005) sA = 1; + return [rv[0] / sA, rv[1] / sA, rv[2] / sA, r * 180 / Math.PI]; + }, + + /** + * Converts a rotation vector to a quaternion vector. + * @public + */ + toQuant: function(q) { + if (!q) q = []; + + var x = q[0] || 0, + y = q[1] || 0, + z = q[2] || 0, + deg = q[3] || 0, + r = deg * (Math.PI / 360), + sR = Math.sin(r), + cR = Math.cos(r); + + q[0] = x * sR; + q[1] = y * sR; + q[2] = z * sR; + q[3] = cR; + return q; + } +}; From 108b1771957d5d5db48b4ce2428856eb2a2ade79 Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Mon, 2 Nov 2015 14:15:35 -0800 Subject: [PATCH 151/195] JSDoc cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.js b/src/utils.js index 3eb6980c6..c4204fc9d 100644 --- a/src/utils.js +++ b/src/utils.js @@ -103,7 +103,7 @@ exports.nar = []; /** * This name is reported in inspectors as the type of objects created via delegate; -* otherwise, we would just use {@link module:enyo/nop~nop}. +* otherwise, we would just use {@link module:enyo/utils#nop}. * * @private */ From 872e49a86bb82d7ac7dbfacbc84bae32b61cdad9 Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Mon, 2 Nov 2015 15:56:21 -0800 Subject: [PATCH 152/195] ENYO-2768: Set visibility to a valid property value. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/VirtualDataRepeater.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VirtualDataRepeater.js b/src/VirtualDataRepeater.js index bdf4b26c7..654dedd56 100644 --- a/src/VirtualDataRepeater.js +++ b/src/VirtualDataRepeater.js @@ -33,7 +33,7 @@ module.exports = kind({ if (this._needsReset) { // Now that we've done our deferred reset, we // can become visible again - this.applyStyle('visibility', 'showing'); + this.applyStyle('visibility', 'visible'); this._needsReset = false; } } @@ -289,7 +289,7 @@ module.exports = kind({ fwd: function() { this.set('first', this.first + 1); }, - + bak: function() { this.set('first', this.first - 1); }, From c2e69068a69daca9113cedee3cdcc8329eb711ad Mon Sep 17 00:00:00 2001 From: Ryan Duffy Date: Tue, 3 Nov 2015 12:39:10 -0600 Subject: [PATCH 153/195] add history to the top of the queue when added during processing Issue: ENYO-2766 Enyo-DCO-1.1-Signed-off-by: Ryan Duffy (ryan.duffy@lge.com) --- src/History.js | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/History.js b/src/History.js index 5dc9b0d5f..71fb02687 100644 --- a/src/History.js +++ b/src/History.js @@ -275,6 +275,8 @@ var EnyoHistory = module.exports = kind.singleton( if (silence) { silence -= 1; next.silenced = true; + } else { + next.silenced = false; } } else { silence += next.count; @@ -305,7 +307,7 @@ var EnyoHistory = module.exports = kind.singleton( */ enqueuePop: function (type, count) { count = count || 1; - _queue.push({type: type, count: count}); + this.addToQueue({type: type, count: count}); // if we've only queued pop/drop events, we need to increment the number of entries to go // back. once a push is queued, the history must be managed in processState. if (!_pushQueued) { @@ -338,7 +340,24 @@ var EnyoHistory = module.exports = kind.singleton( */ enqueuePush: function (entry) { _pushQueued = true; - _queue.push({type: 'push', entry: entry}); + this.addToQueue({type: 'push', entry: entry}); + }, + + /** + * When entries are added while processing the queue, the new entries should be added at the top + * of the queue rather than the end because the queue is processed FIFO and the assumption of + * adding them mid-process is that they are being added at the point in the queue processing in + * which they are called. + * + * @private + */ + addToQueue: function (entry) { + if (_processing) { + _queue.unshift(entry); + this.silencePushEntries(); + } else { + _queue.push(entry); + } }, /** From 8120d758f02fee0beda2efd092044e1aebf5d6b6 Mon Sep 17 00:00:00 2001 From: Ryan Duffy Date: Tue, 3 Nov 2015 13:33:35 -0600 Subject: [PATCH 154/195] set placeholder bg image at create-time When not using sizing, the placeholder would not be set because a change notification doesn't fire for it during create. Adding a check for a falsey prop so placeholder will be set during create. Issue: ENYO-2734 Enyo-DCO-1.1-Signed-off-by: Ryan Duffy (ryan.duffy@lge.com) --- src/Image/Image.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Image/Image.js b/src/Image/Image.js index 7105482d7..85d1bf03a 100644 --- a/src/Image/Image.js +++ b/src/Image/Image.js @@ -295,11 +295,11 @@ module.exports = kind( // use either both urls, src, placeholder, or 'none', in that order url = srcUrl && plUrl && (srcUrl + ',' + plUrl) || srcUrl || plUrl || 'none'; this.applyStyle('background-image', url); - } - else if (prop === 'placeholder') { - this.applyStyle('background-image', plUrl); - } - else { + } else { + // when update source + if (!prop || prop == 'placeholder') { + this.applyStyle('background-image', plUrl); + } this.setAttribute('src', src); } }, From ab41ac342b772b9a57199b474aebd3c04de08d40 Mon Sep 17 00:00:00 2001 From: Gray Norton Date: Tue, 3 Nov 2015 12:04:36 -0800 Subject: [PATCH 155/195] ENYO-2744: Tweaks to allow restoration of list state on render We have recently started getting more aggressive about view virtualization for performance reasons. One tactic in this area involves tearing down the DOM representation of a Control and then restoring it later as needed ("view caching"). In the case where the view being cached / restored contains a DataList, the state of the view was not being preserved as expected because DataList effectively resets upon being (re-)rendered. The proper fix for this issue would involve rethinking the DataList lifecycle with view-virtualization scenarios in mind, but this would be a significant effort and the current DataList is on a relatively short road to replacement, so for now we'll go with a lighter-weight, Moonstone-specific solution. That solution does, however, require a number of minor tweaks and enhancements in Enyo: * A minor change to the delegation interface between enyo/Control and enyo/HTMLStringDelegate, allowing `teardownChildren()` to be more easily extended or overridden. * A new `callback` argument for `enyo/DataList.scrollToIndex()`, allowing for actions to be performed after this potentially asynchronous operation is completed. * A change in `enyo/ViewPreloadSupport.cacheView()` to defer removing the view's DOM node until after its children have been torn down. This enables views to hook `teardownChildren()` and do things in preparation for being torn down that take current DOM state into account. * To support the above change in enyo/ViewPreloadSupport, a minor refactoring of some cross-platform node-removal logic, moving it from enyo/Control to enyo/dom. Enyo-DCO-1.1-Signed-Off-By: Gray Norton (gray.norton@lge.com) --- src/Control/Control.js | 3 +-- src/DataList/DataList.js | 12 +++++++++--- src/HTMLStringDelegate.js | 4 ++-- src/VerticalDelegate.js | 9 +++++++-- src/ViewPreloadSupport.js | 8 +++++--- src/dom.js | 11 +++++++++++ 6 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/Control/Control.js b/src/Control/Control.js index 5324d610f..c5c991ec8 100644 --- a/src/Control/Control.js +++ b/src/Control/Control.js @@ -1199,8 +1199,7 @@ var Control = module.exports = kind( removeNodeFromDom: function() { var node = this.hasNode(); if (node) { - if (node.remove) node.remove(); - else if (node.parentNode) node.parentNode.removeChild(node); + Dom.removeNode(node); } }, diff --git a/src/DataList/DataList.js b/src/DataList/DataList.js index 9d3bcf110..38cff237f 100644 --- a/src/DataList/DataList.js +++ b/src/DataList/DataList.js @@ -224,18 +224,24 @@ var DataList = module.exports = kind( * [collection]{@link module:enyo/DataRepeater~DataRepeater#data} to scroll to the position of that * index in the list. * + * It is important to note that this scrolling operation is not guaranteed to be synchronous. If + * you need to perform another action upon the completion of scrolling, you should pass a callback + * function as a second argument to this method. + * * @param {Number} idx - The index in the [list's]{@link module:enyo/DataList~DataList} * [collection]{@link module:enyo/DataRepeater~DataRepeater#data} to scroll to. + * @param {Function} callback - A function to be executed after the scroll operation is + * complete. * @public */ - scrollToIndex: function (idx) { + scrollToIndex: function (idx, callback) { var len = this.collection? this.collection.length: 0; if (idx >= 0 && idx < len) { if (this.get('absoluteShowing')) { - this.delegate.scrollToIndex(this, idx); + this.delegate.scrollToIndex(this, idx, callback); } else { this._addToShowingQueue('scrollToIndex', function () { - this.delegate.scrollToIndex(this, idx); + this.delegate.scrollToIndex(this, idx, callback); }); } } diff --git a/src/HTMLStringDelegate.js b/src/HTMLStringDelegate.js index 3f79cdc7d..49cbdc063 100644 --- a/src/HTMLStringDelegate.js +++ b/src/HTMLStringDelegate.js @@ -120,7 +120,7 @@ module.exports = { * @private */ renderContent: function (control) { - if (control.generated) this.teardownChildren(control); + if (control.generated) control.teardownChildren(); if (control.hasNode()) control.node.innerHTML = this.generateInnerHtml(control); }, @@ -244,7 +244,7 @@ module.exports = { * @private */ teardownRender: function (control, cache) { - if (control.generated) this.teardownChildren(control, cache); + if (control.generated) control.teardownChildren(cache); control.node = null; control.set('generated', false); }, diff --git a/src/VerticalDelegate.js b/src/VerticalDelegate.js index 089329261..d50fd09ce 100644 --- a/src/VerticalDelegate.js +++ b/src/VerticalDelegate.js @@ -338,9 +338,11 @@ module.exports = { * @method * @param {module:enyo/DataList~DataList} list - The [list]{@link module:enyo/DataList~DataList} to perform this action on. * @param {Number} i - The index to scroll to. + * @param {Function} callback - A function to be executed after the scroll operation is + * complete. * @private */ - scrollToIndex: function (list, i) { + scrollToIndex: function (list, i, callback) { // first see if the child is already available to scroll to var c = this.childForIndex(list, i), // but we also need the page so we can find its position @@ -353,12 +355,15 @@ module.exports = { list.$.scroller.stop(); if (c) { this.scrollToControl(list, c); + if (typeof callback === 'function') { + callback(); + } } else { // we do this to ensure we trigger the paging event when necessary this.resetToPosition(list, this.pagePosition(list, p)); // now retry the original logic until we have this right list.startJob('vertical_delegate_scrollToIndex', function () { - list.scrollToIndex(i); + list.scrollToIndex(i, callback); }); } }, diff --git a/src/ViewPreloadSupport.js b/src/ViewPreloadSupport.js index bc4baae3f..4e0d0059f 100644 --- a/src/ViewPreloadSupport.js +++ b/src/ViewPreloadSupport.js @@ -3,6 +3,7 @@ * @module enyo/ViewPreloadSupport */ var + dom = require('./dom'), kind = require('./kind'), utils = require('./utils'); @@ -111,13 +112,14 @@ var ViewPreloadSupport = { * @public */ cacheView: function (view, preserve) { - var id = this.getViewId(view); + var id = this.getViewId(view), + node = view.hasNode(); // The panel could have already been removed from DOM and torn down if we popped when // moving forward. - if (view.hasNode()) { - view.removeNodeFromDom(); + if (node) { view.teardownRender(true); + dom.removeNode(node); } if (!preserve) { diff --git a/src/dom.js b/src/dom.js index 34085da15..baf213a66 100644 --- a/src/dom.js +++ b/src/dom.js @@ -410,6 +410,17 @@ var dom = module.exports = { }; }, + /** + * Removes a node from the DOM. + * + * @param {Node} node - The [node]{@glossary Node} to remove. + * @public + */ + removeNode: function (node) { + if (node.remove) node.remove(); + else if (node.parentNode) node.parentNode.removeChild(node); + }, + /** * Sets the `innerHTML` property of the specified `node` to `html`. * From d3f221dabe39ef07746296fa1f5e0fefe077b965 Mon Sep 17 00:00:00 2001 From: Ryan Duffy Date: Tue, 3 Nov 2015 13:19:23 -0600 Subject: [PATCH 156/195] fix enyo/History.clear() to keep browser history in sync A prior change updated clear() to only reset the internal history so this change corrects clear() to both reset the internal history and keep the browser history in sync. Issue: ENYO-2699 Enyo-DCO-1.1-Signed-off-by: Ryan Duffy (ryan.duffy@lge.com) --- src/History.js | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/History.js b/src/History.js index 71fb02687..eaaeb9890 100644 --- a/src/History.js +++ b/src/History.js @@ -59,6 +59,10 @@ var // Track if we're in the midst of handling a pop _processing = false, + // If history were disabled or clear called during processing, we can't continue to process the + // queue which will need to resume after the clear processes. + _abortQueueProcessing = false, + // `true` if the platform support the HTML5 History API _supports = !!global.history.pushState; @@ -102,7 +106,7 @@ var EnyoHistory = module.exports = kind.singleton( */ enabledChanged: function () { // reset private members - this.clear(); + if (!this.enabled) this.clear(); }, /** @@ -177,12 +181,15 @@ var EnyoHistory = module.exports = kind.singleton( * @public */ clear: function () { - _queue.splice(0, _queue.length); - _history.splice(0, _history.length); + var ql = _queue.length, + hl = _history.length; + _popQueueCount = 0; _pushQueued = false; + _abortQueueProcessing = _processing; _processing = false; - this.stopJob('history.go'); + if (ql) _queue.splice(0, ql); + if (hl) this.drop(hl); }, /** @@ -236,7 +243,7 @@ var EnyoHistory = module.exports = kind.singleton( this.silencePushEntries(); - while (_queue.length) { + while (_queue.length && !_abortQueueProcessing) { next = _queue.shift(); if (next.type === 'push') { @@ -255,8 +262,12 @@ var EnyoHistory = module.exports = kind.singleton( // otherwise we just drop the entries and do nothing } } - _popQueueCount = 0; - _pushQueued = false; + if (_abortQueueProcessing) { + _abortQueueProcessing = false; + } else { + _popQueueCount = 0; + _pushQueued = false; + } }, /** From 597e26256ed317afa28b7bb7ad463d793a320a1f Mon Sep 17 00:00:00 2001 From: Roy Sutton Date: Wed, 4 Nov 2015 14:13:28 -0800 Subject: [PATCH 157/195] Fix case of Enyo-DCO-1.1-Signed-off-by: Roy Sutton --- src/Control/Control.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Control/Control.js b/src/Control/Control.js index c5c991ec8..7c3f0848d 100644 --- a/src/Control/Control.js +++ b/src/Control/Control.js @@ -410,7 +410,7 @@ var Control = module.exports = kind( * yet to be cached, from the [node]{@glossary Node} itself. * * @param {String} name - The attribute name to get. - * @returns {(String|Null)} The value of the requested attribute, or `null` + * @returns {(String|null)} The value of the requested attribute, or `null` * if there isn't a [DOM node]{@glossary Node} yet. * @public */ @@ -1728,4 +1728,4 @@ Control.floatingLayer = new Control.FloatingLayer({id: 'floatingLayer'}); * @static * @public */ -Control.Fullscreen = fullscreen(Control); \ No newline at end of file +Control.Fullscreen = fullscreen(Control); From ddcf85cbdeb008ab0c9dc7ccc89fb689dd8adfac Mon Sep 17 00:00:00 2001 From: Aaron Tam Date: Wed, 4 Nov 2015 14:52:10 -0800 Subject: [PATCH 158/195] ENYO-2780: Disable user input monitoring, by default. Enyo-DCO-1.1-Signed-off-by: Aaron Tam --- src/SystemMonitor.js | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/SystemMonitor.js b/src/SystemMonitor.js index 7324621dd..7e5564f09 100644 --- a/src/SystemMonitor.js +++ b/src/SystemMonitor.js @@ -71,6 +71,16 @@ module.exports = kind.singleton( */ active: false, + /** + * When `true`, user inputs (mouse movement, keypresses) are considered to determine system idle + * status. + * + * @type {Boolean} + * @default false + * @private + */ + monitorUserInput: false, + /** * @method * @private @@ -88,10 +98,10 @@ module.exports = kind.singleton( f = ((c - p) < d) ? f + 1 : 0; } - if (f == this.frameThreshold && this.idle()) { + if (f == this.frameThreshold && (!this.monitorUserInput || this.idle())) { this.active = false; this.emit('idle'); - if (this.listeners('idle').length === 0) { + if (this.monitorUserInput && this.listeners('idle').length === 0) { var idx = dispatcher.features.indexOf(this._checkEvent); if (idx > -1) dispatcher.features.splice(idx, 1); this._checkEvent = null; @@ -120,10 +130,12 @@ module.exports = kind.singleton( * @public */ start: function () { - if (!this.lastActive) this.lastActive = perfNow(); // setting initial value for lastActive - if (!this._checkEvent) { - this._checkEvent = this.bindSafely(this.checkEvent); - dispatcher.features.push(this._checkEvent); + if (this.monitorUserInput) { + if (!this.lastActive) this.lastActive = perfNow(); // setting initial value for lastActive + if (!this._checkEvent) { + this._checkEvent = this.bindSafely(this.checkEvent); + dispatcher.features.push(this._checkEvent); + } } if (!this.active) { this.active = true; From 0f2eb8d4995022171b5850203b286f55ed45dffa Mon Sep 17 00:00:00 2001 From: Gray Norton Date: Thu, 5 Nov 2015 08:42:41 -0800 Subject: [PATCH 159/195] ENYO-2788: Provide an explicit `beforeTeardown()` hook for Control In the fix for ENYO-2744, we made a tweak to the interface between enyo/Control and enyo/HTMLStringDelegate to make it possible for Controls to override `teardownChildren()`. In doing so, we failed to preserve the `cache` argument to `teardownChildren()`, which caused a regression for Controls that use the 'renderOnShow' feature. This regression could have been addressed by simply making sure we passed the `cache` argument properly, but on further reflection it seems safer and more future-proof to instead revert the previous change in favor of an explicit `beforeTeardown()` lifecycle method that Controls can implement as needed. Doing that now. Enyo-DCO-1.1-Signed-Off-By: Gray Norton (gray.norton@lge.com) --- src/Control/Control.js | 10 ++++++++++ src/HTMLStringDelegate.js | 12 +++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/Control/Control.js b/src/Control/Control.js index c5c991ec8..8f9823e87 100644 --- a/src/Control/Control.js +++ b/src/Control/Control.js @@ -1131,6 +1131,16 @@ var Control = module.exports = kind( }; }, + /** + * If a Control needs to do something before it and its children's DOM nodes + * are torn down, it can implement this lifecycle method, which is called automatically + * by the framework and takes no arguments. + * + * @type {Function} + * @protected + */ + beforeTeardown: null, + /** * @param {Boolean} [cache] - Whether or not we are tearing down as part of a destroy * operation, or if we are just caching. If `true`, the `showing` and `canGenerate` diff --git a/src/HTMLStringDelegate.js b/src/HTMLStringDelegate.js index 49cbdc063..835009a44 100644 --- a/src/HTMLStringDelegate.js +++ b/src/HTMLStringDelegate.js @@ -120,7 +120,7 @@ module.exports = { * @private */ renderContent: function (control) { - if (control.generated) control.teardownChildren(); + if (control.generated) this.teardownChildren(control); if (control.hasNode()) control.node.innerHTML = this.generateInnerHtml(control); }, @@ -244,7 +244,13 @@ module.exports = { * @private */ teardownRender: function (control, cache) { - if (control.generated) control.teardownChildren(cache); + if (control.generated) { + if (typeof control.beforeTeardown === 'function') { + control.beforeTeardown(); + } + this.teardownChildren(control, cache); + } + control.node = null; control.set('generated', false); }, @@ -255,7 +261,7 @@ module.exports = { teardownChildren: function (control, cache) { var child, i = 0; - + for (; (child = control.children[i]); ++i) { child.teardownRender(cache); } From cb515f6c792b0ac221f6a4afe30b6e0032c50d75 Mon Sep 17 00:00:00 2001 From: Ankur Mishra Date: Fri, 6 Nov 2015 18:51:38 +0530 Subject: [PATCH 160/195] Require for rotate in -ve direction Enyo-DCO-1.1-Signed-off-by: Ankur Mishra --- src/AnimationSupport/Tween.js | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/src/AnimationSupport/Tween.js b/src/AnimationSupport/Tween.js index 100ccaf21..f72f53584 100644 --- a/src/AnimationSupport/Tween.js +++ b/src/AnimationSupport/Tween.js @@ -109,24 +109,19 @@ module.exports = { for (k in props) { cState = frame.copy(charc.currentState[k] || []); - if (charc.ease && (typeof charc.ease !== 'function')) { - if ((k == 'rotate')) { - //Without control points - // pts = this.beizerSlerpPoints(charc.ease, frame.copy(oldState[k]), frame.copy(newState[k]), props[k]); - // cState = this.beizerSlerp(t, pts, cState); - - //With control points process - pts = this.beizerSPoints(charc.ease, frame.copy(oldState[k]), frame.copy(newState[k]), props[k]); - cState = this.beizerSpline(t, pts, cState); - - + if (newState[k]) { + if (charc.ease && (typeof charc.ease !== 'function')) { + if ((k == 'rotate')) { + pts = this.beizerSPoints(charc.ease, frame.copy(oldState[k]), frame.copy(newState[k]), props[k]); + cState = this.beizerSpline(t, pts, cState); + } else { + pts = this.calculateEase(charc.ease, frame.copy(oldState[k]), frame.copy(newState[k])); + cState = this.getBezier(t, pts, cState); + } } else { - pts = this.calculateEase(charc.ease, frame.copy(oldState[k]), frame.copy(newState[k])); - cState = this.getBezier(t, pts, cState); + c = k == 'rotate' ? this.slerp : this.lerp; + cState = t ? c(oldState[k], newState[k], ((typeof charc.ease === 'function') ? charc.ease : this.ease)(t, d), cState) : newState[k]; } - } else { - c = k == 'rotate' ? this.slerp : this.lerp; - cState = t ? c(oldState[k], newState[k], ((typeof charc.ease === 'function') ? charc.ease : this.ease)(t, d), cState) : newState[k]; } if (!frame.isTransform(k)) { @@ -313,11 +308,6 @@ module.exports = { if (dot == 1.0) { qR = frame.copy(qA); return qR; - } else if (dot < 0) { - qB[0] = -qB[0]; - qB[1] = -qB[1]; - qB[2] = -qB[2]; - qB[3] = -qB[3]; } theta = Math.acos(dot); for (var i = 0; i < l; i++) { From 87153406a8906348c5ea6cfba79fd439a0632b1c Mon Sep 17 00:00:00 2001 From: Ankur Mishra Date: Fri, 6 Nov 2015 19:10:40 +0530 Subject: [PATCH 161/195] Require for backgroundColor animate Enyo-DCO-1.1-Signed-off-by: Ankur Mishra --- src/AnimationSupport/Frame.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/AnimationSupport/Frame.js b/src/AnimationSupport/Frame.js index 3bfe6fae7..899ff5993 100644 --- a/src/AnimationSupport/Frame.js +++ b/src/AnimationSupport/Frame.js @@ -6,7 +6,7 @@ var Matrix = require('./Matrix'); var - COLOR = {"color": 1, "background-color": 1}, + COLOR = {"color": 1, "backgroundColor": 1}, TRANSFORM = {"translate": 1, "translateX": 1, "translateY": 1, "translateZ": 1, "rotateX": 1, "rotateY": 1, "rotateZ": 1, "rotate": 1, "skew": 1, "scale": 1, "perspective": 1}; /** @@ -253,13 +253,16 @@ var frame = module.exports = { * @public */ getStyleValue: function (style, key) { - var v = style.getPropertyValue(key); - if (v === undefined || v === null || v == "auto" || isNaN(v)) { + var v = style.getPropertyValue(key) || style[key]; + if (v === undefined || v === null || v == "auto") { return 0; } if (COLOR[key]) { return v.replace(/^\w*\(/, '').replace(')', ''); } + if (isNaN(v)) { + return 0; + } v = parseFloat(v, 10); return v; }, From f8de58f10fdf46734bbb6716a5f40981ed9eccd4 Mon Sep 17 00:00:00 2001 From: Ryan Duffy Date: Mon, 9 Nov 2015 14:57:12 -0600 Subject: [PATCH 162/195] reset padding and margin for fullscreen ancestors Issue: ENYO-2771 Enyo-DCO-1.1-Signed-off-by: Ryan Duffy (ryan.duffy@lge.com) --- css/mixins.less | 8 ++++++++ src/Control/fullscreen.less | 12 +++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/css/mixins.less b/css/mixins.less index 09c979e56..a84e780da 100644 --- a/css/mixins.less +++ b/css/mixins.less @@ -28,3 +28,11 @@ &:-o-full-screen { @rule(); } &:fullscreen { @rule(); } } + +.vendor-fullscreen-ancestor(@rule) { + &:-webkit-full-screen-ancestor { @rule(); } + &:-moz-full-screen-ancestor { @rule(); } + &:-ms-fullscreen-ancestor { @rule(); } + &:-o-full-screen-ancestor { @rule(); } + &:fullscreen-ancestor { @rule(); } +} \ No newline at end of file diff --git a/src/Control/fullscreen.less b/src/Control/fullscreen.less index 865f4470b..f87cb3810 100644 --- a/src/Control/fullscreen.less +++ b/src/Control/fullscreen.less @@ -15,5 +15,15 @@ body .enyo-fullscreen { right: 0 !important; bottom: 0 !important; .enyo-fullsize; - box-sizing: border-box !important; + box-sizing: border-box !important; +} + +// ancestors of the full screen control that already fill their containers can still affect the +// positioning of the full screen control with their padding and margin values so we reset them here +.enyo-fit, +.enyo-fill { + .vendor-fullscreen-ancestor({ + padding: 0 !important; + margin: 0 !important; + }); } \ No newline at end of file From aaab8b21fbfa317cc669b0f6f9d3b84347eb0336 Mon Sep 17 00:00:00 2001 From: "rajkumar.selvam" Date: Tue, 10 Nov 2015 15:31:57 +0530 Subject: [PATCH 163/195] Pause and resume of animations and Parallax support is added to the framework Enyo-DCO-1.1-Signed-off-by: Rajkumar Selvam --- .../AnimationInterfaceSupport.js | 14 +- src/AnimationSupport/AnimationSupport.js | 23 +- src/AnimationSupport/Core.js | 23 +- src/AnimationSupport/Fadeable.js | 1 - src/AnimationSupport/Frame.js | 2 +- src/AnimationSupport/Parallax.js | 63 + src/AnimationSupport/Slideable.js | 11 + tests/Tween/TweenTest.js | 377 + tests/Tween/enyo.css | 168 + tests/Tween/enyo.js | 24146 +++++++++++++++ ...ween_module_getcoeff_test_report_after.pdf | Bin 0 -> 104209 bytes ...een_module_getcoeff_test_report_before.pdf | Bin 0 -> 161636 bytes tests/build/enyo.css | 168 + tests/build/enyo.js | 24160 ++++++++++++++++ tests/index.html | 24 + tests/node_modules/chai.js | 5332 ++++ tests/node_modules/mocha.css | 305 + tests/node_modules/mocha.js | 13189 +++++++++ 18 files changed, 67996 insertions(+), 10 deletions(-) create mode 100644 src/AnimationSupport/Parallax.js create mode 100644 tests/Tween/TweenTest.js create mode 100644 tests/Tween/enyo.css create mode 100644 tests/Tween/enyo.js create mode 100644 tests/Tween/reports/Tween_module_getcoeff_test_report_after.pdf create mode 100644 tests/Tween/reports/Tween_module_getcoeff_test_report_before.pdf create mode 100644 tests/build/enyo.css create mode 100644 tests/build/enyo.js create mode 100644 tests/index.html create mode 100644 tests/node_modules/chai.js create mode 100644 tests/node_modules/mocha.css create mode 100644 tests/node_modules/mocha.js diff --git a/src/AnimationSupport/AnimationInterfaceSupport.js b/src/AnimationSupport/AnimationInterfaceSupport.js index 3237a17f1..d2f4253e4 100644 --- a/src/AnimationSupport/AnimationInterfaceSupport.js +++ b/src/AnimationSupport/AnimationInterfaceSupport.js @@ -109,10 +109,11 @@ var AnimationInterfaceSupport = { "down", "move", "scroll", - "mousewheel", + // "mousewheel", "touchstart", "touchmove", - "touchend" + "touchend", + "mousemove" ], /** @@ -164,6 +165,9 @@ var AnimationInterfaceSupport = { case "touchend": this.touchDragEnd(inSender, inEvent); break; + case "mousemove": + this.touchDragMove(inSender, inEvent, (inEvent, inSender.pageX) / 1.5, (inSender.pageY) / 1.5); + break; default: this.handleMyEvent(inSender, inEvent); } @@ -184,7 +188,7 @@ var AnimationInterfaceSupport = { var currentX = x, currentY = y; - if (currentX != 0 || currentY != 0) { + if (currentX !== 0 || currentY !== 0) { this.deltaValueX = this.checkX - currentX; this.checkX = currentX; // set the initial position to the current position while moving @@ -261,11 +265,9 @@ var AnimationInterfaceSupport = { this.setAnimateOne = delta; this.setAnimateTwo = (-1 * (this.translateX)); this.setAnimateThree = (-1 * (this.translateY)); - if (patterns[0].name === "Slideable") { + if (patterns[0].name === "Slideable" || patterns[0].name === "Parallax") { this.setAnimateTwo = this.setAnimateThree; } - - }, /** diff --git a/src/AnimationSupport/AnimationSupport.js b/src/AnimationSupport/AnimationSupport.js index 30856ccb6..590c51884 100644 --- a/src/AnimationSupport/AnimationSupport.js +++ b/src/AnimationSupport/AnimationSupport.js @@ -34,6 +34,7 @@ var AnimationSupport = { * Holds variouts states of animation. * Like: 'started' - Character animation has started(within rAF) * 'paused' - Character animation has paused(within rAF) + * 'resumed' - Character animation has resumed(within rAF) * 'completed' - Character animation has finished(within rAF) * @private */ @@ -174,8 +175,25 @@ var AnimationSupport = { */ pause: function () { this.animating = false; + this.set('animationState', 'paused'); }, + /** + * Halt all existing animations + * @public + */ + pauseAll: function () { + animation.pause(); + }, + + /** + * Resume the paused animation of this character + * @public + */ + resume: function () { + this.animating = true; + this.set('animationState', 'resumed'); + }, /** * @private */ @@ -213,7 +231,7 @@ var sup = kind.concatHandler; */ kind.concatHandler = function (ctor, props, instance) { sup.call(this, ctor, props, instance); - if (props.animate || props.keyFrame || props.handleAnimationEvents) { + if (props.animate || props.keyFrame || props.pattern || props.handleAnimationEvents) { var proto = ctor.prototype || ctor; extend(AnimationSupport, proto); if (props.keyFrame && typeof props.keyFrame != 'function') { @@ -225,5 +243,8 @@ kind.concatHandler = function (ctor, props, instance) { if (props.handleAnimationEvents && typeof props.handleAnimationEvents != 'function') { animation.register(proto); } + if (props.pattern && typeof props.pattern != 'function') { + animation.register(proto); + } } }; \ No newline at end of file diff --git a/src/AnimationSupport/Core.js b/src/AnimationSupport/Core.js index a30504321..45ef0d181 100644 --- a/src/AnimationSupport/Core.js +++ b/src/AnimationSupport/Core.js @@ -107,6 +107,23 @@ module.exports = kind.singleton({ this.chracs.splice(this.chracs.indexOf(curr), 1); }, + /** + * Animator public API to pause animation happening on all the + * characters. + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * + * @public + */ + pause: function () { + for (var i = 0; i < this.chracs.length; i++) { + this.chracs[i].animating = false; + } + }, + + /** * Animator public API to register character with event * @@ -117,7 +134,7 @@ module.exports = kind.singleton({ register: function (charc) { this.deRegister(charc); this.evnts.push(charc); - this.remove(charc); + //this.remove(charc); charc.animating = true; if (!this.isTicking) { @@ -167,6 +184,7 @@ module.exports = kind.singleton({ if (!curr._lastTime || ts >= curr._lastTime) { tween.complete(curr); curr.completed(curr); + curr.set('animationState', 'completed'); if(!curr.active) { this.remove(curr); } @@ -183,6 +201,9 @@ module.exports = kind.singleton({ var i, curr, evlen = this.evnts.length; for (i = 0; i < evlen; i++) { curr = this.evnts[i]; + if (typeof this.evnts[i].commitAnimation === 'function') { + this.evnts[i].commitAnimation(); + } if (curr && curr.ready()) { tween.updateDelta(curr); if (!curr.animating) { diff --git a/src/AnimationSupport/Fadeable.js b/src/AnimationSupport/Fadeable.js index 4e2d2dc82..a7924bd08 100644 --- a/src/AnimationSupport/Fadeable.js +++ b/src/AnimationSupport/Fadeable.js @@ -81,7 +81,6 @@ module.exports = { * Bubble the fadeable event */ triggerEvent: function(e) { - console.log("TODO: Trigger the fadeable event" + e); //this.doFadeStart(); } }; diff --git a/src/AnimationSupport/Frame.js b/src/AnimationSupport/Frame.js index 899ff5993..f0d9824b7 100644 --- a/src/AnimationSupport/Frame.js +++ b/src/AnimationSupport/Frame.js @@ -342,7 +342,7 @@ var frame = module.exports = { sP[k] = dP[k]; eP[k] = tP[k] || dP[k]; } - return {_startAnim: sP, _endAnim: eP, _transform: dP, currentState: dP}; + return {_startAnim: sP, _endAnim: eP, _transform: dP, currentState: dP, matrix: m}; }, getComputedDistance: function (prop, initalProp, finalProp) { diff --git a/src/AnimationSupport/Parallax.js b/src/AnimationSupport/Parallax.js new file mode 100644 index 000000000..e52680d8c --- /dev/null +++ b/src/AnimationSupport/Parallax.js @@ -0,0 +1,63 @@ +/*jslint white: true*/ +var + kind = require('../kind'), + utils = require('../utils'), + animation = require('./Core'), + Slideable = require('enyo/AnimationSupport/Slideable'); + +/** + * Interface to achieve Parallax animation + * + * @module enyo/AnimationSupport/Parallax + * @public + */ +module.exports = { + + /** + * @private + */ + name: 'Parallax', + + /** + * @private + */ + animate: true, + + /** + * @public + * + */ + rendered: kind.inherit(function(sup) { + return function() { + sup.apply(this, arguments); + this.doParallax(this); + }; + }), + + /** + * @public + * + */ + commonTasks: function(delta, deltax, deltay) { + var children = this.children; + for (var i = 0; i < children.length; i++) { + var speed = children[i].speed; + children[i].slide.call(children[i], (-1 * deltax) / speed, (-1 * deltay) / speed, 0); + children[i].start(true); + } + }, + + /** + * @public + * + */ + doParallax: function(container, deltax, deltay) { + var container = this.children; + for (var i = 0; i < this.children.length; i++) { + var currentElement = this.children[i]; + utils.mixin(currentElement, Slideable); + animation.trigger(currentElement); + } + } + +}; diff --git a/src/AnimationSupport/Slideable.js b/src/AnimationSupport/Slideable.js index 09ed9acec..77284ba94 100644 --- a/src/AnimationSupport/Slideable.js +++ b/src/AnimationSupport/Slideable.js @@ -78,6 +78,17 @@ module.exports = { translate: 0 + "," + y + "," + 0 }); break; + case "depth": + this.addAnimation({ + translate: 0 + "," + 0 + "," + x + }); + break; + case "depthForward": + this.addAnimation({ + translate: x + "," + 0 + "," + -0.009 * x + }); + break; + default: this.addAnimation({ translate: x + "," + y + "," + z diff --git a/tests/Tween/TweenTest.js b/tests/Tween/TweenTest.js new file mode 100644 index 000000000..e57fa0f9d --- /dev/null +++ b/tests/Tween/TweenTest.js @@ -0,0 +1,377 @@ +describe('Testing Tween Module', function () { + var undefined = void 0; + + describe('Bezier Point Calculation - getBezier() function', function (){ + describe('Random values', function (){ + var result; + it('should be equal to the expect result', function() { + result = [0.00390625, 0.03125, 0.109375, 0.21875, 0.2734375, 0.21875, 0.109375, 0.03125, 0.00390625]; + expect(Tween.getBezierValues(0.5, 8)).to.deep.equal(result); + }); + it('should be equal to the expect result', function() { + result = [0.24009999999999992, 0.4115999999999999, 0.26459999999999995, 0.0756, 0.0081]; + expect(Tween.getBezierValues(0.3, 4)).to.deep.equal(result); + }); + }); + + describe('Boundary values', function (){ + it('should be equal to [1,0,0,0] when the value of t is zero', function() { + expect(Tween.getBezierValues(0, 3)).to.deep.equal([1,0,0,0]); + }); + it('should be equal to [1] when the value of n is zero', function() { + expect(Tween.getBezierValues(0.1, 0)).to.deep.equal([1]); + }); + it('should be equal to [1] when the values of t and n are zero', function() { + expect(Tween.getBezierValues(0, 0)).to.deep.equal([1]); + }); + it('should be equal to undefined when the value of t is greater than 1', function() { + expect(Tween.getBezierValues(2, 3)).to.equal(undefined); + }); + }); + + describe('Mismatching number of arguments', function (){ + it('should return undefined when number of arguments are only one', function() { + expect(Tween.getBezierValues(0.7)).to.equal(undefined); + }); + it('should return undefined when number of arguments are zero', function() { + expect(Tween.getBezierValues()).to.equal(undefined); + }); + }); + + describe('Negative values', function (){ + it('should be equal to undefined when the value of t is less than zero', function() { + expect(Tween.getBezierValues(-0.5, 3)).to.equal(undefined); + }); + it('should be equal to undefined when the value of n is less than zero', function() { + expect(Tween.getBezierValues(0.5, -3)).to.equal(undefined); + }); + it('should be equal to undefined when the values of t and n are less than zero', function() { + expect(Tween.getBezierValues(-0.5, -3)).to.equal(undefined); + }); + }); + + describe('Non number inputs', function (){ + var result = [0.02560000000000001, 0.15360000000000004, 0.3456000000000001, 0.3456, 0.1296]; + it('should return undefined when value of t is string and not parseable to float', function() { + expect(Tween.getBezierValues("abc", 4)).to.equal(undefined); + }); + it('should return undefined when value of n is string and not parseable to integer', function() { + expect(Tween.getBezierValues(0.6, "xy")).to.equal(undefined); + }); + it('should return undefined when values of t and n are string and not parseable to float and integer respectively', function() { + expect(Tween.getBezierValues("abc", "xy")).to.equal(undefined); + }); + it('should return expected result when value of t is string and parseable to float', function() { + expect(Tween.getBezierValues("0.6", 4)).to.deep.equal(result); + }); + it('should return expected result when value of k is string and parseable to integer', function() { + expect(Tween.getBezierValues(0.6, "4")).to.deep.equal(result); + }); + it('should return expected result when value of t is parseable to float and k is parseable to integer', function() { + expect(Tween.getBezierValues("0.6", "4")).to.deep.equal(result); + }); + }); + + describe('Integer input for time (t)', function (){ + var result = [0, 0, 0, 0, 0, 1]; + it('should return bezier values by parsing the value of t as float when givan as integer', function() { + expect(Tween.getBezierValues(1, 5)).to.deep.equal(result); + }); + it('should return undefined if the value of t is greater than 1 after parsing as float', function() { + expect(Tween.getBezierValues(3, 5)).to.equal(undefined); + }); + it('should return undefined if the value of t is less than 0 after parsing as float', function() { + expect(Tween.getBezierValues(-1, 5)).to.equal(undefined); + }); + }); + + describe('Float input for order (n)', function (){ + var result = [0.0024300000000000016, 0.028350000000000014, 0.13230000000000006, 0.30870000000000003, 0.3601499999999999, 0.16806999999999994]; + it('should return bezier values by parsing the value of n as integer when givan as float', function() { + expect(Tween.getBezierValues(0.7, 5.0)).to.deep.equal(result); + }); + it('should return undefined if the value of n is less than 0 after parsing as integer', function() { + expect(Tween.getBezierValues(0.1, -5.0)).to.equal(undefined); + }); + }); + + describe('False values', function (){ + it('should return undefined when value of t is undefined', function() { + expect(Tween.getBezierValues(undefined, 6)).to.equal(undefined); + }); + it('should return undefined when value of n is undefined', function() { + expect(Tween.getBezierValues(0.4, undefined)).to.equal(undefined); + }); + it('should return undefined when values of t and n are undefined', function() { + expect(Tween.getBezierValues(undefined, undefined)).to.equal(undefined); + }); + it('should return undefined when value of t is null', function() { + expect(Tween.getBezierValues(null, 6)).to.equal(undefined); + }); + it('should return undefined when value of n is null', function() { + expect(Tween.getBezierValues(0.4, null)).to.equal(undefined); + }); + it('should return undefined when values of t and n are null', function() { + expect(Tween.getBezierValues(null, null)).to.equal(undefined); + }); + it('should return undefined when value of t is NaN', function() { + expect(Tween.getBezierValues(null, 6)).to.equal(undefined); + }); + it('should return undefined when value of n is NaN', function() { + expect(Tween.getBezierValues(0.4, NaN)).to.equal(undefined); + }); + it('should return undefined when values of t and n are NaN', function() { + expect(Tween.getBezierValues(NaN, NaN)).to.equal(undefined); + }); + }); + }); + + describe('Bezier Values Calculation - getBezierValues() function', function (){ + describe('Random values', function (){ + var result; + it('should be equal to the expect result', function() { + result = [0.00390625, 0.03125, 0.109375, 0.21875, 0.2734375, 0.21875, 0.109375, 0.03125, 0.00390625]; + expect(Tween.getBezierValues(0.5, 8)).to.deep.equal(result); + }); + it('should be equal to the expect result', function() { + result = [0.24009999999999992, 0.4115999999999999, 0.26459999999999995, 0.0756, 0.0081]; + expect(Tween.getBezierValues(0.3, 4)).to.deep.equal(result); + }); + }); + + describe('Boundary values', function (){ + it('should be equal to [1,0,0,0] when the value of t is zero', function() { + expect(Tween.getBezierValues(0, 3)).to.deep.equal([1,0,0,0]); + }); + it('should be equal to [1] when the value of n is zero', function() { + expect(Tween.getBezierValues(0.1, 0)).to.deep.equal([1]); + }); + it('should be equal to [1] when the values of t and n are zero', function() { + expect(Tween.getBezierValues(0, 0)).to.deep.equal([1]); + }); + it('should be equal to undefined when the value of t is greater than 1', function() { + expect(Tween.getBezierValues(2, 3)).to.equal(undefined); + }); + }); + + describe('Bezier values length', function (){ + it('should always be plus one of the value of n', function() { + var n = 5, + values = Tween.getBezierValues(1, 5); + expect(values.length).to.equal(n+1); + }); + }); + + describe('Mismatching number of arguments', function (){ + it('should return undefined when number of arguments are only one', function() { + expect(Tween.getBezierValues(0.7)).to.equal(undefined); + }); + it('should return undefined when number of arguments are zero', function() { + expect(Tween.getBezierValues()).to.equal(undefined); + }); + }); + + describe('Negative values', function (){ + it('should be equal to undefined when the value of t is less than zero', function() { + expect(Tween.getBezierValues(-0.5, 3)).to.equal(undefined); + }); + it('should be equal to undefined when the value of n is less than zero', function() { + expect(Tween.getBezierValues(0.5, -3)).to.equal(undefined); + }); + it('should be equal to undefined when the values of t and n are less than zero', function() { + expect(Tween.getBezierValues(-0.5, -3)).to.equal(undefined); + }); + }); + + describe('Non number inputs', function (){ + var result = [0.02560000000000001, 0.15360000000000004, 0.3456000000000001, 0.3456, 0.1296]; + it('should return undefined when value of t is string and not parseable to float', function() { + expect(Tween.getBezierValues("abc", 4)).to.equal(undefined); + }); + it('should return undefined when value of n is string and not parseable to integer', function() { + expect(Tween.getBezierValues(0.6, "xy")).to.equal(undefined); + }); + it('should return undefined when values of t and n are string and not parseable to float and integer respectively', function() { + expect(Tween.getBezierValues("abc", "xy")).to.equal(undefined); + }); + it('should return expected result when value of t is string and parseable to float', function() { + expect(Tween.getBezierValues("0.6", 4)).to.deep.equal(result); + }); + it('should return expected result when value of k is string and parseable to integer', function() { + expect(Tween.getBezierValues(0.6, "4")).to.deep.equal(result); + }); + it('should return expected result when value of t is parseable to float and k is parseable to integer', function() { + expect(Tween.getBezierValues("0.6", "4")).to.deep.equal(result); + }); + }); + + describe('Integer input for time (t)', function (){ + var result = [0, 0, 0, 0, 0, 1]; + it('should return bezier values by parsing the value of t as float when givan as integer', function() { + expect(Tween.getBezierValues(1, 5)).to.deep.equal(result); + }); + it('should return undefined if the value of t is greater than 1 after parsing as float', function() { + expect(Tween.getBezierValues(3, 5)).to.equal(undefined); + }); + it('should return undefined if the value of t is less than 0 after parsing as float', function() { + expect(Tween.getBezierValues(-1, 5)).to.equal(undefined); + }); + }); + + describe('Float input for order (n)', function (){ + var result = [0.0024300000000000016, 0.028350000000000014, 0.13230000000000006, 0.30870000000000003, 0.3601499999999999, 0.16806999999999994]; + it('should return bezier values by parsing the value of n as integer when givan as float', function() { + expect(Tween.getBezierValues(0.7, 5.0)).to.deep.equal(result); + }); + it('should return undefined if the value of n is less than 0 after parsing as integer', function() { + expect(Tween.getBezierValues(0.1, -5.0)).to.equal(undefined); + }); + }); + + describe('False values', function (){ + it('should return undefined when value of t is undefined', function() { + expect(Tween.getBezierValues(undefined, 6)).to.equal(undefined); + }); + it('should return undefined when value of n is undefined', function() { + expect(Tween.getBezierValues(0.4, undefined)).to.equal(undefined); + }); + it('should return undefined when values of t and n are undefined', function() { + expect(Tween.getBezierValues(undefined, undefined)).to.equal(undefined); + }); + it('should return undefined when value of t is null', function() { + expect(Tween.getBezierValues(null, 6)).to.equal(undefined); + }); + it('should return undefined when value of n is null', function() { + expect(Tween.getBezierValues(0.4, null)).to.equal(undefined); + }); + it('should return undefined when values of t and n are null', function() { + expect(Tween.getBezierValues(null, null)).to.equal(undefined); + }); + it('should return undefined when value of t is NaN', function() { + expect(Tween.getBezierValues(null, 6)).to.equal(undefined); + }); + it('should return undefined when value of n is NaN', function() { + expect(Tween.getBezierValues(0.4, NaN)).to.equal(undefined); + }); + it('should return undefined when values of t and n are NaN', function() { + expect(Tween.getBezierValues(NaN, NaN)).to.equal(undefined); + }); + }); + }); + + describe('Binomial Coefficient Calculation - getCoeff() function', function (){ + describe('Defined values', function (){ + it('should always be equal to 1 when k is 0', function() { + var num = 1000; + expect(Tween.getCoeff(num, 0)).to.equal(1); + }); + it('should always be equal to the input number itself when k is 1', function() { + var num = 1000; + expect(Tween.getCoeff(num, 1)).to.equal(num); + }); + it('should always be equal to 1 when k is same as the input number', function() { + var num = 1000; + expect(Tween.getCoeff(num, num)).to.equal(1); + }); + }); + + describe('Random values', function (){ + it('should be equal to 45 when n is 10 and k is 2', function() { + expect(Tween.getCoeff(10, 2)).to.equal(45); + }); + it('should be equal to 5152635520761925 when n is 350 and k is 8', function() { + expect(Tween.getCoeff(350, 8)).to.equal(5152635520761925); + }); + }); + + describe('Boundary values', function (){ + it('should be equal to undefined when the value of k is greater than the value of n', function() { + expect(Tween.getCoeff(2, 10)).to.equal(undefined); + }); + }); + + describe('Mismatching number of arguments', function (){ + it('should return undefined when number of arguments are only one', function() { + expect(Tween.getCoeff(200)).to.equal(undefined); + }); + it('should return undefined when number of arguments are zero', function() { + expect(Tween.getCoeff()).to.equal(undefined); + }); + }); + + describe('Negative values', function (){ + it('should return undefined when value of n is less than zero', function() { + expect(Tween.getCoeff(-125, 10)).to.equal(undefined); + }); + it('should return undefined when value of k is less than zero', function() { + expect(Tween.getCoeff(125, -10)).to.equal(undefined); + }); + it('should return undefined when values of both n and k are less than zero', function() { + expect(Tween.getCoeff(-125, -10)).to.equal(undefined); + }); + }); + + describe('Non number inputs', function (){ + it('should return undefined when value of n is string and not parseable to integer', function() { + expect(Tween.getCoeff("abc", 10)).to.equal(undefined); + }); + it('should return undefined when value of k is string and not parseable to integer', function() { + expect(Tween.getCoeff(125, "xy")).to.equal(undefined); + }); + it('should return undefined when values of n and k are string and not parseable to integer', function() { + expect(Tween.getCoeff("abc", "xy")).to.equal(undefined); + }); + it('should return expected result when value of n is string and parseable to integer', function() { + expect(Tween.getCoeff("125", 10)).to.equal(177367091094050); + }); + it('should return expected result when value of k is string and parseable to integer', function() { + expect(Tween.getCoeff(125, "10")).to.equal(177367091094050); + }); + it('should return expected result when values of n and k are string and are parseable to integer', function() { + expect(Tween.getCoeff("125", "10")).to.equal(177367091094050); + }); + }); + + describe('Non integer inputs', function (){ + it('should return result as whole number by doing floor of the value of n when givan as float', function() { + expect(Tween.getCoeff(125.2, 10)).to.equal(177367091094050); + }); + it('should return result as whole number by doing floor of the value of k when givan as float', function() { + expect(Tween.getCoeff(125, 10.8)).to.equal(177367091094050); + }); + it('should return result as whole number by doing floor of the values of n and k when givan as float', function() { + expect(Tween.getCoeff(125.2, 10.8)).to.equal(177367091094050); + }); + }); + + describe('False values', function (){ + it('should return undefined when value of n is undefined', function() { + expect(Tween.getCoeff(undefined, 10)).to.equal(undefined); + }); + it('should return undefined when value of k is undefined', function() { + expect(Tween.getCoeff(125, undefined)).to.equal(undefined); + }); + it('should return undefined when values of n and k are undefined', function() { + expect(Tween.getCoeff(undefined, undefined)).to.equal(undefined); + }); + it('should return undefined when value of n is null', function() { + expect(Tween.getCoeff(null, 10)).to.equal(undefined); + }); + it('should return undefined when value of k is null', function() { + expect(Tween.getCoeff(125, null)).to.equal(undefined); + }); + it('should return undefined when values of n and k are null', function() { + expect(Tween.getCoeff(null, null)).to.equal(undefined); + }); + it('should return undefined when value of n is NaN', function() { + expect(Tween.getCoeff(null, 10)).to.equal(undefined); + }); + it('should return undefined when value of k is NaN', function() { + expect(Tween.getCoeff(125, NaN)).to.equal(undefined); + }); + it('should return undefined when values of n and k are NaN', function() { + expect(Tween.getCoeff(NaN, NaN)).to.equal(undefined); + }); + }); + }); +}); diff --git a/tests/Tween/enyo.css b/tests/Tween/enyo.css new file mode 100644 index 000000000..d011dfac9 --- /dev/null +++ b/tests/Tween/enyo.css @@ -0,0 +1,168 @@ +/* things we always want */ +body { + font-family: 'Helvetica Neue', 'Nimbus Sans L', Arial, sans-serif; +} +/* allow hw-accelerated scrolling on platforms that support it */ +body.webkitOverflowScrolling { + -webkit-overflow-scrolling: touch; +} +/* for apps */ +.enyo-document-fit { + margin: 0; + height: 100%; + /* note: giving html overflow: auto is odd and was only ever done to avoid duplication + however, android 4.04 sometimes does not hide nodes when their display is set to none + if document is overflow auto. + */ + position: relative; +} +.enyo-body-fit { + margin: 0; + height: 100%; + /* helps prevent ios page scroll */ + overflow: auto; + position: relative; +} +.enyo-no-touch-action { + -ms-touch-action: none; +} +.enyo-untouchable { + pointer-events: none; +} +.enyo-untouchable > * { + pointer-events: auto; +} +/* user selection */ +.enyo-unselectable { + cursor: default; + -ms-user-select: none; + -webkit-user-select: none; + -moz-user-select: -moz-none; + user-select: none; +} +.enyo-selectable { + cursor: auto; + -ms-user-select: element; + -webkit-user-select: text; + -moz-user-select: text; + user-select: text; +} +.enyo-selectable::selection, +.enyo-selectable ::selection { + background: #3297FD; + color: #FFF; +} +/* layout */ +body .enyo-fit { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; +} +.enyo-clip { + overflow: hidden; +} +.enyo-border-box { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +/* compositing */ +.enyo-composite { + -webkit-transform: translateZ(0); + -moz-transform: translateZ(0); + -ms-transform: translateZ(0); + -o-transform: translateZ(0); + transform: translateZ(0); +} +.enyo-inline { + display: inline-block; +} +.enyo-positioned { + position: relative; +} +.enyo-fill { + position: relative; + width: 100%; + height: 100%; +} + +/* Accessibility */ +*[tabindex] { + /* remove outline in case dom has tabindex attribute */ + outline: none; + /* remove tap highlight in case dom has tabindex attribute */ + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +.enyo-scrollable { + overflow: hidden; +} + +/* Fullscreen */ +:-webkit-full-screen { + width: 100% !important; + height: 100% !important; +} +:-moz-full-screen { + width: 100% !important; + height: 100% !important; +} +:-ms-fullscreen { + width: 100% !important; + height: 100% !important; +} +:-o-full-screen { + width: 100% !important; + height: 100% !important; +} +:fullscreen { + width: 100% !important; + height: 100% !important; +} +/* Fallback Fullscreen */ +body .enyo-fullscreen { + position: absolute !important; + left: 0 !important; + top: 0 !important; + right: 0 !important; + bottom: 0 !important; + width: 100% !important; + height: 100% !important; + box-sizing: border-box !important; +} + +.enyo-image { + background-position: center; + background-repeat: no-repeat; + background-size: cover; +} +.enyo-image.sized { + display: inline-block; +} +.enyo-image.contain { + background-size: contain; +} +.enyo-image.cover { + background-size: cover; +} + +.enyo-tool-decorator { + display: inline-block; +} +.enyo-tool-decorator > * { + display: inline-block; + vertical-align: middle; +} + +/* reset */ +button { + font-size: inherit; + font-family: inherit; +} +button::-moz-focus-inner { + border: 0; + padding: 0; +} + diff --git a/tests/Tween/enyo.js b/tests/Tween/enyo.js new file mode 100644 index 000000000..6bdcf12a4 --- /dev/null +++ b/tests/Tween/enyo.js @@ -0,0 +1,24146 @@ +(function (scope) { + var require, manifest, exported, entries; + + // if there is already a global require method exposed from somewhere we will find it now + require = scope.require; + + // below is where the generated manifest will be placed + manifest = {'enyo/options':[function (module,exports,global,require,request){ +/*jshint node:true */ +'use strict'; + +/** +* Returns the global enyo options hash +* @module enyo/options +*/ + +module.exports = (global.enyo && global.enyo.options) || {}; + +}],'enyo/PathResolverFactory':[function (module,exports,global,require,request){ + + +var PathResolverFactory = module.exports = function() { + this.paths = {}; + this.pathNames = []; +}; + +PathResolverFactory.prototype = { + addPath: function(inName, inPath) { + this.paths[inName] = inPath; + this.pathNames.push(inName); + this.pathNames.sort(function(a, b) { + return b.length - a.length; + }); + return inPath; + }, + addPaths: function(inPaths) { + if (inPaths) { + for (var n in inPaths) { + this.addPath(n, inPaths[n]); + } + } + }, + includeTrailingSlash: function(inPath) { + return (inPath && inPath.slice(-1) !== "/") ? inPath + "/" : inPath; + }, + // replace macros of the form $pathname with the mapped value of paths.pathname + rewrite: function (inPath) { + var working, its = this.includeTrailingSlash, paths = this.paths; + var fn = function(macro, name) { + working = true; + return its(paths[name]) || ''; + }; + var result = inPath; + do { + working = false; + for (var i=0; i= 0; +}; + +/** +* Binds the `this` context of any method to a scope and a variable number of provided initial +* parameters. +* +* @param {Object} scope - The `this` context for the method. +* @param {(Function|String)} method - A Function or the name of a method to bind. +* @param {...*} [args] Any arguments that will be provided as the initial arguments to the +* enclosed method. +* @returns {Function} The bound method/closure. +* @public +*/ +var bind = exports.bind = function (scope, method) { + if (!method) { + method = scope; + scope = null; + } + scope = scope || global; + if (typeof method == 'string') { + if (scope[method]) { + method = scope[method]; + } else { + throw('enyo.bind: scope["' + method + '"] is null (scope="' + scope + '")'); + } + } + if (typeof method == 'function') { + var args = cloneArray(arguments, 2); + if (method.bind) { + return method.bind.apply(method, [scope].concat(args)); + } else { + return function() { + var nargs = cloneArray(arguments); + // invoke with collected args + return method.apply(scope, args.concat(nargs)); + }; + } + } else { + throw('enyo.bind: scope["' + method + '"] is not a function (scope="' + scope + '")'); + } +}; + +/** +* Binds a callback to a scope. If the object has a `destroyed` property that's truthy, then the +* callback will not be run if called. This can be used to implement both +* {@link module:enyo/CoreObject~Object#bindSafely} and {@link module:enyo/CoreObject~Object}-like objects like +* {@link module:enyo/Model~Model} and {@link module:enyo/Collection~Collection}. +* +* @param {Object} scope - The `this` context for the method. +* @param {(Function|String)} method - A Function or the name of a method to bind. +* @param {...*} [args] Any arguments that will be provided as the initial arguments to the +* enclosed method. +* @returns {Function} The bound method/closure. +* @public +*/ +exports.bindSafely = function (scope, method) { + if (typeof method == 'string') { + if (scope[method]) { + method = scope[method]; + } else { + throw('enyo.bindSafely: scope["' + method + '"] is null (this="' + this + '")'); + } + } + if (typeof method == 'function') { + var args = cloneArray(arguments, 2); + return function() { + if (scope.destroyed) { + return; + } + var nargs = cloneArray(arguments); + return method.apply(scope, args.concat(nargs)); + }; + } else { + throw('enyo.bindSafely: scope["' + method + '"] is not a function (this="' + this + '")'); + } +}; + +/** +* Calls the provided `method` on `scope`, asynchronously. +* +* Uses [window.setTimeout()]{@glossary window.setTimeout} with minimum delay, +* usually around 10ms. +* +* Additional arguments are passed to `method` when it is invoked. +* +* If only a single argument is supplied, will just call that function asynchronously without +* doing any additional binding. +* +* @param {Object} scope - The `this` context for the method. +* @param {(Function|String)} method - A Function or the name of a method to bind. +* @param {...*} [args] Any arguments that will be provided as the initial arguments to the +* enclosed method. +* @returns {Number} The `setTimeout` id. +* @public +*/ +exports.asyncMethod = function (scope, method) { + if (!method) { + // passed just a single argument + return setTimeout(scope, 1); + } else { + return setTimeout(bind.apply(scope, arguments), 1); + } +}; + +/** +* Calls the provided `method` ([String]{@glossary String}) on `scope` with optional +* arguments `args` ([Array]{@glossary Array}), if the object and method exist. +* +* @example +* enyo.call(myWorkObject, 'doWork', [3, 'foo']); +* +* @param {Object} scope - The `this` context for the method. +* @param {(Function|String)} method - A Function or the name of a method to bind. +* @param {Array} [args] - An array of arguments to pass to the method. +* @returns {*} The return value of the method. +* @public +*/ +exports.call = function (scope, method, args) { + var context = scope || this; + if (method) { + var fn = context[method] || method; + if (fn && fn.apply) { + return fn.apply(context, args || []); + } + } +}; + +/** +* Returns the current time in milliseconds. On platforms that support it, +* [Date.now()]{@glossary Date.now} will be used; otherwise this will +* be equivalent to [new Date().getTime()]{@glossary Date.getTime}. +* +* @returns {Number} Number of milliseconds representing the current time. +* @method +* @public +*/ + +var now = exports.now = Date.now || function () { + return new Date().getTime(); +}; + +/** +* When [window.performance]{@glossary window.performance} is available, supplies +* a high-precision, high-performance monotonic timestamp, which is independent of +* changes to the system clock and thus safer for use in animation, etc. Falls back to +* {@link module:enyo/utils#now} (based on the JavaScript [Date]{@glossary Date} object), +* which is subject to system time changes. +* +* @returns {Number} Number of milliseconds representing the current time or time since +* start of application execution as reported by the platform. +* @method +* @public +*/ +exports.perfNow = (function () { + // we have to check whether or not the browser has supplied a valid + // method to use + var perf = window.performance || {}; + // test against all known vendor-specific implementations, but use + // a fallback just in case + perf.now = perf.now || perf.mozNow || perf.msNow || perf.oNow || perf.webkitNow || now; + return function () { + return perf.now(); + }; +}()); + +/** +* A fast-path enabled global getter that takes a string path, which may be a full path (from +* context window/Enyo) or a relative path (to the execution context of the method). It knows how +* to check for and call the backwards-compatible generated getters, as well as how to handle +* computed properties. Returns `undefined` if the object at the given path cannot be found. May +* safely be called on non-existent paths. +* +* @param {String} path - The path from which to retrieve a value. +* @returns {*} The value for the given path, or `undefined` if the path could not be +* completely resolved. +* @method enyo.getPath +* @public +*/ +var getPath = exports.getPath = function (path) { + // we're trying to catch only null/undefined not empty string or 0 cases + if (!path && path !== null && path !== undefined) return path; + + var next = this, + parts, + part, + getter, + prev; + + // obviously there is a severe penalty for requesting get with a path lead + // by unnecessary relative notation... + if (path[0] == '.') path = path.replace(/^\.+/, ''); + + // here's where we check to make sure we have a truthy string-ish + if (!path) return; + + parts = path.split('.'); + part = parts.shift(); + + do { + prev = next; + // for constructors we must check to make sure they are undeferred before + // looking for static properties + // for the auto generated or provided published property support we have separate + // routines that must be called to preserve compatibility + if (next._getters && ((getter = next._getters[part])) && !getter.generated) next = next[getter](); + // for all other special cases to ensure we use any overloaded getter methods + else if (next.get && next !== this && next.get !== getPath) next = next.get(part); + // and for regular cases + else next = next[part]; + } while (next && (part = parts.shift())); + + // if necessary we ensure we've undeferred any constructor that we're + // retrieving here as a final property as well + return next; +}; + +/** +* @private +*/ +getPath.fast = function (path) { + // the current context + var b = this, fn, v; + if (b._getters && (fn = b._getters[path])) { + v = b[fn](); + } else { + v = b[path]; + } + + return v; +}; + +/** +* @TODO: Out of date... +* A global setter that takes a string path (relative to the method's execution context) or a +* full path (relative to window). Attempts to automatically retrieve any previously existing +* value to supply to any observers. If the context is an {@link module:enyo/CoreObject~Object} or subkind, the +* {@link module:enyo/ObserverSupport~ObserverSupport.notify} method is used to notify listeners for the path's being +* set. If the previous value is equivalent to the newly set value, observers will not be +* triggered by default. If the third parameter is present and is an explicit boolean true, the +* observers will be triggered regardless. Returns the context from which the method was executed. +* +* @param {String} path - The path for which to set the given value. +* @param {*} is - The value to set. +* @param {Object} [opts] - An options hash. +* @returns {this} Whatever the given context was when executed. +* @method enyo.setPath +* @public +*/ +var setPath = exports.setPath = function (path, is, opts) { + // we're trying to catch only null/undefined not empty string or 0 cases + if (!path || (!path && path !== null && path !== undefined)) return this; + + var next = this, + options = {create: true, silent: false, force: false}, + base = next, + parts, + part, + was, + force, + create, + silent, + comparator; + + if (typeof opts == 'object') opts = mixin({}, [options, opts]); + else { + force = opts; + opts = options; + } + + if (opts.force) force = true; + silent = opts.silent; + create = opts.create; + comparator = opts.comparator; + + + // obviously there is a severe penalty for requesting get with a path lead + // by unnecessary relative notation... + if (path[0] == '.') path = path.replace(/^\.+/, ''); + + // here's where we check to make sure we have a truthy string-ish + if (!path) return next; + + parts = path.split('.'); + part = parts.shift(); + + do { + + if (!parts.length) was = next.get && next.get !== getPath? next.get(part): next[part]; + else { + // this allows us to ensure that if we're setting a static property of a constructor we have the + // correct constructor + // @TODO: It seems ludicrous to have to check this on every single part of a chain; if we didn't have + // deferred constructors this wouldn't be necessary and is expensive - unnecessarily so when speed is so important + if (next !== base && next.set && next.set !== setPath) { + parts.unshift(part); + next.set(parts.join('.'), is, opts); + return base; + } + if (next !== base && next.get) next = (next.get !== getPath? next.get(part): next[part]) || (create && (next[part] = {})); + else next = next[part] || (create && (next[part] = {})); + } + + } while (next && parts.length && (part = parts.shift())); + + if (!next) return base; + + // now update to the new value + if (next !== base && next.set && next.set !== setPath) { + next.set(part, is, opts); + return base; + } else next[part] = is; + + // if possible we notify the changes but this change is notified from the immediate + // parent not the root object (could be the same) + if (next.notify && !silent && (force || was !== is || (comparator && comparator(was, is)))) next.notify(part, was, is, opts); + // we will always return the original root-object of the call + return base; +}; + +/** +* @private +*/ +setPath.fast = function (path, value) { + // the current context + var b = this, + // the previous value and helper variable + rv, fn; + // we have to check and ensure that we're not setting a computed property + // and if we are, do nothing + if (b._computed && b._computed[path] !== undefined) { + return b; + } + if (b._getters && (fn=b._getters[path])) { + rv = b[fn](); + } else { + rv = b[path]; + } + // set the new value now that we can + b[path] = value; + + // this method is only ever called from the context of enyo objects + // as a protected method + if (rv !== value) { b.notifyObservers(path, rv, value); } + // return the context + return b; +}; + +// ---------------------------------- +// String Functions +// ---------------------------------- + +/** +* Uppercases a given string. Will coerce to a [String]{@glossary String} +* if possible/necessary. +* +* @param {String} str - The string to uppercase. +* @returns {String} The uppercased string. +* @public +*/ +exports.toUpperCase = new exports.Extensible(function (str) { + if (str != null) { + return str.toString().toUpperCase(); + } + return str; +}); + +/** +* Lowercases a given string. Will coerce to a [String]{@glossary String} +* if possible/necessary. +* +* @param {String} str - The string to lowercase. +* @returns {String} The lowercased string. +* @public +*/ +exports.toLowerCase = new exports.Extensible(function (str) { + if (str != null) { + return str.toString().toLowerCase(); + } + return str; +}); + +/** +* Capitalizes a given string. +* +* @param {String} str - The string to capitalize. +* @returns {String} The capitalized string. +* @public +*/ +exports.cap = function (str) { + return str.slice(0, 1).toUpperCase() + str.slice(1); +}; + +/** +* Un-capitalizes a given string. +* +* @param {String} str - The string to un-capitalize. +* @returns {String} The un-capitalized string. +* @public +*/ +exports.uncap = function (str) { + return str.slice(0, 1).toLowerCase() + str.slice(1); +}; + +/** +* Injects an arbitrary number of values, in order, into a template string at +* positions marked by `"%."`. +* +* @param {String} template - The string template to inject with values. +* @param {...String} val The values to inject into the template. +* @returns {String} A copy of the template populated with values. +* @public +*/ +exports.format = function (template) { + var pattern = /\%./g, + arg = 0, + tmp = template, + args = arguments, + replacer; + + replacer = function () { + return args[++arg]; + }; + + return tmp.replace(pattern, replacer); +}; + +/** +* @private +*/ +String.prototype.trim = String.prototype.trim || function () { + return this.replace(/^\s+|\s+$/g, ''); +}; + +/** +* Takes a string and trims leading and trailing spaces. Strings with no length, +* non-strings, and falsy values will be returned without modification. +* +* @param {String} str - The string from which to remove whitespace. +* @returns {String} The trimmed string. +* @public +*/ +exports.trim = function (str) { + return (typeof str == 'string' && str.trim()) || str; +}; + +// ---------------------------------- +// Object Functions +// ---------------------------------- + +/** +* A [polyfill]{@glossary polyfill} for platforms that don't support +* [Object.create()]{@glossary Object.create}. +*/ +Object.create = Object.create || (function () { + var Anon = function () {}; + return function (obj) { + // in the polyfill we can't support the additional features so we are ignoring + // the extra parameters + if (!obj || obj === null || typeof obj != 'object') throw 'Object.create: Invalid parameter'; + Anon.prototype = obj; + return new Anon(); + }; +})(); + +/** +* A [polyfill]{@glossary polyfill} for platforms that don't support +* [Object.keys()]{@glossary Object.keys}. +*/ +Object.keys = Object.keys || function (obj) { + var results = []; + var hop = Object.prototype.hasOwnProperty; + for (var prop in obj) { + if (hop.call(obj, prop)) { + results.push(prop); + } + } + // *sigh* IE 8 + if (!({toString: null}).propertyIsEnumerable('toString')) { + var dontEnums = [ + 'toString', + 'toLocaleString', + 'valueOf', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'constructor' + ]; + for (var i = 0, p; (p = dontEnums[i]); i++) { + if (hop.call(obj, p)) { + results.push(p); + } + } + } + return results; +}; + +/** +* Returns an array of all known enumerable properties found on a given object. +* +* @alias Object.keys. +* @method enyo.keys +* @public +*/ +exports.keys = Object.keys; + +/** +* Convenience method that takes an [array]{@glossary Array} of properties +* and an [object]{@glossary Object} as parameters. Returns a new object +* with only those properties named in the array that are found to exist on the +* base object. If the third parameter is `true`, falsy values will be ignored. +* +* @param {String[]} properties The properties to include on the returned object. +* @param {Object} object - The object from which to retrieve values for the requested properties. +* @param {Boolean} [ignore=false] Whether or not to ignore copying falsy values. +* @returns {Object} A new object populated with the requested properties and values from +* the given object. +* @public +*/ +exports.only = function (properties, object, ignore) { + var ret = {}, + prop, + len, + i; + + for (i = 0, len = properties.length >>> 0; i < len; ++i) { + prop = properties[i]; + + if (ignore && (object[prop] === undefined || object[prop] === null)) continue; + ret[prop] = object[prop]; + } + + return ret; +}; + +/** +* Convenience method that takes two [objects]{@glossary Object} as parameters. +* For each key from the first object, if the key also exists in the second object, +* a mapping of the key from the first object to the key from the second object is +* added to a result object, which is eventually returned. In other words, the +* returned object maps the named properties of the first object to the named +* properties of the second object. The optional third parameter is a boolean +* designating whether to pass unknown key/value pairs through to the new object. +* If `true`, those keys will exist on the returned object. +* +* @param {Object} map - The object with key/value pairs. +* @param {Object} obj - The object whose values will be used. +* @param {Boolean} [pass=false] Whether or not to pass unnamed properties through +* from the given object. +* @returns {Object} A new object whose properties have been mapped. +* @public +*/ +exports.remap = function (map, obj, pass) { + var ret = pass ? clone(obj) : {}, + key; + + for (key in map) { + if (key in obj) ret[map[key]] = obj.get ? obj.get(key) : obj[key]; + } + return ret; +}; + +/** +* Helper method that accepts an [array]{@glossary Array} of +* [objects]{@glossary Object} and returns a hash of those objects indexed +* by the specified `property`. If a `filter` is provided, the filter should +* accept four parameters: the key, the value (object), the current mutable map +* reference, and an immutable copy of the original array of objects for +* comparison. +* +* @param {String} property - The property to index the array by. +* @param {Array} array - An array of property objects. +* @param {Function} [filter] - The filter function to use; accepts four arguments. +* @returns {Object} A hash (object) indexed by the `property` argument +* @public +*/ +exports.indexBy = function (property, array, filter) { + // the return value - indexed map from the given array + var map = {}, + value, + len, + idx = 0; + // sanity check for the array with an efficient native array check + if (!exists(array) || !(array instanceof Array)) { + return map; + } + // sanity check the property as a string + if (!exists(property) || 'string' !== typeof property) { + return map; + } + // the immutable copy of the array + var copy = clone(array); + // test to see if filter actually exsits + filter = exists(filter) && 'function' === typeof filter ? filter : undefined; + for (len = array.length; idx < len; ++idx) { + // grab the value from the array + value = array[idx]; + // make sure that it exists and has the requested property at all + if (exists(value) && exists(value[property])) { + if (filter) { + // if there was a filter use it - it is responsible for + // updating the map accordingly + filter(property, value, map, copy); + } else { + // use the default behavior - check to see if the key + // already exists on the map it will be overwritten + map[value[property]] = value; + } + } + } + // go ahead and return our modified map + return map; +}; + +/** +* Creates and returns a shallow copy of an [Object]{@glossary Object} or an +* [Array]{@glossary Array}. For objects, by default, properties will be scanned and +* copied directly to the clone such that they would pass the +* [hasOwnProperty()]{@glossary Object.hasOwnProperty} test. This is expensive and often not +* required. In this case, the optional second parameter may be used to allow a more efficient +* [copy]{@link Object.create} to be made. +* +* @param {(Object|Array)} base - The [Object]{@glossary Object} or +* [Array]{@glossary Array} to be cloned. +* @param {Boolean} [quick] - If `true`, when cloning objects, a faster [copy]{@link Object.create} +* method will be used. This parameter has no meaning when cloning arrays. +* @returns {*} A clone of the provided `base` if `base` is of the correct type; otherwise, +* returns `base` as it was passed in. +* @public +*/ +var clone = exports.clone = function (base, quick) { + if (base) { + + // avoid the overhead of calling yet another internal function to do type-checking + // just copy the array and be done with it + if (base instanceof Array) return base.slice(); + else if (base instanceof Object) { + return quick ? Object.create(base) : mixin({}, base); + } + } + + // we will only do this if it is not an array or native object + return base; +}; + +var empty = {}; +var mixinDefaults = { + exists: false, + ignore: false, + filter: null +}; + +/** + @todo Rewrite with appropriate documentation for options parameter (typedef) + @todo document 'quick' option + + Will take a variety of options to ultimately mix a set of properties + from objects into single object. All configurations accept a boolean as + the final parameter to indicate whether or not to ignore _truthy_/_existing_ + values on any _objects_ prior. + + If _target_ exists and is an object, it will be the base for all properties + and the returned value. If the parameter is used but is _falsy_, a new + object will be created and returned. If no such parameter exists, the first + parameter must be an array of objects and a new object will be created as + the _target_. + + The _source_ parameter may be an object or an array of objects. If no + _target_ parameter is provided, _source_ must be an array of objects. + + The _options_ parameter allows you to set the _ignore_ and/or _exists_ flags + such that if _ignore_ is true, it will not override any truthy values in the + target, and if _exists_ is true, it will only use truthy values from any of + the sources. You may optionally add a _filter_ method-option that returns a + true or false value to indicate whether the value should be used. It receives + parameters in this order: _property_, _source value_, _source values_, + _target_, _options_. Note that modifying the target in the filter method can + have unexpected results. + + Setting _options_ to true will set all options to true. + +* @method enyo.mixin +* @public +*/ +var mixin = exports.mixin = function () { + var ret = arguments[0], + src = arguments[1], + opts = arguments[2], + val; + + if (!ret) ret = {}; + else if (ret instanceof Array) { + opts = src; + src = ret; + ret = {}; + } + + if (!opts || opts === true) opts = mixinDefaults; + + if (src instanceof Array) for (var i=0, it; (it=src[i]); ++i) mixin(ret, it, opts); + else { + for (var key in src) { + val = src[key]; + + // quickly ensure the property isn't a default + if (empty[key] !== val) { + if ( + (!opts.exists || val) && + (!opts.ignore || !ret[key]) && + (opts.filter? opts.filter(key, val, src, ret, opts): true) + ) { + ret[key] = val; + } + } + } + } + + return ret; +}; + +/** +* Returns an [array]{@glossary Array} of the values of all properties in an +* [object]{@glossary Object}. +* +* @param {Object} obj - The [Object]{@glossary Object} to read the values from. +* @returns {Array} An [array]{@glossary Array} with the values from the `obj`. +* @public +*/ +exports.values = function (obj) { + var ret = []; + for (var key in obj) { + if (obj.hasOwnProperty(key)) ret.push(obj[key]); + } + return ret; +}; + +// ---------------------------------- +// Array Functions +// ---------------------------------- + +/** +* Because our older API parameters are not consistent with other array API methods, and also +* because only [IE8 lacks integrated support]{@glossary polyfill} for +* [indexOf()]{@linkcode external:Array.indexOf}, we ensure it is defined (only IE8) and advise, +* moving forward, that the built-in method be used. But to preserve our original API, it will +* simply call this method, knowing it exists. +* +* @private +*/ +Array.prototype.indexOf = Array.prototype.indexOf || function (el, offset) { + var len = this.length >>> 0; + + offset = +offset || 0; + + if (Math.abs(offset) === Infinity) offset = 0; + if (offset < 0) offset += len; + if (offset < 0) offset = 0; + + for (; offset < len; ++offset) { + if (this[offset] === el) return offset; + } + + return -1; +}; + +/** +* Because our older API parameters are not consistent with other array API methods, and also +* because only [IE8 lacks integrated support]{@glossary polyfill} for +* [lastIndexOf()]{@glossary Array.lastIndexOf} we ensure it is defined (only IE8) and +* advise, moving forward, that the built-in method be used. But to preserve our original API, it +* will simply call this method, knowing it exists. +* +* @private +*/ +Array.prototype.lastIndexOf = Array.prototype.lastIndexOf || function (el, offset) { + var array = Object(this) + , len = array.length >>> 0; + + if (len === 0) return -1; + + if (offset !== undefined) { + offset = Number(offset); + if (Math.abs(offset) > len) offset = len; + if (offset === Infinity || offset === -Infinity) offset = len; + if (offset < 0) offset += len; + } else offset = len; + + for (; offset > -1; --offset) { + if (array[offset] === el) return offset; + } + + return -1; +}; + +/** +* A [polyfill]{@glossary polyfill} for platforms that don't support +* [Array.findIndex()]{@glossary Array.findIndex}. +*/ +Array.prototype.findIndex = Array.prototype.findIndex || function (fn, ctx) { + for (var i=0, len=this.length >>> 0; i>> 0; i>> 0; i>> 0; i>> 0; i>> 0; i>> 0; i -1) roots.splice(idx, 1); + + // now we can call the original + destroy.apply(this, arguments); + }; + } +}; + +}],'enyo/AnimationSupport/Matrix':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Matrix module for matrix related calculation +* +* @module enyo/AnimationSupport/Matrix +*/ +module.exports = { + /** + * @public + */ + identity: function() { + return [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]; + }, + + /** + * @public + */ + translate: function (x, y, z) { + return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y ? y : 0, z ? z : 0, 1]; + }, + + /** + * @public + */ + translateX: function (x) { + return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x ? x : 0, 0, 0, 1]; + }, + + /** + * @public + */ + translateY: function (y) { + return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, y ? y : 0, 0, 1]; + }, + + /** + * @public + */ + translateZ: function (z) { + return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, z ? z : 0, 1]; + }, + + /** + * @public + */ + scale: function (x, y, z) { + return [x, 0, 0, 0, 0, y ? y : 1, 0, 0, 0, 0, z ? z : 1, 0, 0, 0, 0, 1]; + }, + + /** + * @public + */ + skew: function (a, b) { + a = a ? Math.tan(a * Math.PI / 180): 0; + b = b ? Math.tan(b * Math.PI / 180): 0; + return [1, b, 0, 0, a, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + }, + + /** + * @public + */ + rotateX: function (a) { + var cosa, sina; + a = a * Math.PI / 180; + cosa = Math.cos(a); + sina = Math.sin(a); + return [1, 0, 0, 0, 0, cosa, -sina, 0, 0, sina, cosa, 0, 0, 0, 0, 1]; + }, + + /** + * @public + */ + rotateY: function (b) { + var cosb, sinb; + b = b * Math.PI / 180; + cosb = Math.cos(b); + sinb = Math.sin(b); + return [cosb, 0, sinb, 0, 0, 1, 0, 0, -sinb, 0, cosb, 0, 0, 0, 0, 1]; + }, + + /** + * @public + */ + rotateZ: function (g) { + var cosg, sing; + g = g * Math.PI / 180; + cosg = Math.cos(g); + sing = Math.sin(g); + return [cosg, -sing, 0, 0, sing, cosg, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + }, + + /** + * @public + */ + rotate: function (a, b, g) { + a = a * Math.PI / 180; + b = b * Math.PI / 180; + g = g * Math.PI / 180; + var ca = Math.cos(a); + var sa = Math.sin(a); + var cb = Math.cos(b); + var sb = Math.sin(b); + var cg = Math.cos(g); + var sg = Math.sin(g); + var m = [ + cb * cg, + ca * sg + sa * sb * cg, + sa * sg - ca * sb * cg, + 0, + -cb * sg, + ca * cg - sa * sb * sg, + sa * cg + ca * sb * sg, + 0, + sb, + -sa * cb, + ca * cb, + 0, + 0, 0, 0, 1 + ]; + return m; + }, + + /** + * @public + */ + multiply: function(m1, m2) { + return [ + m1[0] * m2[0] + m1[4] * m2[1] + m1[8] * m2[2], + m1[1] * m2[0] + m1[5] * m2[1] + m1[9] * m2[2], + m1[2] * m2[0] + m1[6] * m2[1] + m1[10] * m2[2], + 0, + m1[0] * m2[4] + m1[4] * m2[5] + m1[8] * m2[6], + m1[1] * m2[4] + m1[5] * m2[5] + m1[9] * m2[6], + m1[2] * m2[4] + m1[6] * m2[5] + m1[10] * m2[6], + 0, + m1[0] * m2[8] + m1[4] * m2[9] + m1[8] * m2[10], + m1[1] * m2[8] + m1[5] * m2[9] + m1[9] * m2[10], + m1[2] * m2[8] + m1[6] * m2[9] + m1[10] * m2[10], + 0, + m1[0] * m2[12] + m1[4] * m2[13] + m1[8] * m2[14] + m1[12], + m1[1] * m2[12] + m1[5] * m2[13] + m1[9] * m2[14] + m1[13], + m1[2] * m2[12] + m1[6] * m2[13] + m1[10] * m2[14] + m1[14], + 1 + ]; + }, + + /** + * @public + */ + multiplyN: function(m1, m2) { + var i, j, sum, + m = [], + l1 = m1.length, + l2 = m2.length; + + for (i = 0; i < l1; i++) { + sum = 0; + for (j = 0; j < l2; j++) { + sum += m1[i][j] * m2[j]; + } + m.push(sum); + } + return m; + }, + + /** + * @public + */ + inverseN: function(matrix, n) { + var i, j, k, r, t, + precision = 100000, + result = [], + row = []; + for (i = 0; i < n; i++) { + for (j = n; j < 2 * n; j++) { + if (i == (j - n)) matrix[i][j] = 1.0; + else matrix[i][j] = 0.0; + } + } + + for (i = 0; i < n; i++) { + for (j = 0; j < n; j++) { + if (i != j) { + r = matrix[j][i] / matrix[i][i]; + r = Math.round(r * precision) / precision; + for (k = 0; k < 2 * n; k++) { + t = Math.round(matrix[j][k] * precision) / precision; + t -= Math.round((r * matrix[i][k]) * precision) / precision; + matrix[j][k] = t; + } + } + } + } + + for (i = 0; i < n; i++) { + t = matrix[i][i]; + for (j = 0; j < 2 * n; j++) { + matrix[i][j] = matrix[i][j] / t; + } + } + + for (i = 0; i < n; i++) { + row = []; + for (k = 0, j = n; j < 2 * n; j++, k++) { + row.push(matrix[i][j]); + } + result.push(row); + } + + return result; + }, + + /** + * @public + */ + toString: function (m) { + var ms = 'matrix3d('; + for (var i = 0; i < 15; i++) { + ms += (m[i] < 0.000001 && m[i] > -0.000001) ? '0,' : m[i] + ','; + } + ms += m[15] + ')'; + return ms; + } +}; +}],'enyo/ModelList':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/ModelList~ModelList} Object. +* @module enyo/ModelList +*/ + +/** +* A special type of [array]{@glossary Array} used internally by data layer +* [kinds]{@glossary kind}. +* +* @class ModelList +* @protected +*/ +function ModelList (args) { + Array.call(this); + this.table = {}; + if (args) this.add(args, 0); +} + +ModelList.prototype = Object.create(Array.prototype); + +module.exports = ModelList; + +/** +* Adds [models]{@link module:enyo/Model~Model} to the [list]{@link module:enyo/ModelList~ModelList}, updating an +* internal table by the model's [primaryKey]{@link module:enyo/Model~Model#primaryKey} (if +* possible) and its [euid]{@glossary euid}. +* +* @name module:enyo/ModelList~ModelList#add +* @method +* @param {(module:enyo/Model~Model|module:enyo/Model~Model[])} models The [model or models]{@link module:enyo/Model~Model} +* to add to the [list]{@link module:enyo/ModelList~ModelList}. +* @param {Number} [idx] - If provided and valid, the models will be +* [spliced]{@glossary Array.splice} into the list at this position. +* @returns {module:enyo/Model~Model[]} An immutable [array]{@glossary Array} of models +* that were actually added to the list. +* @protected +*/ +ModelList.prototype.add = function (models, idx) { + var table = this.table, + added = [], + model, + euid, + id, + i = 0; + + if (models && !(models instanceof Array)) models = [models]; + + for (; (model = models[i]); ++i) { + euid = model.euid; + + // we only want to actually add models we haven't already seen... + if (!table[euid]) { + id = model.get(model.primaryKey); + + if (id != null) { + + // @TODO: For now if we already have an entry for a model by its supposed unique + // identifier but it isn't the instance we just found we can't just + // overwrite the previous instance so we mark the new one as headless + if (table[id] && table[id] !== model) model.headless = true; + // otherwise we do the normal thing and add the entry for it + else table[id] = model; + } + + // nomatter what though the euid should be unique + table[euid] = model; + added.push(model); + } + } + + if (added.length) { + idx = !isNaN(idx) ? Math.min(Math.max(0, idx), this.length) : 0; + added.unshift(0); + added.unshift(idx); + this.splice.apply(this, added); + } + + if (added.length > 0) added = added.slice(2); + added.at = idx; + + return added; +}; + +/** +* Removes the specified [models]{@link module:enyo/Model~Model} from the [list]{@link module:enyo/ModelList~ModelList}. +* +* @name module:enyo/ModelList~ModelList#remove +* @method +* @param {(module:enyo/Model~Model|module:enyo/Model~Model[])} models The [model or models]{@link module:enyo/Model~Model} +* to remove from the [list]{@link module:enyo/ModelList~ModelList}. +* @returns {module:enyo/Model~Model[]} An immutable [array]{@glossary Array} of +* models that were actually removed from the list. +* @protected +*/ +ModelList.prototype.remove = function (models) { + var table = this.table, + removed = [], + model, + idx, + id, + i, + + // these modifications are made to allow more performant logic to take place in + // views that may need to know this information + low = Infinity, + high = -1, + indices = []; + + if (models && !(models instanceof Array)) models = [models]; + + // we start at the end to ensure that you could even pass the list itself + // and it will work + for (i = models.length - 1; (model = models[i]); --i) { + table[model.euid] = null; + id = model.get(model.primaryKey); + + if (id != null) table[id] = null; + + idx = models === this ? i : this.indexOf(model); + if (idx > -1) { + if (idx < low) low = idx; + if (idx > high) high = idx; + + this.splice(idx, 1); + removed.push(model); + indices.push(idx); + } + } + + // since this is a separate array we will add this property to it for internal use only + removed.low = low; + removed.high = high; + removed.indices = indices; + + return removed; +}; + +/** +* Determines whether the specified [model]{@link module:enyo/Model~Model} is present in the +* [list]{@link module:enyo/ModelList~ModelList}. Will attempt to resolve a [string]{@glossary String} +* or [number]{@glossary Number} to either a [primaryKey]{@link module:enyo/Model~Model#primaryKey} +* or [euid]{@glossary euid}. +* +* @name module:enyo/ModelList~ModelList#has +* @method +* @param {(module:enyo/Model~Model|String|Number)} model An identifier representing either the +* [model]{@link module:enyo/Model~Model} instance, its [primaryKey]{@link module:enyo/Model~Model#primaryKey}, +* or its [euid]{@glossary euid}. +* @returns {Boolean} Whether or not the model is present in the [list]{@link module:enyo/ModelList~ModelList}. +* @protected +*/ +ModelList.prototype.has = function (model) { + if (model === undefined || model === null) return false; + + if (typeof model == 'string' || typeof model == 'number') { + return !! this.table[model]; + } else return this.indexOf(model) > -1; +}; + +/** +* Given an identifier, attempts to return the associated [model]{@link module:enyo/Model~Model}. +* The identifier should be a [string]{@glossary String} or [number]{@glossary Number}. +* +* @name module:enyo/ModelList~ModelList#resolve +* @method +* @param {(String|Number)} model - An identifier (either a +* [primaryKey]{@link module:enyo/Model~Model#primaryKey} or an [euid]{@glossary euid}). +* @returns {(undefined|null|module:enyo/Model~Model)} If the identifier could be resolved, a +* [model]{@link module:enyo/Model~Model} instance is returned; otherwise, `undefined`, or +* possibly `null` if the model once belonged to the [list]{@link module:enyo/ModelList~ModelList}. +* @protected +*/ +ModelList.prototype.resolve = function (model) { + if (typeof model == 'string' || typeof model == 'number') { + return this.table[model]; + } else return model; +}; + +/** +* Copies the current [list]{@link module:enyo/ModelList~ModelList} and returns an shallow copy. This +* method differs from the [slice()]{@glossary Array.slice} method inherited from +* native [Array]{@glossary Array} in that this returns an {@link module:enyo/ModelList~ModelList}, +* while `slice()` returns an array. +* +* @name module:enyo/ModelList~ModelList#copy +* @method +* @returns {module:enyo/ModelList~ModelList} A shallow copy of the callee. +* @protected +*/ +ModelList.prototype.copy = function () { + return new ModelList(this); +}; + +}],'enyo/States':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Shared values for various [kinds]{@glossary kind} used to indicate a state or +* (multiple, simultaneous) states. These flags are binary values represented by +* hexadecimal numerals. They may be modified and compared (or even extended) using +* [bitwise operations]{@glossary bitwise} or various +* [API methods]{@link module:enyo/StateSupport~StateSupport} available to the kinds that support them. +* Make sure to explore the documentation for individual kinds, as they may have +* specific uses for a given flag. +* +* As a cursory overview, here is a table of the values already declared by built-in flags. +* Each hexadecimal numeral represents a unique power of 2 in binary, from which we can use +* [bitwise masks]{@glossary bitwise} to determine if a particular value is present. +* +* ```javascript +* HEX DEC BIN +* 0x0001 1 0000 0000 0000 0001 +* 0x0002 2 0000 0000 0000 0010 +* 0x0004 4 0000 0000 0000 0100 +* 0x0008 8 0000 0000 0000 1000 +* 0x0010 16 0000 0000 0001 0000 +* 0x0020 32 0000 0000 0010 0000 +* 0x0040 64 0000 0000 0100 0000 +* 0x0080 128 0000 0000 1000 0000 +* 0x0100 256 0000 0001 0000 0000 +* 0x0200 512 0000 0010 0000 0000 +* 0x0400 1024 0000 0100 0000 0000 +* 0x0800 2048 0000 1000 0000 0000 +* +* ... +* +* 0x1000 4096 0001 0000 0000 0000 +* ``` +* +* As a hint, converting (HEX) 0x0800 to DEC do: +* +* ```javascript +* (0*16^3) + (8*16^2) + (0*16^1) + (0*16^0) = 2048 +* ``` +* +* As a hint, converting (HEX) 0x0800 to BIN do: +* +* ```javascript +* 0 8 0 0 (HEX) +* ---- ---- ---- ---- +* 0000 1000 0000 0000 (BIN) +* ``` +* +* @module enyo/States +* @public +* @see module:enyo/StateSupport~StateSupport +*/ +module.exports = { + + /** + * Only exists in the client and was created during the runtime of the + * [application]{@glossary application}. + * + * @type {Number} + * @default 1 + */ + NEW: 0x0001, + + /** + * Has been modified locally only. + * + * @type {Number} + * @default 2 + */ + DIRTY: 0x0002, + + /** + * Has not been modified locally. + * + * @type {Number} + * @default 4 + */ + CLEAN: 0x0004, + + /** + * Can no longer be modified. + * @type {Number} + * @default 8 + */ + DESTROYED: 0x0008, + + /** + * Currently attempting to fetch. + * + * @see module:enyo/Model~Model#fetch + * @see module:enyo/RelationalModel~RelationalModel#fetch + * @see module:enyo/Collection~Collection#fetch + * + * @type {Number} + * @default 16 + */ + FETCHING: 0x0010, + + /** + * Currently attempting to commit. + * + * @see module:enyo/Model~Model#commit + * @see module:enyo/RelationalModel~RelationalModel#commit + * @see module:enyo/Collection~Collection#commit + * + * @type {Number} + * @default 32 + */ + COMMITTING: 0x0020, + + /** + * Currently attempting to destroy. + * + * @see module:enyo/Model~Model#destroy + * @see module:enyo/RelationalModel~RelationalModel#destroy + * @see module:enyo/Collection~Collection#destroy + * + * @type {Number} + * @default 64 + */ + DESTROYING: 0x0040, + + /** + * There was an error during commit. + * + * @see module:enyo/Model~Model#commit + * @see module:enyo/RelationalModel~RelationalModel#commit + * @see module:enyo/Collection~Collection#commit + * + * @type {Number} + * @default 128 + */ + ERROR_COMMITTING: 0x0080, + + /** + * There was an error during fetch. + * + * @see module:enyo/Model~Model#fetch + * @see module:enyo/RelationalModel~RelationalModel#fetch + * @see module:enyo/Collection~Collection#fetch + * + * @type {Number} + * @default 256 + */ + ERROR_FETCHING: 0x0100, + + /** + * There was an error during destroy. + * + * @see module:enyo/Model~Model#destroy + * @see module:enyo/RelationalModel~RelationalModel#destroy + * @see module:enyo/Collection~Collection#destroy + * + * @type {Number} + * @default 512 + */ + ERROR_DESTROYING: 0x0200, + + /** + * An error was encountered for which there was no exact flag, or an invalid error was + * specified. + * + * @type {Number} + * @default 1024 + */ + ERROR_UNKNOWN: 0x0400, + + /** + * A multi-state [bitmask]{@glossary bitwise}. Compares a given flag to the states + * included in the definition of `BUSY`. By default, this is one of + * [FETCHING]{@link module:enyo/States.FETCHING}, [COMMITTING]{@link module:enyo/States.COMMITTING}, or + * [DESTROYING]{@link module:enyo/States.DESTROYING}. It may be extended to include additional + * values using the [bitwise]{@glossary bitwise} `OR` operator (`|`). + * + * @type {Number} + * @default 112 + */ + BUSY: 0x0010 | 0x0020 | 0x0040, + + /** + * A multi-state [bitmask]{@glossary bitwise}. Compares a given flag to the states + * included in the definition of `ERROR`. By default, this is one of + * [ERROR_FETCHING]{@link module:enyo/States.ERROR_FETCHING}, + * [ERROR_COMMITTING]{@link module:enyo/States.ERROR_COMMITTING}, + * [ERROR_DESTROYING]{@link module:enyo/States.ERROR_DESTROYING}, or + * [ERROR_UNKNOWN]{@link module:enyo/States.ERROR_UNKNOWN}. It may be extended to include + * additional values using the [bitwise]{@glossary bitwise} `OR` operator (`|`). + * + * @type {Number} + * @default 1920 + */ + ERROR: 0x0080 | 0x0100 | 0x0200 | 0x0400, + + /** + * A multi-state [bitmask]{@glossary bitwise}. Compares a given flag to the states + * included in the definition of `READY`. By default, this is the inverse of any + * values included in [BUSY]{@link module:enyo/States.BUSY} or [ERROR]{@link module:enyo/States.ERROR}. + * + * @type {Number} + * @default -2041 + */ + READY: ~(0x0008 | 0x0010 | 0x0020 | 0x0040 | 0x0080 | 0x0100 | 0x0200 | 0x0400) +}; + +}],'enyo/AnimationSupport/Vector':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Vector module for vector related calculations. +* Also provides API's for Quaternion vectors for rotation. +* +* @module enyo/AnimationSupport/Vector +*/ +module.exports = { + /** + * Divides vector with a scalar value. + * @public + */ + divide: function (q, s) { + return [q[0] / s, q[1] / s, q[2] / s]; + }, + + /** + * Length of 3D vectors + * @public + */ + len: function (q) { + return Math.sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2]); + }, + + /** + * Dot product of 3D vectors + * @public + */ + dot: function (q1, q2) { + return (q1[0] * q2[0]) + (q1[1] * q2[1]) + (q1[2] * q2[2]) + q1[3] && q2[3] ? (q1[3] * q2[3]) : 0; + }, + + /** + * Cross product of two vectors + * @public + */ + cross: function (q1, q2) { + return [ + q1[1] * q2[2] - q1[2] * q2[1], + q1[2] * q2[0] - q1[0] * q2[2], + q1[0] * q2[1] - q1[1] * q2[0] + ]; + }, + + /** + * Normalizing a vector is obtaining another unit vector in the same direction. + * To normalize a vector, divide the vector by its magnitude. + * @public + */ + normalize: function (q) { + return this.divide(q, this.len(q)); + }, + + /** + * Combine scalar values with two vectors. + * Required during parsing scaler values matrix. + * @public + */ + combine: function (a, b, ascl, bscl) { + return [ + (ascl * a[0]) + (bscl * b[0]), + (ascl * a[1]) + (bscl * b[1]), + (ascl * a[2]) + (bscl * b[2]) + ]; + }, + + /** + * Converts a quaternion vector to a rotation vector. + * @public + */ + toVector: function (rv) { + var r = 2 * Math.acos(rv[3]); + var sA = Math.sqrt(1.0 - rv[3] * rv[3]); + if (Math.abs(sA) < 0.0005) sA = 1; + return [rv[0] / sA, rv[1] / sA, rv[2] / sA, r * 180 / Math.PI]; + }, + + /** + * Converts a rotation vector to a quaternion vector. + * @public + */ + toQuant: function (q) { + if (!q) q = []; + + var x = q[0] || 0, + y = q[1] || 0, + z = q[2] || 0, + deg = q[3] || 0, + r = deg * (Math.PI / 360), + sR = Math.sin(r), + cR = Math.cos(r); + + q[0] = x * sR; + q[1] = y * sR; + q[2] = z * sR; + q[3] = cR; + return q; + } +}; +}],'enyo/job':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains methods for dealing with jobs. +* @module enyo/job +*/ +var _jobs = {}; + +/** +* Runs a job after the specified amount of time has elapsed +* since a job with the same name has run. +* +* Jobs can be used to throttle behaviors. If some event may occur one time or +* multiple times, but we want a response to occur only once every `n` seconds, +* we can use a job. +* +* @example +* onscroll: function() { +* // updateThumb will be called, but only when 1 second has elapsed since the +* // last onscroll +* exports("updateThumb", this.bindSafely("updateThumb"), 1000); +* } +* +* @param {String} nom - The name of the job to throttle. +* @param {(Function|String)} job - Either the name of a method or a [function]{@glossary Function} +* to execute as the requested job. +* @param {Number} wait - The number of milliseconds to wait before executing the job again. +* @function +* @public +*/ +exports = module.exports = function (nom, job, wait) { + exports.stop(nom); + _jobs[nom] = setTimeout(function() { + exports.stop(nom); + job(); + }, wait); +}; + +/** +* Cancels the named job, if it has not already fired. +* +* @param {String} nom - The name of the job to cancel. +* @static +* @public +*/ +exports.stop = function (nom) { + if (_jobs[nom]) { + clearTimeout(_jobs[nom]); + delete _jobs[nom]; + } +}; + +/** +* Immediately invokes the job and prevents any other calls +* to `exports.throttle()` with the same job name from running for the +* specified amount of time. +* +* This is used for throttling user events when you want to provide an +* immediate response, but later invocations might just be noise if they arrive +* too often. +* +* @param {String} nom - The name of the job to throttle. +* @param {(Function|String)} job - Either the name of a method or a [function]{@glossary Function} +* to execute as the requested job. +* @param {Number} wait - The number of milliseconds to wait before executing the +* job again. +* @static +* @public +*/ +exports.throttle = function (nom, job, wait) { + // if we still have a job with this name pending, return immediately + if (_jobs[nom]) { + return; + } + job(); + _jobs[nom] = setTimeout(function() { + exports.stop(nom); + }, wait); +}; + +}],'enyo/platform':[function (module,exports,global,require,request){ +require('enyo'); + +var utils = require('./utils'); + +/** +* Determines OS versions of platforms that need special treatment. Can have one of the following +* properties: +* +* * android +* * androidChrome (Chrome on Android, standard starting in 4.1) +* * androidFirefox +* * ie +* * ios +* * webos +* * windowsPhone +* * blackberry +* * tizen +* * safari (desktop version) +* * chrome (desktop version) +* * firefox (desktop version) +* * firefoxOS +* +* If the property is defined, its value will be the major version number of the platform. +* +* Example: +* ```javascript +* // android 2 does not have 3d css +* if (enyo.platform.android < 3) { +* t = 'translate(30px, 50px)'; +* } else { +* t = 'translate3d(30px, 50px, 0)'; +* } +* this.applyStyle('-webkit-transform', t); +* ``` +* +* @module enyo/platform +*/ +exports = module.exports = + /** @lends module:enyo/platform~platform */ { + //* `true` if the platform has native single-finger [events]{@glossary event}. + touch: Boolean(('ontouchstart' in window) || window.navigator.msMaxTouchPoints), + //* `true` if the platform has native double-finger [events]{@glossary event}. + gesture: Boolean(('ongesturestart' in window) || window.navigator.msMaxTouchPoints) +}; + +/** +* @private +*/ +var ua = navigator.userAgent; +var ep = exports; +var platforms = [ + // Android 4+ using Chrome + {platform: 'androidChrome', regex: /Android .* Chrome\/(\d+)[.\d]+/}, + // Android 2 - 4 + {platform: 'android', regex: /Android (\d+)/}, + // Kindle Fire + // Force version to 2, (desktop mode does not list android version) + {platform: 'android', regex: /Silk\/1./, forceVersion: 2, extra: {silk: 1}}, + // Kindle Fire HD (Silk versions 2 or 3) + // Force version to 4 + {platform: 'android', regex: /Silk\/2./, forceVersion: 4, extra: {silk: 2}}, + {platform: 'android', regex: /Silk\/3./, forceVersion: 4, extra: {silk: 3}}, + // Windows Phone 7 - 8 + {platform: 'windowsPhone', regex: /Windows Phone (?:OS )?(\d+)[.\d]+/}, + // IE 8 - 10 + {platform: 'ie', regex: /MSIE (\d+)/}, + // IE 11 + {platform: 'ie', regex: /Trident\/.*; rv:(\d+)/}, + // iOS 3 - 5 + // Apple likes to make this complicated + {platform: 'ios', regex: /iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/}, + // webOS 1 - 3 + {platform: 'webos', regex: /(?:web|hpw)OS\/(\d+)/}, + // webOS 4 / OpenWebOS + {platform: 'webos', regex: /WebAppManager|Isis|webOS\./, forceVersion: 4}, + // Open webOS release LuneOS + {platform: 'webos', regex: /LuneOS/, forceVersion: 4, extra: {luneos: 1}}, + // desktop Safari + {platform: 'safari', regex: /Version\/(\d+)[.\d]+\s+Safari/}, + // desktop Chrome + {platform: 'chrome', regex: /Chrome\/(\d+)[.\d]+/}, + // Firefox on Android + {platform: 'androidFirefox', regex: /Android;.*Firefox\/(\d+)/}, + // FirefoxOS + {platform: 'firefoxOS', regex: /Mobile;.*Firefox\/(\d+)/}, + // desktop Firefox + {platform: 'firefox', regex: /Firefox\/(\d+)/}, + // Blackberry Playbook + {platform: 'blackberry', regex: /PlayBook/i, forceVersion: 2}, + // Blackberry 10+ + {platform: 'blackberry', regex: /BB1\d;.*Version\/(\d+\.\d+)/}, + // Tizen + {platform: 'tizen', regex: /Tizen (\d+)/} +]; +for (var i = 0, p, m, v; (p = platforms[i]); i++) { + m = p.regex.exec(ua); + if (m) { + if (p.forceVersion) { + v = p.forceVersion; + } else { + v = Number(m[1]); + } + ep[p.platform] = v; + if (p.extra) { + utils.mixin(ep, p.extra); + } + ep.platformName = p.platform; + break; + } +} + +},{'./utils':'enyo/utils'}],'enyo/EventEmitter':[function (module,exports,global,require,request){ +/** +* Exports the {@link module:enyo/EventEmitter~EventEmitter} mixin. +* @module enyo/EventEmitter +*/ + +require('enyo'); + +var + utils = require('./utils'); + +var + eventTable = {}; + +/** +* @private +*/ +function addListener(obj, e, fn, ctx) { + + obj.listeners().push({ + event: e, + method: fn, + ctx: ctx || obj + }); + + return obj; +} + +/** +* @private +*/ +function removeListener(obj, e, fn, ctx) { + var listeners = obj.listeners() + , idx; + + if (listeners.length) { + idx = listeners.findIndex(function (ln) { + return ln.event == e && ln.method === fn && (ctx? ln.ctx === ctx: true); + }); + idx >= 0 && listeners.splice(idx, 1); + } + + return obj; +} + +/** +* @private +*/ +function emit(obj, args) { + var len = args.length + , e = args[0] + , listeners = obj.listeners(e); + + if (listeners.length) { + if (len > 1) { + args = utils.toArray(args); + args.unshift(obj); + } else { + args = [obj, e]; + } + + for (var i=0, ln; (ln=listeners[i]); ++i) ln.method.apply(ln.ctx, args); + + return true; + } + + return false; +} + +/** +* {@link module:enyo/EventEmitter~EventEmitter} is a {@glossary mixin} that adds support for +* registered {@glossary event} listeners. These events are different from +* bubbled events (e.g., DOM events and [handlers]{@link module:enyo/Component~Component#handlers}). +* When [emitted]{@link module:enyo/EventEmitter~EventEmitter#emit}, these events **do not bubble** +* and will only be handled by [registered listeners]{@link module:enyo/EventEmitter~EventEmitter#on}. +* +* @mixin +* @public +*/ +var EventEmitter = { + + /** + * @private + */ + name: 'EventEmitter', + + /** + * @private + */ + _silenced: false, + + /** + * @private + */ + _silenceCount: 0, + + /** + * Disables propagation of [events]{@glossary event}. This is a counting + * semaphor and [unsilence()]{@link module:enyo/EventEmitter~EventEmitter.unsilence} will need to + * be called the same number of times that this method is called. + * + * @see module:enyo/EventEmitter~EventEmitter.unsilence + * @returns {this} The callee for chaining. + * @public + */ + silence: function () { + this._silenced = true; + this._silenceCount++; + return this; + }, + + /** + * Enables propagation of [events]{@glossary event}. This is a counting + * semaphor and this method will need to be called the same number of times + * that [silence()]{@link module:enyo/EventEmitter~EventEmitter.silence} was called. + * + * @see module:enyo/EventEmitter~EventEmitter.silence + * @returns {this} The callee for chaining. + * @public + */ + unsilence: function (force) { + if (force) { + this._silenceCount = 0; + this._silenced = false; + } else { + this._silenceCount && this._silenceCount--; + this._silenceCount === 0 && (this._silenced = false); + } + return this; + }, + + /** + * Determines whether the callee is currently [silenced]{@link module:enyo/EventEmitter~EventEmitter.silence}. + * + * @returns {Boolean} Whether or not the callee is + * [silenced]{@link module:enyo/EventEmitter~EventEmitter.silence}. + * @public + */ + isSilenced: function () { + return this._silenced; + }, + + /** + * @alias enyo.EventEmitter.on + * @deprecated + * @public + */ + addListener: function (e, fn, ctx) { + return addListener(this, e, fn, ctx); + }, + + /** + * Adds an {@glossary event} listener. Until [removed]{@link module:enyo/EventEmitter~EventEmitter.off}, + * this listener will fire every time the event is + * [emitted]{@link module:enyo/EventEmitter~EventEmitter#emit}. + * + * @param {String} e - The {@glossary event} name to register for. + * @param {Function} fn - The listener. + * @param {Object} [ctx] - The optional context under which to execute the listener. + * @returns {this} The callee for chaining. + * @public + */ + on: function (e, fn, ctx) { + return addListener(this, e, fn, ctx); + }, + + /** + * @alias enyo.EventEmitter.off + * @deprecated + * @public + */ + removeListener: function (e, fn, ctx) { + return removeListener(this, e, fn, ctx); + }, + + /** + * Removes an {@glossary event} listener. + * + * @param {String} e - The {@glossary event} name. + * @param {Function} fn - The listener to unregister. + * @param {Object} [ctx] - If the listener was registered with a context, it + * should be provided when unregistering as well. + * @returns {this} The callee for chaining. + * @public + */ + off: function (e, fn, ctx) { + return removeListener(this, e, fn, ctx); + }, + + /** + * Removes all listeners, or all listeners for a given {@glossary event}. + * + * @param {String} [e] - The optional target {@glossary event}. + * @returns {this} The callee for chaining. + */ + removeAllListeners: function (e) { + var euid = this.euid + , loc = euid && eventTable[euid]; + + if (loc) { + if (e) { + eventTable[euid] = loc.filter(function (ln) { + return ln.event != e; + }); + } else { + eventTable[euid] = null; + } + } + + return this; + }, + + /** + * Primarily intended for internal use, this method returns an immutable copy + * of all listeners, or all listeners for a particular {@glossary event} (if any). + * + * @param {String} [e] - The targeted {@glossary event}. + * @returns {Object[]} Event listeners are stored in [hashes]{@glossary Object}. + * The return value will be an [array]{@glossary Array} of these hashes + * if any listeners exist. + * @public + */ + listeners: function (e) { + var euid = this.euid || (this.euid = utils.uid('e')) + , loc = eventTable[euid] || (eventTable[euid] = []); + + return !e? loc: loc.filter(function (ln) { + return ln.event == e || ln.event == '*'; + }); + }, + + /** + * @alias enyo.EventEmitter.emit + * @deprecated + * @public + */ + triggerEvent: function () { + return !this._silenced? emit(this, arguments): false; + }, + + /** + * Emits the named {@glossary event}. All subsequent arguments will be passed + * to the event listeners. + * + * @param {String} e - The {@glossary event} to emit. + * @param {...*} args All subsequent arguments will be passed to the event listeners. + * @returns {Boolean} Whether or not any listeners were notified. + * @public + */ + emit: function () { + return !this._silenced? emit(this, arguments): false; + } +}; + +module.exports = EventEmitter; + +},{'./utils':'enyo/utils'}],'enyo/StateSupport':[function (module,exports,global,require,request){ +/** +* Exports the {@link module:enyo/StateSupport~StateSupport} mixin +* @module enyo/StateSupport +*/ + +require('enyo'); + +var + States = require('./States'); + +/** +* Provides generic API methods related to using {@link module:enyo/States}. +* +* @mixin +* @public +*/ +var StateSupport = { + + /** + * @private + */ + name: 'StateSupport', + + /** + * The given status. This property will be modified by the other API methods of + * {@link module:enyo/StateSupport~StateSupport}. + * + * @type module:enyo/States + * @default null + */ + status: null, + + /** + * Will remove any [error flags]{@link module:enyo/States.ERROR} from the given + * [status]{@link module:enyo/StateSupport~StateSupport.status}. + * + * @public + */ + clearError: function () { + this.status = this.status & ~States.ERROR; + }, + + /** + * Convenience method to avoid using [bitwise]{@glossary bitwise} comparison for the + * [status]{@link module:enyo/StateSupport~StateSupport.status}. Determines whether the current status + * (or the optional passed-in value) is an [error state]{@link module:enyo/States.ERROR}. + * The passed-in value will only be used if it is a [Number]{@glossary Number}. + * + * @param {module:enyo/States} [status] - The specific value to compare as an + * [error state]{@link module:enyo/States.ERROR}. + * @returns {Boolean} Whether the value is an [error state]{@link module:enyo/States.ERROR} or not. + * @public + */ + isError: function (status) { + return !! ((isNaN(status) ? this.status : status) & States.ERROR); + }, + + /** + * Convenience method to avoid using [bitwise]{@glossary bitwise} comparison for the + * [status]{@link module:enyo/StateSupport~StateSupport.status}. Determines whether the current status + * (or the optional passed-in value) is a [busy state]{@link module:enyo/States.BUSY}. The + * passed-in value will only be used if it is a [Number]{@glossary Number}. + * + * @param {module:enyo/States} [status] - The specific value to compare as a + * [busy state]{@link module:enyo/States.BUSY}. + * @returns {Boolean} Whether the value is a [busy state]{@link module:enyo/States.BUSY} or not. + * @public + */ + isBusy: function (status) { + return !! ((isNaN(status) ? this.status : status) & States.BUSY); + }, + + /** + * Convenience method to avoid using [bitwise]{@glossary bitwise} comparison for the + * [status]{@link module:enyo/StateSupport~StateSupport.status}. Determines whether the current status + * (or the optional passed-in value) is a [ready state]{@link module:enyo/States.READY}. The + * passed-in value will only be used if it is a [Number]{@glossary Number}. + * + * @param {module:enyo/States} [status] - The specific value to compare as a + * [ready state]{@link module:enyo/States.READY}. + * @returns {Boolean} Whether the value is a [ready state]{@link module:enyo/States.BUSY} or not. + * @public + */ + isReady: function (status) { + return !! ((isNaN(status) ? this.status : status) & States.READY); + } +}; + +module.exports = StateSupport; + +},{'./States':'enyo/States'}],'enyo/logger':[function (module,exports,global,require,request){ +require('enyo'); + +var + json = require('./json'), + utils = require('./utils'), + platform = require('./platform'); + +/** +* These platforms only allow one argument for [console.log()]{@glossary console.log}: +* +* * android +* * ios +* * webos +* +* @private +*/ +var dumbConsole = Boolean(platform.android || platform.ios || platform.webos); + +/** +* Internally used methods and properties associated with logging. +* +* @module enyo/logging +* @public +*/ +exports = module.exports = { + + /** + * The log level to use. Can be a value from -1 to 99, where -1 disables all + * logging, 0 is 'error', 10 is 'warn', and 20 is 'log'. It is preferred that + * this value be set using the [setLogLevel()]{@link module:enyo/logging#setLogLevel} + * method. + * + * @type {Number} + * @default 99 + * @public + */ + level: 99, + + /** + * The known levels. + * + * @private + */ + levels: {log: 20, warn: 10, error: 0}, + + /** + * @private + */ + shouldLog: function (fn) { + var ll = parseInt(this.levels[fn], 0); + return (ll <= this.level); + }, + + /** + * @private + */ + validateArgs: function (args) { + // gracefully handle and prevent circular reference errors in objects + for (var i=0, l=args.length, item; (item=args[i]) || i/g,'>') : ''; + }, + + /** + * Returns an object describing the geometry of this node. + * + * @param {Node} n - The [node]{@glossary Node} to measure. + * @returns {Object} An object containing the properties `top`, `left`, + * `height`, and `width`. + * @public + */ + getBounds: function(n) { + if (n) { + return {left: n.offsetLeft, top: n.offsetTop, width: n.offsetWidth, height: n.offsetHeight}; + } + else { + return null; + } + }, + + /** + * This is designed to be copied into the `computedStyle` object. + * + * @private + */ + _ie8GetComputedStyle: function(prop) { + var re = /(\-([a-z]){1})/g; + if (prop === 'float') { + prop = 'styleFloat'; + } else if (re.test(prop)) { + prop = prop.replace(re, function () { + return arguments[2].toUpperCase(); + }); + } + return this[prop] !== undefined ? this[prop] : null; + }, + + /** + * @private + */ + getComputedStyle: function(node) { + if(platform.ie < 9 && node && node.currentStyle) { + //simple global.getComputedStyle polyfill for IE8 + var computedStyle = utils.clone(node.currentStyle); + computedStyle.getPropertyValue = this._ie8GetComputedStyle; + computedStyle.setProperty = function() { + return node.currentStyle.setExpression.apply(node.currentStyle, arguments); + }; + computedStyle.removeProperty = function() { + return node.currentStyle.removeAttribute.apply(node.currentStyle, arguments); + }; + return computedStyle; + } else { + return global.getComputedStyle && node && global.getComputedStyle(node, null); + } + }, + + /** + * @private + */ + getComputedStyleValue: function(node, property, computedStyle) { + var s = computedStyle || this.getComputedStyle(node), + nIE = platform.ie; + + s = s ? s.getPropertyValue(property) : null; + + if (nIE) { + var oConversion = { + 'thin' : (nIE > 8 ? 2 : 1) + 'px', + 'medium' : (nIE > 8 ? 4 : 3) + 'px', + 'thick' : (nIE > 8 ? 6 : 5) + 'px', + 'none' : '0' + }; + if (typeof oConversion[s] != 'undefined') { + s = oConversion[s]; + } + + if (s == 'auto') { + switch (property) { + case 'width': + s = node.offsetWidth; + break; + case 'height': + s = node.offsetHeight; + break; + } + } + } + + return s; + }, + + /** + * @private + */ + getFirstElementByTagName: function(tagName) { + var e = document.getElementsByTagName(tagName); + return e && e[0]; + }, + + /** + * @private + */ + applyBodyFit: function() { + var h = this.getFirstElementByTagName('html'); + if (h) { + this.addClass(h, 'enyo-document-fit'); + } + dom.addBodyClass('enyo-body-fit'); + dom.bodyIsFitting = true; + }, + + /** + * @private + */ + getWindowWidth: function() { + if (global.innerWidth) { + return global.innerWidth; + } + if (document.body && document.body.offsetWidth) { + return document.body.offsetWidth; + } + if (document.compatMode=='CSS1Compat' && + document.documentElement && + document.documentElement.offsetWidth ) { + return document.documentElement.offsetWidth; + } + return 320; + }, + + /** + * @private + */ + getWindowHeight: function() { + if (global.innerHeight) { + return global.innerHeight; + } + if (document.body && document.body.offsetHeight) { + return document.body.offsetHeight; + } + if (document.compatMode=='CSS1Compat' && + document.documentElement && + document.documentElement.offsetHeight ) { + return document.documentElement.offsetHeight; + } + return 480; + }, + + /** + * The proportion by which the `body` tag differs from the global size, in both X and Y + * dimensions. This is relevant when we need to scale the whole interface down from 1920x1080 + * (1080p) to 1280x720 (720p), for example. + * + * @private + */ + _bodyScaleFactorY: 1, + _bodyScaleFactorX: 1, + updateScaleFactor: function() { + var bodyBounds = this.getBounds(document.body); + this._bodyScaleFactorY = bodyBounds.height / this.getWindowHeight(); + this._bodyScaleFactorX = bodyBounds.width / this.getWindowWidth(); + }, + + /** + * @private + */ + // Workaround for lack of compareDocumentPosition support in IE8 + // Code MIT Licensed, John Resig; source: http://ejohn.org/blog/comparing-document-position/ + compareDocumentPosition: function(a, b) { + return a.compareDocumentPosition ? + a.compareDocumentPosition(b) : + a.contains ? + (a != b && a.contains(b) && 16) + + (a != b && b.contains(a) && 8) + + (a.sourceIndex >= 0 && b.sourceIndex >= 0 ? + (a.sourceIndex < b.sourceIndex && 4) + + (a.sourceIndex > b.sourceIndex && 2) : + 1) + + 0 : + 0; + }, + + /** + * @private + */ + // moved from FittableLayout.js into common protected code + _ieCssToPixelValue: function(node, value) { + var v = value; + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + var s = node.style; + // store style and runtime style values + var l = s.left; + var rl = node.runtimeStyle && node.runtimeStyle.left; + // then put current style in runtime style. + if (rl) { + node.runtimeStyle.left = node.currentStyle.left; + } + // apply given value and measure its pixel value + s.left = v; + v = s.pixelLeft; + // finally restore previous state + s.left = l; + if (rl) { + s.runtimeStyle.left = rl; + } + return v; + }, + + /** + * @private + */ + _pxMatch: /px/i, + getComputedBoxValue: function(node, prop, boundary, computedStyle) { + var s = computedStyle || this.getComputedStyle(node); + if (s && (!platform.ie || platform.ie >= 9)) { + var p = s.getPropertyValue(prop + '-' + boundary); + return p === 'auto' ? 0 : parseInt(p, 10); + } else if (node && node.currentStyle) { + var v = node.currentStyle[prop + utils.cap(boundary)]; + if (!v.match(this._pxMatch)) { + v = this._ieCssToPixelValue(node, v); + } + return parseInt(v, 0); + } + return 0; + }, + + /** + * Gets the boundaries of a [node's]{@glossary Node} `margin` or `padding` box. + * + * @param {Node} node - The [node]{@glossary Node} to measure. + * @param {Node} box - The boundary to measure from ('padding' or 'margin'). + * @returns {Object} An object containing the properties `top`, `right`, `bottom`, and + * `left`. + * @public + */ + calcBoxExtents: function(node, box) { + var s = this.getComputedStyle(node); + return { + top: this.getComputedBoxValue(node, box, 'top', s), + right: this.getComputedBoxValue(node, box, 'right', s), + bottom: this.getComputedBoxValue(node, box, 'bottom', s), + left: this.getComputedBoxValue(node, box, 'left', s) + }; + }, + + /** + * Gets the calculated padding of a node. Shortcut for + * {@link module:enyo/dom#calcBoxExtents}. + * + * @param {Node} node - The [node]{@glossary Node} to measure. + * @returns {Object} An object containing the properties `top`, `right`, `bottom`, and + * `left`. + * @public + */ + calcPaddingExtents: function(node) { + return this.calcBoxExtents(node, 'padding'); + }, + + /** + * Gets the calculated margin of a node. Shortcut for + * {@link module:enyo/dom#calcBoxExtents}. + * + * @param {Node} node - The [node]{@glossary Node} to measure. + * @returns {Object} An object containing the properties `top`, `right`, `bottom`, and + * `left`. + * @public + */ + calcMarginExtents: function(node) { + return this.calcBoxExtents(node, 'margin'); + }, + /** + * Returns an object like `{top: 0, left: 0, bottom: 100, right: 100, height: 10, width: 10}` + * that represents the object's position relative to `relativeToNode` (suitable for absolute + * positioning within that parent node). Negative values mean part of the object is not + * visible. If you leave `relativeToNode` as `undefined` (or it is not a parent element), then + * the position will be relative to the viewport and suitable for absolute positioning in a + * floating layer. + * + * @param {Node} node - The [node]{@glossary Node} to measure. + * @param {Node} relativeToNode - The [node]{@glossary Node} to measure the distance from. + * @returns {Object} An object containing the properties `top`, `right`, `bottom`, `left`, + * `height`, and `width`. + * @public + */ + calcNodePosition: function(targetNode, relativeToNode) { + // Parse upward and grab our positioning relative to the viewport + var top = 0, + left = 0, + node = targetNode, + width = node.offsetWidth, + height = node.offsetHeight, + transformProp = dom.getStyleTransformProp(), + xregex = /translateX\((-?\d+)px\)/i, + yregex = /translateY\((-?\d+)px\)/i, + borderLeft = 0, borderTop = 0, + totalHeight = 0, totalWidth = 0, + offsetAdjustLeft = 0, offsetAdjustTop = 0; + + if (relativeToNode) { + totalHeight = relativeToNode.offsetHeight; + totalWidth = relativeToNode.offsetWidth; + } else { + totalHeight = (document.body.parentNode.offsetHeight > this.getWindowHeight() ? this.getWindowHeight() - document.body.parentNode.scrollTop : document.body.parentNode.offsetHeight); + totalWidth = (document.body.parentNode.offsetWidth > this.getWindowWidth() ? this.getWindowWidth() - document.body.parentNode.scrollLeft : document.body.parentNode.offsetWidth); + } + + if (node.offsetParent) { + do { + // Adjust the offset if relativeToNode is a child of the offsetParent + // For IE 8 compatibility, have to use integer 8 instead of Node.DOCUMENT_POSITION_CONTAINS + if (relativeToNode && this.compareDocumentPosition(relativeToNode, node.offsetParent) & 8) { + offsetAdjustLeft = relativeToNode.offsetLeft; + offsetAdjustTop = relativeToNode.offsetTop; + } + // Ajust our top and left properties based on the position relative to the parent + left += node.offsetLeft - (node.offsetParent ? node.offsetParent.scrollLeft : 0) - offsetAdjustLeft; + if (transformProp && xregex.test(node.style[transformProp])) { + left += parseInt(node.style[transformProp].replace(xregex, '$1'), 10); + } + top += node.offsetTop - (node.offsetParent ? node.offsetParent.scrollTop : 0) - offsetAdjustTop; + if (transformProp && yregex.test(node.style[transformProp])) { + top += parseInt(node.style[transformProp].replace(yregex, '$1'), 10); + } + // Need to correct for borders if any exist on parent elements + if (node !== targetNode) { + if (node.currentStyle) { + // Oh IE, we do so love working around your incompatibilities + borderLeft = parseInt(node.currentStyle.borderLeftWidth, 10); + borderTop = parseInt(node.currentStyle.borderTopWidth, 10); + } else if (global.getComputedStyle) { + borderLeft = parseInt(global.getComputedStyle(node, '').getPropertyValue('border-left-width'), 10); + borderTop = parseInt(global.getComputedStyle(node, '').getPropertyValue('border-top-width'), 10); + } else { + // No computed style options, so try the normal style object (much less robust) + borderLeft = parseInt(node.style.borderLeftWidth, 10); + borderTop = parseInt(node.style.borderTopWidth, 10); + } + if (borderLeft) { + left += borderLeft; + } + if (borderTop) { + top += borderTop; + } + } + // Continue if we have an additional offsetParent, and either don't have a relativeToNode or the offsetParent is contained by the relativeToNode (if offsetParent contains relativeToNode, then we have already calculated up to the node, and can safely exit) + // For IE 8 compatibility, have to use integer 16 instead of Node.DOCUMENT_POSITION_CONTAINED_BY + } while ((node = node.offsetParent) && (!relativeToNode || this.compareDocumentPosition(relativeToNode, node) & 16)); + } + return { + 'top': top, + 'left': left, + 'bottom': totalHeight - top - height, + 'right': totalWidth - left - width, + 'height': height, + 'width': width + }; + }, + + /** + * Sets the `innerHTML` property of the specified `node` to `html`. + * + * @param {Node} node - The [node]{@glossary Node} to set. + * @param {String} html - An HTML string. + * @public + */ + setInnerHtml: function(node, html) { + node.innerHTML = html; + }, + + /** + * Checks a [DOM]{@glossary Node} [node]{@glossary Node} for a specific CSS class. + * + * @param {Node} node - The [node]{@glossary Node} to set. + * @param {String} s - The class name to check for. + * @returns {(Boolean|undefined)} `true` if `node` has the `s` class; `undefined` + * if there is no `node` or it has no `className` property. + * @public + */ + hasClass: function(node, s) { + if (!node || !node.className) { return; } + return (' ' + node.className + ' ').indexOf(' ' + s + ' ') >= 0; + }, + + /** + * Uniquely adds a CSS class to a DOM node. + * + * @param {Node} node - The [node]{@glossary Node} to set. + * @param {String} s - The class name to add. + * @public + */ + addClass: function(node, s) { + if (node && !this.hasClass(node, s)) { + var ss = node.className; + node.className = (ss + (ss ? ' ' : '') + s); + } + }, + + /** + * Removes a CSS class from a DOM node if it exists. + * + * @param {Node} node - The [node]{@glossary Node} from which to remove the class. + * @param {String} s - The class name to remove from `node`. + * @public + */ + removeClass: function(node, s) { + if (node && this.hasClass(node, s)) { + var ss = node.className; + node.className = (' ' + ss + ' ').replace(' ' + s + ' ', ' ').slice(1, -1); + } + }, + + /** + * Adds a class to `document.body`. This defers the actual class change if nothing has been + * rendered into `body` yet. + * + * @param {String} s - The class name to add to the document's `body`. + * @public + */ + addBodyClass: function(s) { + if (!utils.exists(roots.roots) || roots.roots.length === 0) { + if (dom._bodyClasses) { + dom._bodyClasses.push(s); + } else { + dom._bodyClasses = [s]; + } + } + else { + dom.addClass(document.body, s); + } + }, + + /** + * Returns an object describing the absolute position on the screen, relative to the top left + * corner of the screen. This function takes into account account absolute/relative + * `offsetParent` positioning, `scroll` position, and CSS transforms (currently + * `translateX`, `translateY`, and `matrix3d`). + * + * ```javascript + * {top: ..., right: ..., bottom: ..., left: ..., height: ..., width: ...} + * ``` + * + * Values returned are only valid if `hasNode()` is truthy. If there's no DOM node for the + * object, this returns a bounds structure with `undefined` as the value of all fields. + * + * @param {Node} n - The [node]{@glossary Node} to measure. + * @returns {Object} An object containing the properties `top`, `right`, `bottom`, `left`, + * `height`, and `width`. + * @public + */ + getAbsoluteBounds: function(targetNode) { + return utils.clone(targetNode.getBoundingClientRect()); + }, + + /** + * @private + */ + flushBodyClasses: function() { + if (dom._bodyClasses) { + for (var i = 0, c; (c=dom._bodyClasses[i]); ++i) { + dom.addClass(document.body, c); + } + dom._bodyClasses = null; + } + }, + + /** + * @private + */ + _bodyClasses: null, + + /** + * Convert to various unit formats. Useful for converting pixels to a resolution-independent + * measurement method, like "rem". Other units are available if defined in the + * {@link module:enyo/dom#unitToPixelFactors} object. + * + * ```javascript + * var + * dom = require('enyo/dom'); + * + * // Do calculations and get back the desired CSS unit. + * var frameWidth = 250, + * frameWithMarginInches = dom.unit( 10 + frameWidth + 10, 'in' ), + * frameWithMarginRems = dom.unit( 10 + frameWidth + 10, 'rem' ); + * // '2.8125in' == frameWithMarginInches + * // '22.5rem' == frameWithMarginRems + * ``` + * + * @param {(String|Number)} pixels - The the pixels or math to convert to the unit. + * ("px" suffix in String format is permitted. ex: `'20px'`) + * @param {(String)} toUnit - The name of the unit to convert to. + * @returns {(Number|undefined)} Resulting conversion, in case of malformed input, `undefined` + * @public + */ + unit: function (pixels, toUnit) { + if (!toUnit || !this.unitToPixelFactors[toUnit]) return; + if (typeof pixels == 'string' && pixels.substr(-2) == 'px') pixels = parseInt(pixels.substr(0, pixels.length - 2), 10); + if (typeof pixels != 'number') return; + + return (pixels / this.unitToPixelFactors[toUnit]) + '' + toUnit; + }, + + /** + * Object that stores all of the pixel conversion factors to each keyed unit. + * + * @public + */ + unitToPixelFactors: { + 'rem': 12, + 'in': 96 + } +}; + +// override setInnerHtml for Windows 8 HTML applications +if (typeof global.MSApp !== 'undefined') { + dom.setInnerHtml = function(node, html) { + global.MSApp.execUnsafeLocalFunction(function() { + node.innerHTML = html; + }); + }; +} + +// use faster classList interface if it exists +if (document.head && document.head.classList) { + dom.hasClass = function(node, s) { + if (node) { + return node.classList.contains(s); + } + }; + dom.addClass = function(node, s) { + if (node) { + return node.classList.add(s); + } + }; + dom.removeClass = function (node, s) { + if (node) { + return node.classList.remove(s); + } + }; +} + +/** +* Allows bootstrapping in environments that do not have a global object right away. +* +* @param {Function} func - The function to run +* @public +*/ +dom.requiresWindow = function(func) { + func(); +}; + + +var cssTransformProps = ['transform', '-webkit-transform', '-moz-transform', '-ms-transform', '-o-transform'], + styleTransformProps = ['transform', 'webkitTransform', 'MozTransform', 'msTransform', 'OTransform']; + +/** +* @private +*/ +dom.calcCanAccelerate = function() { + /* Android 2 is a liar: it does NOT support 3D transforms, even though Perspective is the best check */ + if (platform.android <= 2) { + return false; + } + var p$ = ['perspective', 'WebkitPerspective', 'MozPerspective', 'msPerspective', 'OPerspective']; + for (var i=0, p; (p=p$[i]); i++) { + if (typeof document.body.style[p] != 'undefined') { + return true; + } + } + return false; +}; +/** +* @private +*/ +dom.getCssTransformProp = function() { + if (this._cssTransformProp) { + return this._cssTransformProp; + } + var i = utils.indexOf(this.getStyleTransformProp(), styleTransformProps); + this._cssTransformProp = cssTransformProps[i]; + return this._cssTransformProp; +}; + +/** +* @private +*/ +dom.getStyleTransformProp = function() { + if (this._styleTransformProp || !document.body) { + return this._styleTransformProp; + } + for (var i = 0, p; (p = styleTransformProps[i]); i++) { + if (typeof document.body.style[p] != 'undefined') { + this._styleTransformProp = p; + return this._styleTransformProp; + } + } +}; + +/** +* @private +*/ +dom.domTransformsToCss = function(inTransforms) { + var n, v, text = ''; + for (n in inTransforms) { + v = inTransforms[n]; + if ((v !== null) && (v !== undefined) && (v !== '')) { + text += n + '(' + v + ') '; + } + } + return text; +}; + +/** +* @private +*/ +dom.transformsToDom = function(control) { + var css = this.domTransformsToCss(control.domTransforms), + styleProp; + + if (control.hasNode()) { + styleProp = this.getStyleTransformProp(); + } else { + styleProp = this.getCssTransformProp(); + } + + if (styleProp) control.applyStyle(styleProp, css); +}; + +/** +* Returns `true` if the platform supports CSS3 Transforms. +* +* @returns {Boolean} `true` if platform supports CSS `transform` property; +* otherwise, `false`. +* @public +*/ +dom.canTransform = function() { + return Boolean(this.getStyleTransformProp()); +}; + +/** +* Returns `true` if platform supports CSS3 3D Transforms. +* +* Typically used like this: +* ``` +* if (dom.canAccelerate()) { +* dom.transformValue(this.$.slidingThing, 'translate3d', x + ',' + y + ',' + '0') +* } else { +* dom.transformValue(this.$.slidingThing, 'translate', x + ',' + y); +* } +* ``` +* +* @returns {Boolean} `true` if platform supports CSS3 3D Transforms; +* otherwise, `false`. +* @public +*/ +dom.canAccelerate = function() { + return (this.accelerando !== undefined) ? this.accelerando : document.body && (this.accelerando = this.calcCanAccelerate()); +}; + +/** +* Applies a series of transforms to the specified {@link module:enyo/Control~Control}, using +* the platform's prefixed `transform` property. +* +* **Note:** Transforms are not commutative, so order is important. +* +* Transform values are updated by successive calls, so +* ```javascript +* dom.transform(control, {translate: '30px, 40px', scale: 2, rotate: '20deg'}); +* dom.transform(control, {scale: 3, skewX: '-30deg'}); +* ``` +* +* is equivalent to: +* ```javascript +* dom.transform(control, {translate: '30px, 40px', scale: 3, rotate: '20deg', skewX: '-30deg'}); +* ``` +* +* When applying these transforms in a WebKit browser, this is equivalent to: +* ```javascript +* control.applyStyle('-webkit-transform', 'translate(30px, 40px) scale(3) rotate(20deg) skewX(-30deg)'); +* ``` +* +* And in Firefox, this is equivalent to: +* ```javascript +* control.applyStyle('-moz-transform', 'translate(30px, 40px) scale(3) rotate(20deg) skewX(-30deg)'); +* ``` +* +* @param {module:enyo/Control~Control} control - The {@link module:enyo/Control~Control} to transform. +* @param {Object} transforms - The set of transforms to apply to `control`. +* @public +*/ +dom.transform = function(control, transforms) { + var d = control.domTransforms = control.domTransforms || {}; + utils.mixin(d, transforms); + this.transformsToDom(control); +}; + +/** +* Applies a single transform to the specified {@link module:enyo/Control~Control}. +* +* Example: +* ``` +* tap: function(inSender, inEvent) { +* var c = inEvent.originator; +* var r = c.rotation || 0; +* r = (r + 45) % 360; +* c.rotation = r; +* dom.transformValue(c, 'rotate', r); +* } +* ``` +* +* This will rotate the tapped control by 45 degrees clockwise. +* +* @param {module:enyo/Control~Control} control - The {@link module:enyo/Control~Control} to transform. +* @param {String} transform - The name of the transform function. +* @param {(String|Number)} value - The value to apply to the transform. +* @public +*/ +dom.transformValue = function(control, transform, value) { + var d = control.domTransforms = control.domTransforms || {}; + d[transform] = value; + this.transformsToDom(control); +}; + +/** +* Applies a transform that should trigger GPU compositing for the specified +* {@link module:enyo/Control~Control}. By default, the acceleration is only +* applied if the browser supports it. You may also optionally force-set `value` +* directly, to be applied to `translateZ(value)`. +* +* @param {module:enyo/Control~Control} control - The {@link module:enyo/Control~Control} to accelerate. +* @param {(String|Number)} [value] - An optional value to apply to the acceleration transform +* property. +* @public +*/ +dom.accelerate = function(control, value) { + var v = value == 'auto' ? this.canAccelerate() : value; + this.transformValue(control, 'translateZ', v ? 0 : null); +}; + + +/** + * The CSS `transition` property name for the current browser/platform, e.g.: + * + * * `-webkit-transition` + * * `-moz-transition` + * * `transition` + * + * @type {String} + * @private + */ +dom.transition = (platform.ios || platform.android || platform.chrome || platform.androidChrome || platform.safari) + ? '-webkit-transition' + : (platform.firefox || platform.firefoxOS || platform.androidFirefox) + ? '-moz-transition' + : 'transition'; + +},{'./roots':'enyo/roots','./utils':'enyo/utils','./platform':'enyo/platform'}],'enyo/animation':[function (module,exports,global,require,request){ +/** +* Contains methods useful for animations. +* @module enyo/animation +*/ + +require('enyo'); + +var + platform = require('./platform'), + utils = require('./utils'); + +var ms = Math.round(1000/60); +var prefix = ['webkit', 'moz', 'ms', 'o', '']; +var r = 'requestAnimationFrame'; +var c = 'cancel' + utils.cap(r); + +/* +* Fallback on setTimeout +* +* @private +*/ +var _requestFrame = function(inCallback) { + return global.setTimeout(inCallback, ms); +}; + +/* +* Fallback on clearTimeout +* +* @private +*/ +var _cancelFrame = function(inId) { + return global.clearTimeout(inId); +}; + +for (var i = 0, pl = prefix.length, p, wc, wr; (p = prefix[i]) || i < pl; i++) { + // if we're on ios 6 just use setTimeout, requestAnimationFrame has some kinks currently + if (platform.ios >= 6) { + break; + } + + // if prefixed, becomes Request and Cancel + wc = p ? (p + utils.cap(c)) : c; + wr = p ? (p + utils.cap(r)) : r; + // Test for cancelRequestAnimationFrame, because some browsers (Firefix 4-10) have a request without a cancel + if (global[wc]) { + _cancelFrame = global[wc]; + _requestFrame = global[wr]; + if (p == 'webkit') { + /* + Note: In Chrome, the first return value of webkitRequestAnimationFrame is 0. + We make 1 bogus call so the first used return value of webkitRequestAnimationFrame is > 0, as the spec requires. + This makes it so that the requestId is always truthy. + (we choose to do this rather than wrapping the native function to avoid the overhead) + */ + _cancelFrame(_requestFrame(utils.nop)); + } + break; + } +} +/** +* Requests an animation callback. +* +* On compatible browsers, if `node` is defined, the [callback]{@glossary callback} will +* fire only if `node` is visible. +* +* @param {Function} callback - A [callback]{@glossary callback} to be executed on the +* animation frame. +* @param {Node} node - The DOM node to request the animation frame for. +* @returns {Object} A request id to be used with +* {@link module:enyo/animation#cancelRequestAnimationFrame}. +* @public +*/ +exports.requestAnimationFrame = function(callback, node) { + return _requestFrame(callback, node); +}; +/** +* Cancels a requested animation callback with the specified id. +* +* @public +*/ +exports.cancelRequestAnimationFrame = function(inId) { + return _cancelFrame(inId); +}; + +/** +* A set of interpolation functions for animations, similar in function to CSS3 +* transitions. +* +* These are intended for use with {@link module:enyo/animation#easedLerp}. Each easing function +* accepts one (1) [Number]{@glossary Number} parameter and returns one (1) +* [Number]{@glossary Number} value. +* +* @public +*/ +exports.easing = /** @lends module:enyo/animation~easing.prototype */ { + /** + * cubicIn + * + * @public + */ + cubicIn: function(n) { + return Math.pow(n, 3); + }, + /** + * cubicOut + * + * @public + */ + cubicOut: function(n) { + return Math.pow(n - 1, 3) + 1; + }, + /** + * expoOut + * + * @public + */ + expoOut: function(n) { + return (n == 1) ? 1 : (-1 * Math.pow(2, -10 * n) + 1); + }, + /** + * quadInOut + * + * @public + */ + quadInOut: function(n) { + n = n * 2; + if (n < 1) { + return Math.pow(n, 2) / 2; + } + return -1 * ((--n) * (n - 2) - 1) / 2; + }, + /** + * linear + * + * @public + */ + linear: function(n) { + return n; + } +}; + +/** +* Gives an interpolation of an animated transition's distance from 0 to 1. +* +* Given a start time (`t0`) and an animation duration (`duration`), this +* method applies the `easing` function to the percentage of time elapsed +* divided by duration, capped at 100%. +* +* @param {Number} t0 - Start time. +* @param {Number} duration - Duration in milliseconds. +* @param {Function} easing - An easing [function]{@glossary Function} reference from +* {@link module:enyo/animation#easing}. +* @param {Boolean} reverse - Whether the animation will run in reverse. +* @returns {Number} The resulting position, capped at a maximum of 100%. +* @public +*/ +exports.easedLerp = function(t0, duration, easing, reverse) { + var lerp = (utils.perfNow() - t0) / duration; + if (reverse) { + return lerp >= 1 ? 0 : (1 - easing(1 - lerp)); + } else { + return lerp >= 1 ? 1 : easing(lerp); + } +}; + +/** +* Gives an interpolation of an animated transition's distance from +* `startValue` to `valueChange`. +* +* Applies the `easing` function with a wider range of variables to allow for +* more complex animations. +* +* @param {Number} t0 - Start time. +* @param {Number} duration - Duration in milliseconds. +* @param {Function} easing - An easing [function]{@glossary Function} reference from +* {@link module:enyo/animation#easing}. +* @param {Boolean} reverse - Whether the animation will run in reverse. +* @param {Number} time +* @param {Number} startValue - Starting value. +* @param {Number} valueChange +* @returns {Number} The resulting position, capped at a maximum of 100%. +* @public +*/ +exports.easedComplexLerp = function(t0, duration, easing, reverse, time, startValue, valueChange) { + var lerp = (utils.perfNow() - t0) / duration; + if (reverse) { + return easing(1 - lerp, time, startValue, valueChange, duration); + } else { + return easing(lerp, time, startValue, valueChange, duration); + } +}; + +},{'./platform':'enyo/platform','./utils':'enyo/utils'}],'enyo/kind':[function (module,exports,global,require,request){ +require('enyo'); + +var + logger = require('./logger'), + utils = require('./utils'); + +var defaultCtor = null; + +/** +* Creates a JavaScript [constructor]{@glossary constructor} function with +* a prototype defined by `props`. **All constructors must have a unique name.** +* +* `enyo.kind()` makes it easy to build a constructor-with-prototype (like a +* class) that has advanced features like prototype-chaining +* ([inheritance]{@glossary inheritance}). +* +* A plug-in system is included for extending the abilities of the +* [kind]{@glossary kind} generator, and constructors are allowed to +* perform custom operations when subclassed. +* +* If you make changes to `enyo.kind()`, be sure to add or update the appropriate +* [unit tests](@link https://github.com/enyojs/enyo/tree/master/tools/test/core/tests). +* +* For more information, see the documentation on +* [Kinds]{@linkplain $dev-guide/key-concepts/kinds.html} in the Enyo Developer Guide. +* +* @module enyo/kind +* @param {Object} props - A [hash]{@glossary Object} of properties used to define and create +* the [kind]{@glossary kind} +* @public +*/ +/*jshint -W120*/ +var kind = exports = module.exports = function (props) { +/*jshint +W120*/ + // extract 'name' property + var name = props.name || ''; + delete props.name; + // extract 'kind' property + var hasKind = ('kind' in props); + var kindName = props.kind; + delete props.kind; + // establish base class reference + var base = constructorForKind(kindName); + var isa = base && base.prototype || null; + // if we have an explicit kind property with value undefined, we probably + // tried to reference a kind that is not yet in scope + if (hasKind && kindName === undefined || base === undefined) { + var problem = kindName === undefined ? 'undefined kind' : 'unknown kind (' + kindName + ')'; + throw 'enyo.kind: Attempt to subclass an ' + problem + '. Check dependencies for [' + (name || '') + '].'; + } + // make a boilerplate constructor + var ctor = kind.makeCtor(); + // semi-reserved word 'constructor' causes problems with Prototype and IE, so we rename it here + if (props.hasOwnProperty('constructor')) { + props._constructor = props.constructor; + delete props.constructor; + } + // create our prototype + //ctor.prototype = isa ? enyo.delegate(isa) : {}; + utils.setPrototype(ctor, isa ? utils.delegate(isa) : {}); + // there are special cases where a base class has a property + // that may need to be concatenated with a subclasses implementation + // as opposed to completely overwriting it... + kind.concatHandler(ctor, props); + + // put in our props + utils.mixin(ctor.prototype, props); + // alias class name as 'kind' in the prototype + // but we actually only need to set this if a new name was used, + // not if it is inheriting from a kind anonymously + if (name) { + ctor.prototype.kindName = name; + } + // this is for anonymous constructors + else { + ctor.prototype.kindName = base && base.prototype? base.prototype.kindName: ''; + } + // cache superclass constructor + ctor.prototype.base = base; + // reference our real constructor + ctor.prototype.ctor = ctor; + // support pluggable 'features' + utils.forEach(kind.features, function(fn){ fn(ctor, props); }); + + if (name) kindCtors[name] = ctor; + + return ctor; +}; + +exports.setDefaultCtor = function (ctor) { + defaultCtor = ctor; +}; + +var getDefaultCtor = exports.getDefaultCtor = function () { + return defaultCtor; +}; + +/** +* @private +*/ +var concatenated = exports.concatenated = []; + +/** +* Creates a singleton of a given [kind]{@glossary kind} with a given +* definition. **The `name` property will be the instance name of the singleton +* and must be unique.** +* +* ```javascript +* enyo.singleton({ +* kind: 'enyo.Control', +* name: 'app.MySingleton', +* published: { +* value: 'foo' +* }, +* makeSomething: function() { +* //... +* } +* }); +* +* app.MySingleton.makeSomething(); +* app.MySingleton.setValue('bar'); +*``` +* +* @public +*/ +exports.singleton = function (conf) { + // extract 'name' property (the name of our singleton) + delete(conf.name); + // create an unnamed kind and save its constructor's function + var Kind = kind(conf); + var inst = new Kind(); + return inst; +}; + +/** +* @private +*/ +kind.makeCtor = function () { + var enyoConstructor = function () { + if (!(this instanceof enyoConstructor)) { + throw 'enyo.kind: constructor called directly, not using "new"'; + } + + // two-pass instantiation + var result; + if (this._constructor) { + // pure construction + result = this._constructor.apply(this, arguments); + } + // defer initialization until entire constructor chain has finished + if (this.constructed) { + // post-constructor initialization + this.constructed.apply(this, arguments); + } + + if (result) { + return result; + } + }; + return enyoConstructor; +}; + +/** +* Classes referenced by name may omit this namespace (e.g., "Button" instead of "enyo.Button") +* +* @private +*/ +kind.defaultNamespace = 'enyo'; + +/** +* Feature hooks for the oop system +* +* @private +*/ +kind.features = []; + +/** +* Used internally by several mechanisms to allow safe and normalized handling for extending a +* [kind's]{@glossary kind} super-methods. It can take a +* [constructor]{@glossary constructor}, a [prototype]{@glossary Object.prototype}, or an +* instance. +* +* @private +*/ +kind.extendMethods = function (ctor, props, add) { + var proto = ctor.prototype || ctor, + b = proto.base; + if (!proto.inherited && b) { + proto.inherited = kind.inherited; + } + // rename constructor to _constructor to work around IE8/Prototype problems + if (props.hasOwnProperty('constructor')) { + props._constructor = props.constructor; + delete props.constructor; + } + // decorate function properties to support inherited (do this ex post facto so that + // ctor.prototype is known, relies on elements in props being copied by reference) + for (var n in props) { + var p = props[n]; + if (isInherited(p)) { + // ensure that if there isn't actually a super method to call, it won't + // fail miserably - while this shouldn't happen often, it is a sanity + // check for mixin-extensions for kinds + if (add) { + p = proto[n] = p.fn(proto[n] || utils.nop); + } else { + p = proto[n] = p.fn(b? (b.prototype[n] || utils.nop): utils.nop); + } + } + if (utils.isFunction(p)) { + if (add) { + proto[n] = p; + p.displayName = n + '()'; + } else { + p._inherited = b? b.prototype[n]: null; + // FIXME: we used to need some extra values for inherited, then inherited got cleaner + // but in the meantime we used these values to support logging in Object. + // For now we support this legacy situation, by suppling logging information here. + p.displayName = proto.kindName + '.' + n + '()'; + } + } + } +}; +kind.features.push(kind.extendMethods); + +/** +* Called by {@link module:enyo/CoreObject~Object} instances attempting to access super-methods +* of a parent class ([kind]{@glossary kind}) by calling +* `this.inherited(arguments)` from within a kind method. This can only be done +* safely when there is known to be a super class with the same method. +* +* @private +*/ +kind.inherited = function (originals, replacements) { + // one-off methods are the fast track + var target = originals.callee; + var fn = target._inherited; + + // regardless of how we got here, just ensure we actually + // have a function to call or else we throw a console + // warning to notify developers they are calling a + // super method that doesn't exist + if ('function' === typeof fn) { + var args = originals; + if (replacements) { + // combine the two arrays, with the replacements taking the first + // set of arguments, and originals filling up the rest. + args = []; + var i = 0, l = replacements.length; + for (; i < l; ++i) { + args[i] = replacements[i]; + } + l = originals.length; + for (; i < l; ++i) { + args[i] = originals[i]; + } + } + return fn.apply(this, args); + } else { + logger.warn('enyo.kind.inherited: unable to find requested ' + + 'super-method from -> ' + originals.callee.displayName + ' in ' + this.kindName); + } +}; + +// dcl inspired super-inheritance + +/** +* @private +*/ +var Inherited = function (fn) { + this.fn = fn; +}; + +/** +* When defining a method that overrides an existing method in a [kind]{@glossary kind}, you +* can wrap the definition in this function and it will decorate it appropriately for inheritance +* to work. +* +* The older `this.inherited(arguments)` method still works, but this version results in much +* faster code and is the only one supported for kind [mixins]{@glossary mixin}. +* +* @param {Function} fn - A [function]{@glossary Function} that takes a single +* argument (usually named `sup`) and returns a function where +* `sup.apply(this, arguments)` is used as a mechanism to make the +* super-call. +* @public +*/ +exports.inherit = function (fn) { + return new Inherited(fn); +}; + +/** +* @private +*/ +var isInherited = exports.isInherited = function (fn) { + return fn && (fn instanceof Inherited); +}; + + +// +// 'statics' feature +// +kind.features.push(function(ctor, props) { + // install common statics + if (!ctor.subclass) { + ctor.subclass = kind.statics.subclass; + } + if (!ctor.extend) { + ctor.extend = kind.statics.extend; + } + if (!ctor.kind) { + ctor.kind = kind.statics.kind; + } + // move props statics to constructor + if (props.statics) { + utils.mixin(ctor, props.statics); + delete ctor.prototype.statics; + } + // also support protectedStatics which won't interfere with defer + if (props.protectedStatics) { + utils.mixin(ctor, props.protectedStatics); + delete ctor.prototype.protectedStatics; + } + // allow superclass customization + var base = ctor.prototype.base; + while (base) { + base.subclass(ctor, props); + base = base.prototype.base; + } +}); + +/** +* @private +*/ +kind.statics = { + + /** + * A [kind]{@glossary kind} may set its own `subclass()` method as a + * static method for its [constructor]{@glossary constructor}. Whenever + * it is subclassed, the constructor and properties will be passed through + * this method for special handling of important features. + * + * @param {Function} ctor - The [constructor]{@glossary constructor} of the + * [kind]{@glossary kind} being subclassed. + * @param {Object} props - The properties of the kind being subclassed. + * @memberof enyo.kind + * @public + */ + subclass: function (ctor, props) {}, + + /** + * Allows for extension of the current [kind]{@glossary kind} without + * creating a new kind. This method is available on all + * [constructors]{@glossary constructor}, although calling it on a + * [deferred]{@glossary deferred} constructor will force it to be + * resolved at that time. This method does not re-run the + * {@link module:enyo/kind~kind.features} against the constructor or instance. + * + * @param {Object|Object[]} props A [hash]{@glossary Object} or [array]{@glossary Array} + * of [hashes]{@glossary Object}. Properties will override + * [prototype]{@glossary Object.prototype} properties. If a + * method that is being added already exists, the new method will + * supersede the existing one. The method may call + * `this.inherited()` or be wrapped with `kind.inherit()` to call + * the original method (this chains multiple methods tied to a + * single [kind]{@glossary kind}). + * @param {Object} [target] - The instance to be extended. If this is not specified, then the + * [constructor]{@glossary constructor} of the + * [object]{@glossary Object} this method is being called on will + * be extended. + * @returns {Object} The constructor of the class, or specific + * instance, that has been extended. + * @memberof enyo.kind + * @public + */ + extend: function (props, target) { + var ctor = this + , exts = utils.isArray(props)? props: [props] + , proto, fn; + + fn = function (key, value) { + return !(typeof value == 'function' || isInherited(value)) && concatenated.indexOf(key) === -1; + }; + + proto = target || ctor.prototype; + for (var i=0, ext; (ext=exts[i]); ++i) { + kind.concatHandler(proto, ext, true); + kind.extendMethods(proto, ext, true); + utils.mixin(proto, ext, {filter: fn}); + } + + return target || ctor; + }, + + /** + * Creates a new sub-[kind]{@glossary kind} of the current kind. + * + * @param {Object} props A [hash]{@glossary Object} of properties used to define and create + * the [kind]{@glossary kind} + * @return {Function} Constructor of the new kind + * @memberof enyo.kind + * @public + */ + kind: function (props) { + if (props.kind && props.kind !== this) { + logger.warn('Creating a different kind from a constructor\'s kind() method is not ' + + 'supported and will be replaced with the constructor.'); + } + props.kind = this; + return kind(props); + } +}; + +/** +* @private +*/ +exports.concatHandler = function (ctor, props, instance) { + var proto = ctor.prototype || ctor + , base = proto.ctor; + + while (base) { + if (base.concat) base.concat(ctor, props, instance); + base = base.prototype.base; + } +}; + +/** +* Factory for [kinds]{@glossary kind} identified by [strings]{@glossary String}. +* +* @private +*/ +var kindCtors = exports._kindCtors = {}; + +/** +* @private +*/ +var constructorForKind = exports.constructorForKind = function (kind) { + if (kind === null) { + return kind; + } else if (kind === undefined) { + return getDefaultCtor(); + } + else if (utils.isFunction(kind)) { + return kind; + } + logger.warn('Creating instances by name is deprecated. Name used:', kind); + // use memoized constructor if available... + var ctor = kindCtors[kind]; + if (ctor) { + return ctor; + } + // otherwise look it up and memoize what we find + // + // if kind is an object in enyo, say "Control", then ctor = enyo["Control"] + // if kind is a path under enyo, say "Heritage.Button", then ctor = enyo["Heritage.Button"] || enyo.Heritage.Button + // if kind is a fully qualified path, say "enyo.Heritage.Button", then ctor = enyo["enyo.Heritage.Button"] || enyo.enyo.Heritage.Button || enyo.Heritage.Button + // + // Note that kind "Foo" will resolve to enyo.Foo before resolving to global "Foo". + // This is important so "Image" will map to built-in Image object, instead of enyo.Image control. + ctor = Theme[kind] || (global.enyo && global.enyo[kind]) || utils.getPath.call(global, 'enyo.' + kind) || global[kind] || utils.getPath.call(global, kind); + + // If what we found at this namespace isn't a function, it's definitely not a kind constructor + if (!utils.isFunction(ctor)) { + throw '[' + kind + '] is not the name of a valid kind.'; + } + kindCtors[kind] = ctor; + return ctor; +}; + +/** +* Namespace for current theme (`enyo.Theme.Button` references the Button specialization for the +* current theme). +* +* @private +*/ +var Theme = exports.Theme = {}; + +/** +* @private +*/ +exports.registerTheme = function (ns) { + utils.mixin(Theme, ns); +}; + +/** +* @private +*/ +exports.createFromKind = function (nom, param) { + var Ctor = nom && constructorForKind(nom); + if (Ctor) { + return new Ctor(param); + } +}; + +},{'./logger':'enyo/logger','./utils':'enyo/utils'}],'enyo/resolution':[function (module,exports,global,require,request){ +require('enyo'); + +var + Dom = require('./dom'); + +var _baseScreenType = 'standard', + _riRatio, + _screenType, + _screenTypes = [ {name: 'standard', pxPerRem: 16, width: global.innerWidth, height: global.innerHeight, aspectRatioName: 'standard'} ], // Assign one sane value in case defineScreenTypes is never run. + _screenTypeObject, + _oldScreenType; + +var getScreenTypeObject = function (type) { + type = type || _screenType; + if (_screenTypeObject && _screenTypeObject.name == type) { + return _screenTypeObject; + } + return _screenTypes.filter(function (elem) { + return (type == elem.name); + })[0]; +}; + +/** +* Resolution independence methods +* @module enyo/resolution +*/ +var ri = module.exports = { + /** + * Setup screen resolution scaling capabilities by defining all of the screens you're working + * with. These should be in the order of smallest to largest (according to width). Running + * this also initializes the rest of this resolution code. + * + * In the arguments, the following properties are required: 'name', 'pxPerRem', 'width', + * 'aspectRatioName'. The property 'base' defines the primary or default resoultion that + * everything else will be based upon. + * + * ``` + * ri.defineScreenTypes([ + * {name: 'vga', pxPerRem: 8, width: 640, height: 480, aspectRatioName: 'standard'}, + * {name: 'xga', pxPerRem: 16, width: 1024, height: 768, aspectRatioName: 'standard'}, + * {name: 'hd', pxPerRem: 16, width: 1280, height: 720, aspectRatioName: 'hdtv'}, + * {name: 'fhd', pxPerRem: 24, width: 1920, height: 1080, aspectRatioName: 'hdtv', base: true}, + * {name: 'uw-uxga', pxPerRem: 24, width: 2560, height: 1080, aspectRatioName: 'cinema'}, + * {name: 'uhd', pxPerRem: 48, width: 3840, height: 2160, aspectRatioName: 'hdtv'} + * ]); + * ``` + * + * @param {Array} types An array of objects with arguments like the example + * @public + */ + defineScreenTypes: function (types) { + _screenTypes = types; + for (var i = 0; i < _screenTypes.length; i++) { + if (_screenTypes[i]['base']) _baseScreenType = _screenTypes[i].name; + } + ri.init(); + }, + + /** + * Fetches the best-matching screen type name for the current screen size. The "best" screen type + * is determined by the screen type name that is the closest to the screen resolution without + * going over. ("The Price is Right" style.) + * + * @param {Object} [rez] - Optional measurement scheme. Must have "height" and "width" properties. + * @returns {String} Screen type, like "fhd", "uhd", etc. + * @public + */ + getScreenType: function (rez) { + rez = rez || { + height: global.innerHeight, + width: global.innerWidth + }; + var i, + types = _screenTypes, + bestMatch = types[types.length - 1].name; + + // loop thorugh resolutions + for (i = types.length - 1; i >= 0; i--) { + // find the one that matches our current size or is smaller. default to the first. + if (rez.width <= types[i].width) { + bestMatch = types[i].name; + } + } + // return the name of the resolution if we find one. + return bestMatch; + }, + + /** + * @private + */ + updateScreenBodyClasses: function (type) { + type = type || _screenType; + if (_oldScreenType) { + Dom.removeClass(document.body, 'enyo-res-' + _oldScreenType.toLowerCase()); + var oldScrObj = getScreenTypeObject(_oldScreenType); + if (oldScrObj && oldScrObj.aspectRatioName) { + Dom.removeClass(document.body, 'enyo-aspect-ratio-' + oldScrObj.aspectRatioName.toLowerCase()); + } + } + if (type) { + Dom.addBodyClass('enyo-res-' + type.toLowerCase()); + var scrObj = getScreenTypeObject(type); + if (scrObj.aspectRatioName) { + Dom.addBodyClass('enyo-aspect-ratio-' + scrObj.aspectRatioName.toLowerCase()); + } + return type; + } + }, + + /** + * @private + */ + getRiRatio: function (type) { + type = type || _screenType; + if (type) { + var ratio = this.getUnitToPixelFactors(type) / this.getUnitToPixelFactors(_baseScreenType); + if (type == _screenType) { + // cache this if it's for our current screen type. + _riRatio = ratio; + } + return ratio; + } + return 1; + }, + + /** + * @private + */ + getUnitToPixelFactors: function (type) { + type = type || _screenType; + if (type) { + return getScreenTypeObject(type).pxPerRem; + } + return 1; + }, + + /** + * Calculates the aspect ratio of the screen type provided. If none is provided the current + * screen type is used. + * + * @param {String} type Screen type to get the aspect ratio of. Providing nothing uses the + * current screen type. + * @returns {Number} The calculated screen ratio (1.333, 1.777, 2.333, etc) + * @public + */ + getAspectRatio: function (type) { + var scrObj = getScreenTypeObject(type); + if (scrObj.width && scrObj.height) { + return (scrObj.width / scrObj.height); + } + return 1; + }, + + /** + * Returns the name of the aspect ration given the screen type or the default screen type if + * none is proided. + * + * @param {String} type Screen type to get the aspect ratio of. Providing nothing uses the + * current screen type. + * @returns {String} The name of the type of screen ratio + * @public + */ + getAspectRatioName: function (type) { + var scrObj = getScreenTypeObject(type); + return scrObj.aspectRatioName || 'standard'; + }, + + /** + * Takes a provided pixel value and preforms a scaling operation on the number based on the + * current screen type. + * + * @param {Number} px The amount of standard-resolution pixels to scale to the current screen + * resolution. + * @returns {Number} The scaled value based on the current screen scaling factor. + * @public + */ + scale: function (px) { + return (_riRatio || this.getRiRatio()) * px; + }, + + /** + * The default configurable [options]{@link ri.selectSrc#options}. + * + * @typedef {Object} ri.selectSrc~src + * @property {String} hd - HD / 720p Resolution image asset source URI/URL + * @property {String} fhd - FHD / 1080p Resolution image asset source URI/URL + * @property {String} uhd - UHD / 4K Resolution image asset source URI/URL + * + * @typedef {String} ri.selectSrc~src - Image asset source URI/URL + */ + + /** + * Image src chooser. A simple utility method to select the ideal image asset from a set of + * assets, based on various screen resolutions: HD (720p), FHD (1080p), UHD (4k). When provided + * with a src argument, multiResSrc will choose the best image with respect to the current screen + * resolution. `src` may be either the traditional string, which will pass straight through, or a + * hash/object of screen types and their asset sources (keys:screen and values:src). The image + * sources will be used chosen when the screen resolution is less than or equal to the provided + * screen types. + * + * ``` + * // Take advantage of the multi-rez mode + * {kind: 'moon.Image', src: { + * 'hd': 'http://lorempixel.com/64/64/city/1/', + * 'fhd': 'http://lorempixel.com/128/128/city/1/', + * 'uhd': 'http://lorempixel.com/256/256/city/1/' + * }, alt: 'Multi-rez'}, + * // Standard string `src` + * {kind: 'moon.Image', src: http://lorempixel.com/128/128/city/1/', alt: 'Large'}, + * ``` + * + * @param {(String|moon.ri.selectSrc~src)} src A string containing a single image src or a + * key/value hash/object containing keys representing screen types (hd, fhd, uhd, etc) and + * values containing the asset src for that target screen resolution. + * @returns {String} The choosen src given the string or list provided. + * @public + */ + selectSrc: function (src) { + if (typeof src != 'string' && src) { + var i, t, + newSrc = src.fhd || src.uhd || src.hd, + types = _screenTypes; + + // loop through resolutions + for (i = types.length - 1; i >= 0; i--) { + t = types[i].name; + if (_screenType == t && src[t]) newSrc = src[t]; + } + + src = newSrc; + } + return src; + }, + + /** + * This will need to be re-run any time the screen size changes, so all the values can be + * re-cached. + * + * @public + */ + // Later we can wire this up to a screen resize event so it doesn't need to be called manually. + init: function () { + _oldScreenType = _screenType; + _screenType = this.getScreenType(); + _screenTypeObject = getScreenTypeObject(); + this.updateScreenBodyClasses(); + Dom.unitToPixelFactors.rem = this.getUnitToPixelFactors(); + _riRatio = this.getRiRatio(); + } +}; + +ri.init(); + +},{'./dom':'enyo/dom'}],'enyo/HTMLStringDelegate':[function (module,exports,global,require,request){ +require('enyo'); + +var + Dom = require('./dom'); + +var selfClosing = {img: 1, hr: 1, br: 1, area: 1, base: 1, basefont: 1, input: 1, link: 1, + meta: 1, command: 1, embed: 1, keygen: 1, wbr: 1, param: 1, source: 1, track: 1, col: 1}; + +/** +* This is the default render delegate used by {@link module:enyo/Control~Control}. It +* generates the HTML [string]{@glossary String} content and correctly inserts +* it into the DOM. A string-concatenation technique is used to perform DOM +* insertion in batches. +* +* @module enyo/HTMLStringDelegate +* @public +*/ +module.exports = { + + /** + * @private + */ + invalidate: function (control, item) { + switch (item) { + case 'content': + this.renderContent(control); + break; + default: + control.tagsValid = false; + break; + } + }, + + /** + * @private + */ + render: function (control) { + if (control.parent) { + control.parent.beforeChildRender(control); + + if (!control.parent.generated) return; + if (control.tag === null) return control.parent.render(); + } + + if (!control.hasNode()) this.renderNode(control); + if (control.hasNode()) { + this.renderDom(control); + if (control.generated) control.rendered(); + } + }, + + /** + * @private + */ + renderInto: function (control, parentNode) { + parentNode.innerHTML = this.generateHtml(control); + + if (control.generated) control.rendered(); + }, + + /** + * @private + */ + renderNode: function (control) { + this.teardownRender(control); + control.node = document.createElement(control.tag); + control.addNodeToParent(); + control.set('generated', true); + }, + + /** + * @private + */ + renderDom: function (control) { + this.renderAttributes(control); + this.renderStyles(control); + this.renderContent(control); + }, + + /** + * @private + */ + renderStyles: function (control) { + var style = control.style; + + // we can safely do this knowing it will synchronize properly without a double + // set in the DOM because we're flagging the internal property + if (control.hasNode()) { + control.node.style.cssText = style; + // retrieve the parsed value for synchronization + control.cssText = style = control.node.style.cssText; + // now we set it knowing they will be synchronized and everybody that is listening + // will also be updated to know about the change + control.set('style', style); + } + }, + + /** + * @private + */ + renderAttributes: function (control) { + var attrs = control.attributes, + node = control.hasNode(), + key, + val; + + if (node) { + for (key in attrs) { + val = attrs[key]; + if (val === null || val === false || val === "") { + node.removeAttribute(key); + } else { + node.setAttribute(key, val); + } + } + } + }, + + /** + * @private + */ + renderContent: function (control) { + if (control.generated) this.teardownChildren(control); + if (control.hasNode()) control.node.innerHTML = this.generateInnerHtml(control); + }, + + /** + * @private + */ + generateHtml: function (control) { + var content, + html; + + if (control.canGenerate === false) { + return ''; + } + // do this first in case content generation affects outer html (styles or attributes) + content = this.generateInnerHtml(control); + // generate tag, styles, attributes + html = this.generateOuterHtml(control, content); + // NOTE: 'generated' is used to gate access to findNodeById in + // hasNode, because findNodeById is expensive. + // NOTE: we typically use 'generated' to mean 'created in DOM' + // but that has not actually happened at this point. + // We set 'generated = true' here anyway to avoid having to walk the + // control tree a second time (to set it later). + // The contract is that insertion in DOM will happen synchronously + // to generateHtml() and before anybody should be calling hasNode(). + control.set('generated', true); + return html; + }, + + /** + * @private + */ + generateOuterHtml: function (control, content) { + if (!control.tag) return content; + if (!control.tagsValid) this.prepareTags(control); + return control._openTag + content + control._closeTag; + }, + + /** + * @private + */ + generateInnerHtml: function (control) { + var allowHtml = control.allowHtml, + content; + + // flow can alter the way that html content is rendered inside + // the container regardless of whether there are children. + control.flow(); + if (control.children.length) return this.generateChildHtml(control); + else { + content = control.get('content'); + return allowHtml ? content : Dom.escape(content); + } + }, + + /** + * @private + */ + generateChildHtml: function (control) { + var child, + html = '', + i = 0, + delegate; + + for (; (child = control.children[i]); ++i) { + delegate = child.renderDelegate || this; + html += delegate.generateHtml(child); + } + + return html; + }, + + /** + * @private + */ + prepareTags: function (control) { + var html = ''; + + // open tag + html += '<' + control.tag + (control.style ? ' style="' + control.style + '"' : ''); + html += this.attributesToHtml(control.attributes); + if (selfClosing[control.tag]) { + control._openTag = html + '/>'; + control._closeTag = ''; + } else { + control._openTag = html + '>'; + control._closeTag = ''; + } + + control.tagsValid = true; + }, + + /** + * @private + */ + attributesToHtml: function(attrs) { + var key, + val, + html = ''; + + for (key in attrs) { + val = attrs[key]; + if (val != null && val !== false && val !== '') { + html += ' ' + key + '="' + this.escapeAttribute(val) + '"'; + } + } + + return html; + }, + + /** + * @private + */ + escapeAttribute: function (text) { + if (typeof text != 'string') return text; + + return String(text).replace(/&/g, '&').replace(/\"/g, '"'); + }, + + /** + * @private + */ + teardownRender: function (control, cache) { + if (control.generated) this.teardownChildren(control, cache); + control.node = null; + control.set('generated', false); + }, + + /** + * @private + */ + teardownChildren: function (control, cache) { + var child, + i = 0; + + for (; (child = control.children[i]); ++i) { + child.teardownRender(cache); + } + } +}; + +},{'./dom':'enyo/dom'}],'enyo/gesture/util':[function (module,exports,global,require,request){ +var + dom = require('../dom'), + platform = require('../platform'), + utils = require('../utils'); + +/** +* Used internally by {@link module:enyo/gesture} +* +* @module enyo/gesture/util +* @private +*/ +module.exports = { + + /** + * @private + */ + eventProps: ['target', 'relatedTarget', 'clientX', 'clientY', 'pageX', 'pageY', + 'screenX', 'screenY', 'altKey', 'ctrlKey', 'metaKey', 'shiftKey', + 'detail', 'identifier', 'dispatchTarget', 'which', 'srcEvent'], + + /** + * Creates an {@glossary event} of type `type` and returns it. + * `evt` should be an event [object]{@glossary Object}. + * + * @param {String} type - The type of {@glossary event} to make. + * @param {(Event|Object)} evt - The event you'd like to clone or an object that looks like it. + * @returns {Object} The new event [object]{@glossary Object}. + * @public + */ + makeEvent: function(type, evt) { + var e = {}; + e.type = type; + for (var i=0, p; (p=this.eventProps[i]); i++) { + e[p] = evt[p]; + } + e.srcEvent = e.srcEvent || evt; + e.preventDefault = this.preventDefault; + e.disablePrevention = this.disablePrevention; + + if (dom._bodyScaleFactorX !== 1 || dom._bodyScaleFactorY !== 1) { + // Intercept only these events, not all events, like: hold, release, tap, etc, + // to avoid doing the operation again. + if (e.type == 'move' || e.type == 'up' || e.type == 'down' || e.type == 'enter' || e.type == 'leave') { + e.clientX *= dom._bodyScaleFactorX; + e.clientY *= dom._bodyScaleFactorY; + } + } + // + // normalize event.which and event.pageX/event.pageY + // Note that while 'which' works in IE9, it is broken for mousemove. Therefore, + // in IE, use global.event.button + if (platform.ie < 10) { + //Fix for IE8, which doesn't include pageX and pageY properties + if(platform.ie==8 && e.target) { + e.pageX = e.clientX + e.target.scrollLeft; + e.pageY = e.clientY + e.target.scrollTop; + } + var b = global.event && global.event.button; + if (b) { + // multi-button not supported, priority: left, right, middle + // (note: IE bitmask is 1=left, 2=right, 4=center); + e.which = b & 1 ? 1 : (b & 2 ? 2 : (b & 4 ? 3 : 0)); + } + } else if (platform.webos || global.PalmSystem) { + // Temporary fix for owos: it does not currently supply 'which' on move events + // and the user agent string doesn't identify itself so we test for PalmSystem + if (e.which === 0) { + e.which = 1; + } + } + return e; + }, + + /** + * Installed on [events]{@glossary event} and called in event context. + * + * @private + */ + preventDefault: function() { + if (this.srcEvent) { + this.srcEvent.preventDefault(); + } + }, + + /** + * @private + */ + disablePrevention: function() { + this.preventDefault = utils.nop; + if (this.srcEvent) { + this.srcEvent.preventDefault = utils.nop; + } + } +}; + +},{'../dom':'enyo/dom','../platform':'enyo/platform','../utils':'enyo/utils'}],'enyo/AnimationSupport/Frame':[function (module,exports,global,require,request){ +require('enyo'); + +var + Dom = require('../dom'), + Vector = require('./Vector'), + Matrix = require('./Matrix'); + +var + COLOR = {"color": 1, "background-color": 1}, + TRANSFORM = {"translate": 1, "translateX": 1, "translateY": 1, "translateZ": 1, "rotateX": 1, "rotateY": 1, "rotateZ": 1, "rotate": 1, "skew": 1, "scale": 1, "perspective": 1}; + +/** +* Frame is a module responsible for providing animation features required for a frame. +* This module exposes bunch of animation API's like matrix calculation, +* fetching inital DOM properties and also applying style updates to DOM. +* +* These methods need to be merged with DOM API's of enyo. +* +* @module enyo/AnimationSupport/Frame +*/ +var frame = module.exports = { + /** + * @public + * Creates a matrix based on transformation vectors. + * @param: trns- translate vector + * rot - rotate quaternion vector + * sc - scale vector + * sq - sqew vector + * per - perspective vector + */ + recomposeMatrix: function(trns, rot, sc, sq, per) { + var i, + x = rot[0], + y = rot[1], + z = rot[2], + w = rot[3], + m = Matrix.identity(), + sM = Matrix.identity(), + rM = Matrix.identity(); + + // apply perspective + if(per) { + m[3] = per[0]; + m[7] = per[1]; + m[11] = per[2]; + m[15] = per[3]; + } + + m[12] = trns[0]; + m[13] = trns[1]; + m[14] = trns[2]; + + // apply rotate + rM[0] = 1 - 2 * (y * y + z * z); + rM[1] = 2 * (x * y - z * w); + rM[2] = 2 * (x * z + y * w); + rM[4] = 2 * (x * y + z * w); + rM[5] = 1 - 2 * (x * x + z * z); + rM[6] = 2 * (y * z - x * w); + rM[8] = 2 * (x * z - y * w); + rM[9] = 2 * (y * z + x * w); + rM[10] = 1 - 2 * (x * x + y * y); + + m = Matrix.multiply(m, rM); + + // apply skew + if (sq[2]) { + sM[9] = sq[2]; + m = Matrix.multiply(m, sM); + } + + if (sq[1]) { + sM[9] = 0; + sM[8] = sq[1]; + m = Matrix.multiply(m, sM); + } + + if (sq[0]) { + sM[8] = 0; + sM[4] = sq[0]; + m = Matrix.multiply(m, sM); + } + + // apply scale + for (i = 0; i < 12; i += 4) { + m[0 + i] *= sc[0]; + m[1 + i] *= sc[1]; + m[2 + i] *= sc[2]; + } + return m; + }, + + /** + * @public + * Get transformation vectors out of matrix + * @param matrix - Transformation matrix + * ret - Return object which holds translate, + * rotate, scale, sqew & perspective. + */ + decomposeMatrix: function(matrix, ret) { + var i, + tV = [], + rV = [], + pV = [], + skV = [], + scV = [], + row = [], + pdum3 = {}; + + if (matrix[15] === 0) return false; + + for (i = 0; i < 16; i++) + matrix[0] /= matrix[15]; + + //TODO: decompose perspective + pV = [0, 0, 0, 0]; + + for (i = 0; i < 3; i++) + tV[i] = matrix[12 + i]; + + for (i = 0; i < 12; i += 4) { + row.push([ + matrix[0 + i], + matrix[1 + i], + matrix[2 + i] + ]); + } + + scV[0] = Vector.len(row[0]); + row[0] = Vector.normalize(row[0]); + skV[0] = Vector.dot(row[0], row[1]); + row[1] = Vector.combine(row[1], row[0], 1.0, -skV[0]); + + scV[1] = Vector.len(row[1]); + row[1] = Vector.normalize(row[1]); + skV[0] /= scV[1]; + + // Compute XZ and YZ shears, orthogonalize 3rd row + skV[1] = Vector.dot(row[0], row[2]); + row[2] = Vector.combine(row[2], row[0], 1.0, -skV[1]); + skV[2] = Vector.dot(row[1], row[2]); + row[2] = Vector.combine(row[2], row[1], 1.0, -skV[2]); + + // Next, get Z scale and normalize 3rd row. + scV[2] = Vector.len(row[2]); + row[2] = Vector.normalize(row[2]); + skV[1] /= scV[2]; + skV[2] /= scV[2]; + + pdum3 = Vector.cross(row[1], row[2]); + if (Vector.dot(row[0], pdum3) < 0) { + for (i = 0; i < 3; i++) { + scV[i] *= -1; + row[i][0] *= -1; + row[i][1] *= -1; + row[i][2] *= -1; + } + } + + rV[0] = 0.5 * Math.sqrt(Math.max(1 + row[0][0] - row[1][1] - row[2][2], 0)); + rV[1] = 0.5 * Math.sqrt(Math.max(1 - row[0][0] + row[1][1] - row[2][2], 0)); + rV[2] = 0.5 * Math.sqrt(Math.max(1 - row[0][0] - row[1][1] + row[2][2], 0)); + rV[3] = 0.5 * Math.sqrt(Math.max(1 + row[0][0] + row[1][1] + row[2][2], 0)); + + if (row[2][1] > row[1][2]) rV[0] = -rV[0]; + if (row[0][2] > row[2][0]) rV[1] = -rV[1]; + if (row[1][0] > row[0][1]) rV[2] = -rV[2]; + + ret.translate = tV; + ret.rotate = rV; + ret.scale = scV; + ret.skew = skV; + ret.perspective = pV; + return true; + }, + + /** + * Clones an array based on offset value. + * @public + */ + copy: function(v, offset) { + return Array.prototype.slice.call(v, offset || 0); + }, + + /** + * Validates if property is a transform property. + * @public + */ + isTransform: function(transform) { + return TRANSFORM[transform]; + }, + + /** + * Applies trasnformation to DOM element with the matrix values. + * @public + */ + accelerate: function (ele, m) { + m = m ? m : Matrix.identity(); + frame.setTransformProperty(ele, m); + }, + + /** + * Reform matrix 2D to 3D + * @public + */ + parseMatrix: function (v) { + var m = Matrix.identity(); + v = v.replace(/^\w*\(/, '').replace(')', ''); + v = this.parseValue(v); + if (v.length <= 6) { + m[0] = v[0]; + m[1] = v[1]; + m[4] = v[2]; + m[5] = v[3]; + m[12] = v[4]; + m[13] = v[5]; + } else { + m = v; + } + return m; + }, + + /** + * Converts comma seperated values to array. + * @public + */ + parseValue: function (val) { + return val.toString().split(",").map(function(v) { + return parseFloat(v, 10); + }); + }, + + /** + * Gets a matrix for DOM element. + * @public + */ + getMatrix: function (style) { + var m = style.getPropertyValue('transform') || + style.getPropertyValue('-moz-transform') || + style.getPropertyValue('-webkit-transform') || + style.getPropertyValue('-ms-transform') || + style.getPropertyValue('-o-transform'); + if (m === undefined || m === null || m == "none") { + return ""; + } + return this.parseMatrix(m); + }, + + /** + * Gets a style property applied from the DOM element. + * @param: style - Computed style of a DOM. + * key - property name for which style has to be fetched. + * @public + */ + getStyleValue: function (style, key) { + var v = style.getPropertyValue(key); + if (v === undefined || v === null || v == "auto" || isNaN(v)) { + return 0; + } + if (COLOR[key]) { + return v.replace(/^\w*\(/, '').replace(')', ''); + } + v = parseFloat(v, 10); + return v; + }, + + + /** + * Applies style property to DOM element. + * @public + */ + setProperty: function (ele, prop, val) { + if (COLOR[prop]) { + val = val.map(function(v) { return parseInt(v, 10);}); + val = 'rgb('+ val + ')'; + } else if (prop == 'opacity') { + val = val[0].toFixed(6); + val = (val <= 0) ? '0.000001' : val; + } else { + val = val[0] + 'px'; + } + ele.style[prop] = val; + }, + + /** + * Applies transform property to DOM element. + * @public + */ + setTransformProperty: function (element, matrix) { + var mat = Matrix.toString(matrix); + element.style.transform = mat; + element.style.webkitTransform = mat; + element.style.MozTransform = mat; + element.style.msTransform = mat; + element.style.OTransform = mat; + }, + + /** + * Get DOM node animation properties. + * @param: node- DOM node + * props- Properties to fetch from DOM. + * initial-Default properties to be applied. + * @public + */ + getCompoutedProperty: function (node, props, initial) { + if(!node || !props) return; + + var eP = {}, + sP = initial ? this.copy(initial) : {}, + tP = {}, + dP = {}, + m, k, v, + s = Dom.getComputedStyle(node); + + for (k in props) { + v = sP[k]; + if (!this.isTransform(k)) { + v = v || this.getStyleValue(s, k); + eP[k] = this.parseValue(props[k]); + sP[k] = this.parseValue(v); + } else { + v = this.parseValue(props[k]); + tP[k] = k == 'rotate' ? Vector.toQuant(v) : v; + } + } + + if (initial) { + dP.translate = initial.translate; + dP.rotate = initial.rotate; + dP.scale = initial.scale; + dP.skew = initial.skew; + dP.perspective = initial.perspective; + } else { + m = this.getMatrix(s) || Matrix.identity(); + this.decomposeMatrix(m, dP); + } + + for(k in dP) { + sP[k] = dP[k]; + eP[k] = tP[k] || dP[k]; + } + return {_startAnim: sP, _endAnim: eP, _transform: dP, currentState: dP}; + } +}; +},{'../dom':'enyo/dom','./Vector':'enyo/AnimationSupport/Vector','./Matrix':'enyo/AnimationSupport/Matrix'}],'enyo/Control/floatingLayer':[function (module,exports,global,require,request){ +/** +* Exports the {@link module:enyo/Control/floatingLayer~FloatingLayer} singleton instance. +* @module enyo/Control/floatingLayer +*/ + +var + kind = require('../kind'), + platform = require('../platform'); + +module.exports = function (Control) { + /** + * {@link module:enyo/Control/floatingLayer~FloatingLayer} is a + * [control]{@link module:enyo/Control~Control} that provides a layer for controls that should be + * displayed above an [application]{@link module:enyo/Application~Application}. The `floatingLayer` + * singleton can be set as a control's parent to have the control float above the application, e.g.: + * + * ``` + * var floatingLayer = require('enyo/floatingLayer'); + * ... + * create: kind.inherit(function (sup) { + * return function() { + * sup.apply(this, arguments); + * this.setParent(floatingLayer); + * } + * }); + * ``` + * + * Note: `FloatingLayer` is not meant to be instantiated by users. + * + * @class FloatingLayer + * @extends module:enyo/Control~Control + * @ui + * @protected + */ + var FloatingLayer = kind( + /** @lends module:enyo/Control/floatingLayer~FloatingLayer.prototype */ { + + /** + * @private + */ + kind: Control, + + /** + * @private + */ + classes: 'enyo-fit enyo-clip enyo-untouchable', + + /** + * @private + */ + accessibilityPreventScroll: true, + + /** + * @method + * @private + */ + create: kind.inherit(function (sup) { + return function() { + sup.apply(this, arguments); + this.setParent(null); + + if (platform.ie < 11) { + this.removeClass('enyo-fit'); + } + }; + }), + + /** + * Detects when [node]{@glossary Node} is detatched due to `document.body` being stomped. + * + * @method + * @private + */ + hasNode: kind.inherit(function (sup) { + return function() { + sup.apply(this, arguments); + if (this.node && !this.node.parentNode) { + this.teardownRender(); + } + return this.node; + }; + }), + + /** + * @method + * @private + */ + render: kind.inherit(function (sup) { + return function() { + this.parentNode = document.body; + return sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + generateInnerHtml: function () { + return ''; + }, + + /** + * @private + */ + beforeChildRender: function () { + if (!this.hasNode()) { + this.render(); + } + }, + + /** + * @private + */ + teardownChildren: function () { + } + }); + + return FloatingLayer; +}; +},{'../kind':'enyo/kind','../platform':'enyo/platform'}],'enyo/MixinSupport':[function (module,exports,global,require,request){ +/** +* Exports the {@link module:enyo/MixinSupport~MixinSupport} mixin. +* @module enyo/MixinSupport +*/ + +require('enyo'); + + +var + utils = require('./utils'), + kind = require('./kind'), + logger = require('./logger'); + +kind.concatenated.push('mixins'); + +var sup = kind.statics.extend; + +var extend = kind.statics.extend = function extend (args, target) { + if (utils.isArray(args)) return utils.forEach(args, function (ln) { extend.call(this, ln, target); }, this); + if (typeof args == 'string') apply(target || this.prototype, args); + else { + if (args.mixins) feature(target || this, args); + + // this allows for mixins to apply mixins which...is less than ideal but possible + if (args.name) apply(target || this.prototype, args); + else sup.apply(this, arguments); + } +}; + +/* +* Applies, with safeguards, a given mixin to an object. +*/ +function apply (proto, props) { + var applied = proto._mixins? (proto._mixins = proto._mixins.slice()): (proto._mixins = []) + , name = utils.isString(props)? props: props.name + , idx = utils.indexOf(name, applied); + if (idx < 0) { + name == props && (props = utils.getPath(name)); + // if we could not resolve the requested mixin (should never happen) + // we throw a simple little error + // @TODO: Normalize error format + !props && logger.error('Could not find the mixin ' + name); + + // it should be noted that this ensures it won't recursively re-add the same mixin but + // since it is possible for mixins to apply mixins the names will be out of order + // this name is pushed on but the nested mixins are applied before this one + name && applied.push(name); + + props = utils.clone(props); + + // we need to temporarily move the constructor if it has one so it + // will override the correct method - this is a one-time permanent + // runtime operation so subsequent additions of the mixin don't require + // it again + if (props.hasOwnProperty('constructor')) { + props._constructor = props.constructor; + delete props.constructor; + } + + delete props.name; + extend(props, proto); + + // now put it all back the way it was + props.name = name; + } +} + +function feature (ctor, props) { + if (props.mixins) { + var proto = ctor.prototype || ctor + , mixins = props.mixins; + + // delete props.mixins; + // delete proto.mixins; + + proto._mixins && (proto._mixins = proto._mixins.slice()); + utils.forEach(mixins, function (ln) { apply(proto, ln); }); + } +} + +kind.features.push(feature); + +/** +* An internally-used support {@glossary mixin} that adds API methods to aid in +* using and applying mixins to [kinds]{@glossary kind}. +* +* @mixin +* @protected +*/ +var MixinSupport = { + + /** + * @private + */ + name: 'MixinSupport', + + /** + * Extends the instance with the given properties. + * + * @param {Object} props - The property [hash]{@glossary Object} from which to extend + * the callee. + */ + extend: function (props) { + props && apply(this, props); + }, + + /** + * @private + */ + importProps: kind.inherit(function (sup) { + return function (props) { + props && props.mixins && feature(this, props); + + sup.apply(this, arguments); + }; + }) +}; + +module.exports = MixinSupport; + +},{'./utils':'enyo/utils','./kind':'enyo/kind','./logger':'enyo/logger'}],'enyo/ComputedSupport':[function (module,exports,global,require,request){ +/** +* Exports the {@link module:enyo/ComputedSupport~ComputedSupport} mixin. +* @module enyo/ComputedSupport +*/ + +require('enyo'); + +var + kind = require('./kind'), + utils = require('./utils'); + +var extend = kind.statics.extend; + +kind.concatenated.push('computed'); + +function getComputedValue (obj, path) { + var cache = obj._getComputedCache(path) + , isCached = obj._isComputedCached(path); + + // in the end, for efficiency and completeness in other situations + // it is better to know the returned value of all computed properties + // but in cases where they are set as cached we will sometimes use + // that value + if (cache.dirty || cache.dirty === undefined) { + isCached && (cache.dirty = false); + cache.previous = cache.value; + cache.value = obj[path](); + } + + return cache.value; +} + +function queueComputed (obj, path) { + var queue = obj._computedQueue || (obj._computedQueue = []) + , deps = obj._computedDependencies[path]; + + if (deps) { + for (var i=0, dep; (dep=deps[i]); ++i) { + if (!queue.length || -1 == queue.indexOf(dep)) queue.push(dep); + } + } +} + +function flushComputed (obj) { + var queue = obj._computedQueue; + obj._computedQueue = null; + if (queue && obj.isObserving()) { + for (var i=0, ln; (ln=queue[i]); ++i) { + obj.notify(ln, obj._getComputedCache(ln).value, getComputedValue(obj, ln)); + } + } +} + +/** +* A {@glossary mixin} that adds API methods to support +* [computed properties]{@glossary computed_property}. Unlike other support mixins, +* this mixin does not need to be explicitly included by a [kind]{@glossary kind}. If the +* `computed` [array]{@glossary Array} is found in a kind definition, this mixin will +* automatically be included. +* +* @mixin +* @public +*/ +var ComputedSupport = { + /** + * @private + */ + name: 'ComputedSuport', + + /** + * @private + */ + _computedRecursion: 0, + + /** + * Primarily intended for internal use, this method determines whether the + * given path is a known [computed property]{@glossary computed_property}. + * + * @param {String} path - The property or path to test. + * @returns {Boolean} Whether or not the `path` is a + * [computed property]{@glossary computed_property}. + * @public + */ + isComputed: function (path) { + // if it exists it will be explicitly one of these cases and it is cheaper than hasOwnProperty + return this._computed && (this._computed[path] === true || this._computed[path] === false); + }, + + /** + * Primarily intended for internal use, this method determines whether the + * given path is a known dependency of a + * [computed property]{@glossary computed_property}. + * + * @param {String} path - The property or path to test. + * @returns {Boolean} Whether or not the `path` is a dependency of a + * [computed property]{@glossary computed_property}. + * @public + */ + isComputedDependency: function (path) { + return !! (this._computedDependencies? this._computedDependencies[path]: false); + }, + + /** + * @private + */ + get: kind.inherit(function (sup) { + return function (path) { + return this.isComputed(path)? getComputedValue(this, path): sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + set: kind.inherit(function (sup) { + return function (path) { + // we do not accept parameters for computed properties + return this.isComputed(path)? this: sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + notifyObservers: function () { + return this.notify.apply(this, arguments); + }, + + /** + * @private + */ + notify: kind.inherit(function (sup) { + return function (path, was, is) { + this.isComputedDependency(path) && queueComputed(this, path); + this._computedRecursion++; + sup.apply(this, arguments); + this._computedRecursion--; + this._computedQueue && this._computedRecursion === 0 && flushComputed(this); + return this; + }; + }), + + /** + * @private + */ + _isComputedCached: function (path) { + return this._computed[path]; + }, + + /** + * @private + */ + _getComputedCache: function (path) { + var cache = this._computedCache || (this._computedCache = {}); + return cache[path] || (cache[path] = {}); + } +}; + +module.exports = ComputedSupport; + +/* +* Hijack the original so we can add additional default behavior. +*/ +var sup = kind.concatHandler; + +// @NOTE: It seems like a lot of work but it really won't happen that much and the more +// we push to kind-time the better for initialization time + +/** +* @private +*/ +kind.concatHandler = function (ctor, props, instance) { + + sup.call(this, ctor, props, instance); + + // only matters if there are computed properties to manage + if (props.computed) { + + var proto = ctor.prototype || ctor + , computed = proto._computed? Object.create(proto._computed): {} + , dependencies = proto._computedDependencies? Object.create(proto._computedDependencies): {}; + + // if it hasn't already been applied we need to ensure that the prototype will + // actually have the computed support mixin present, it will not apply it more + // than once to the prototype + extend(ComputedSupport, proto); + + // @NOTE: This is the handling of the original syntax provided for computed properties in 2.3.ish... + // All we do here is convert it to a structure that can be used for the other scenario and preferred + // computed declarations format + if (!props.computed || !(props.computed instanceof Array)) { + (function () { + var tmp = [], deps, name, conf; + // here is the slow iteration over the properties... + for (name in props.computed) { + // points to the dependencies of the computed method + deps = props.computed[name]; + /*jshint -W083 */ + conf = deps && deps.find(function (ln) { + // we deliberately remove the entry here and forcibly return true to break + return typeof ln == 'object'? (utils.remove(deps, ln) || true): false; + }); + /*jshint +W083 */ + // create a single entry now for the method/computed with all dependencies + tmp.push({method: name, path: deps, cached: conf? conf.cached: null}); + } + + // note that we only do this one so even for a mixin that is evaluated several + // times this would only happen once + props.computed = tmp; + }()); + } + + var addDependency = function (path, dep) { + // its really an inverse look at the original + var deps; + + if (dependencies[path] && !dependencies.hasOwnProperty(path)) dependencies[path] = dependencies[path].slice(); + deps = dependencies[path] || (dependencies[path] = []); + deps.push(dep); + }; + + // now we handle the new computed properties the way we intended to + for (var i=0, ln; (ln=props.computed[i]); ++i) { + // if the entry already exists we are merely updating whether or not it is + // now cached + computed[ln.method] = !! ln.cached; + // we must now look to add an entry for any given dependencies and map them + // back to the computed property they will trigger + /*jshint -W083 */ + if (ln.path && ln.path instanceof Array) ln.path.forEach(function (dep) { addDependency(dep, ln.method); }); + /*jshint +W083 */ + else if (ln.path) addDependency(ln.path, ln.method); + } + + // arg, free the key from the properties so it won't be applied later... + // delete props.computed; + // make sure to reassign the correct items to the prototype + proto._computed = computed; + proto._computedDependencies = dependencies; + } +}; + +},{'./kind':'enyo/kind','./utils':'enyo/utils'}],'enyo/Source':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Source~Source} kind. +* @module enyo/Source +*/ + +var + kind = require('./kind'), + utils = require('./utils'), + logger = require('./logger'); + +/** +* All of the known, instanced [sources]{@link module:enyo/Source~Source}, by name. +* +* @name enyo~sources +* @type {Object} +* @readonly +*/ +var sources = {}; + +/** +* This is an abstract base class. A [source]{@link module:enyo/Source~Source} is a communication +* layer used by data layer [kinds]{@glossary kind} to retrieve and persist data and +* application state via its abstract API methods. +* +* @class Source +* @public +*/ +var Source = module.exports = kind( + /** @lends module:enyo/Source~Source.prototype */ { + + name: 'enyo.Source', + + /** + * @private + */ + kind: null, + + /** + * @private + */ + + + /** + * When initialized, the source should be passed properties to set on itself. + * These properties should include the name by which it will be referenced in + * the application. + * + * @param {Object} [props] - The properties to set on itself. + * @public + */ + constructor: function (props) { + if (props) this.importProps(props); + // automatic coersion of name removing prefix + this.name || (this.name = this.kindName.replace(/^(.*)\./, "")); + // now add to the global registry of sources + sources[this.name] = this; + }, + + /** + * Overload this method to handle retrieval of data. This method should accept an options + * [hash]{@glossary Object} with additional configuration properties, including `success` + * and `error` callbacks to handle the result. + * + * @virtual + * @param {(module:enyo/Model~Model|module:enyo/Collection~Collection)} model The [model]{@link module:enyo/Model~Model} or + * [collection]{@link module:enyo/Collection~Collection} to be retrieved. + * @param {Object} opts - The configuration options [hash]{@glossary Object}, including + * `success` and `error` callbacks. + */ + fetch: function (model, opts) { + // + }, + + /** + * Overload this method to handle persisting of data. This method should accept an options + * [hash]{@glossary Object} with additional configuration properties, including `success` + * and `error` callbacks to handle the result. + * + * @virtual + * @param {(module:enyo/Model~Model|module:enyo/Collection~Collection)} model The [model]{@link module:enyo/Model~Model} or + * [collection]{@link module:enyo/Collection~Collection} to be persisted. + * @param {Object} opts - The configuration options [hash]{@glossary Object}, including + * `success` and `error` callback. + */ + commit: function (model, opts) { + // + }, + + /** + * Overload this method to handle deletion of data. This method should accept an options + * [hash]{@glossary Object} with additional configuration properties, including `success` + * and `error` callbacks to handle the result. If called without parameters, it will + * instead destroy itself and be removed from [enyo.sources]{@link enyo~sources}, rendering + * itself unavailable for further operations. + * + * @param {(module:enyo/Model~Model|module:enyo/Collection~Collection)} model The [model]{@link module:enyo/Model~Model} or + * [collection]{@link module:enyo/Collection~Collection} to be deleted. + * @param {Object} opts - The configuration options [hash]{@glossary Object}, including + * `success` and `error` callbacks. + */ + destroy: function (model, opts) { + + // if called with no parameters we actually just breakdown the source and remove + // it as being available + if (!arguments.length) { + sources[this.name] = null; + this.name = null; + } + }, + + /** + * Overload this method to handle querying of data based on the passed-in constructor. This + * method should accept an options [hash]{@glossary Object} with additional configuration + * properties, including `success` and `error` callbacks to handle the result. + * + * @virtual + * @param {Function} ctor - The constructor for the [kind]{@glossary kind} of + * {@link module:enyo/Model~Model} or {@link module:enyo/Collection~Collection} to be queried. + * @param {Object} opts - The configuration options [hash]{@glossary Object}, including + * `success` and `error` callbacks. + */ + find: function (ctor, opts) { + // + }, + + /** + * @private + */ + importProps: function (props) { + props && utils.mixin(this, props); + }, + + /** + * @see module:enyo/utils#getPath + * @method + * @public + */ + get: utils.getPath, + + /** + * @see module:enyo/utils#setPath + * @method + * @public + */ + set: utils.setPath +}); + +/** +* Creates an instance of {@link module:enyo/Source~Source} with the given properties. These +* properties should include a `kind` property with the name of the +* [kind]{@glossary kind} of source and a `name` for the instance. This static +* method is also available on all [subkinds]{@glossary subkind} of +* `enyo.Source`. The instance will automatically be added to the +* [enyo.sources]{@link enyo~sources} [object]{@glossary Object} and may be +* referenced by its `name`. +* +* @name enyo.Source.create +* @static +* @method +* @param {Object} props - The properties to pass to the constructor for the requested +* [kind]{@glossary kind} of [source]{@link module:enyo/Source~Source}. +* @returns {module:enyo/Source~Source} An instance of the requested kind of source. +* @public +*/ +Source.create = function (props) { + var Ctor = (props && props.kind) || this; + + if (typeof Ctor == 'string') Ctor = kind.constructorForKind(Ctor); + + return new Ctor(props); +}; + +/** +* @static +* @private +*/ +Source.concat = function (ctor, props) { + + // force noDefer so that we can actually set this method on the constructor + if (props) props.noDefer = true; + + ctor.create = Source.create; +}; + +/** +* @static +* @private +*/ +Source.execute = function (action, model, opts) { + var source = opts.source || model.source, + + // we need to be able to bind the success and error callbacks for each of the + // sources we'll be using + options = utils.clone(opts, true), + nom = source, + msg; + + if (source) { + + // if explicitly set to true then we need to use all available sources in the + // application + if (source === true) { + + for (nom in sources) { + source = sources[nom]; + if (source[action]) { + + // bind the source name to the success and error callbacks + options.success = opts.success.bind(null, nom); + options.error = opts.error.bind(null, nom); + + source[action](model, options); + } + } + } + + // if it is an array of specific sources to use we, well, will only use those! + else if (source instanceof Array) { + source.forEach(function (nom) { + var src = typeof nom == 'string' ? sources[nom] : nom; + + if (src && src[action]) { + // bind the source name to the success and error callbacks + options.success = opts.success.bind(null, src.name); + options.error = opts.error.bind(null, src.name); + + src[action](model, options); + } + }); + } + + // if it is an instance of a source + else if (source instanceof Source && source[action]) { + + // bind the source name to the success and error callbacks + options.success = opts.success.bind(null, source.name); + options.error = opts.error.bind(null, source.name); + + source[action](model, options); + } + + // otherwise only one was specified and we attempt to use that + else if ((source = sources[nom]) && source[action]) { + + // bind the source name to the success and error callbacks + options.success = opts.success.bind(null, nom); + options.error = opts.error.bind(null, nom); + + source[action](model, options); + } + + // we could not resolve the requested source + else { + msg = 'enyo.Source.execute(): requested source(s) could not be found for ' + + model.kindName + '.' + action + '()'; + + logger.warn(msg); + + // we need to fail the attempt and let it be handled + opts.error(nom ? typeof nom == 'string' ? nom : nom.name : 'UNKNOWN', msg); + } + } else { + msg = 'enyo.Source.execute(): no source(s) provided for ' + model.kindName + '.' + + action + '()'; + + logger.warn(msg); + + // we need to fail the attempt and let it be handled + opts.error(nom ? typeof nom == 'string' ? nom : nom.name : 'UNKNOWN', msg); + } +}; + +Source.sources = sources; + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./logger':'enyo/logger'}],'enyo/ApplicationSupport':[function (module,exports,global,require,request){ +/** +* Exports the {@link module:enyo/ApplicationSupport~ApplicationSupport} mixin. +* @module enyo/ApplicationSupport +*/ + +require('enyo'); + +var kind = require('./kind'); + +/** +* An internally-used support {@glossary mixin} that is applied to all +* [components]{@link module:enyo/Component~Component} of an {@link module:enyo/Application~Application} instance +* (and to their components, recursively). This mixin adds an `app` property to +* each component -- a local reference to the `Application` instance that +* the component belongs to. +* +* @mixin +* @protected +*/ +var ApplicationSupport = { + + /** + * @private + */ + name: 'ApplicationSupport', + + /** + * @private + */ + adjustComponentProps: kind.inherit(function (sup) { + return function (props) { + props.app = props.app || this.app; + sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + destroy: kind.inherit(function (sup) { + return function () { + // release the reference to the application + this.app = null; + sup.apply(this, arguments); + }; + }) + +}; + +module.exports = ApplicationSupport; + +},{'./kind':'enyo/kind'}],'enyo/ComponentBindingSupport':[function (module,exports,global,require,request){ +/** +* Exports the {@link module:enyo/ComponentBindingSupport~ComponentBindingSupport} mixin. +* @module enyo/ComponentBindingSupport +*/ + +require('enyo'); + +var + kind = require('./kind'); + +/** +* An internally-used {@glossary mixin} applied to {@link module:enyo/Component~Component} +* instances to better support [bindings]{@link module:enyo/Binding~Binding}. +* +* @mixin +* @protected +*/ +var ComponentBindingSupport = { + + /** + * @private + */ + name: 'ComponentBindingSupport', + + /** + * @private + */ + adjustComponentProps: kind.inherit(function (sup) { + return function (props) { + sup.apply(this, arguments); + props.bindingTransformOwner || (props.bindingTransformOwner = this.getInstanceOwner()); + }; + }) +}; + +module.exports = ComponentBindingSupport; + +},{'./kind':'enyo/kind'}],'enyo/MultipleDispatchSupport':[function (module,exports,global,require,request){ +/** +* Exports the {@link module:enyo/MultipleDispatchSupport~MultipleDispatchSupport} mixin. +* @module enyo/MultipleDispatchSupport +*/ + +require('enyo'); + +var + kind = require('./kind'), + utils = require('./utils'); + +/** +* A collection of methods to allow a single {@link module:enyo/Component~Component} to +* [dispatch]{@link module:enyo/Component~Component#dispatchEvent} a single {@glossary event} to +* multiple targets. The events are synchronously propagated in the order in +* which the targets are encountered. Note that this {@glossary mixin} is +* already applied to a base [kind]{@glossary kind}, +* {@link module:enyo/MultipleDispatchComponent~MultipleDispatchComponent}. +* +* @mixin +* @public +*/ +var MultipleDispatchSupport = { + + /** + * @private + */ + name: 'MultipleDispatchSupport', + + /** + * Adds a target for dispatching. + * + * @param {module:enyo/Component~Component} component - The {@link module:enyo/Component~Component} to add as a dispatch target. + * @public + */ + addDispatchTarget: function (component) { + var dt = this._dispatchTargets; + if (component && !~utils.indexOf(component, dt)) { + dt.push(component); + } + }, + /** + * Removes a target from dispatching. + * + * @param {module:enyo/Component~Component} component - The {@link module:enyo/Component~Component} to remove as a dispatch + * target. + * @public + */ + removeDispatchTarget: function (component) { + var dt = this._dispatchTargets, i; + i = utils.indexOf(component, dt); + if (i > -1) { + dt.splice(i, 1); + } + }, + + /** + * @private + */ + bubbleUp: kind.inherit(function (sup) { + return function (name, event, sender) { + if (this._dispatchDefaultPath) { + sup.apply(this, arguments); + } + var dt = this._dispatchTargets; + for (var i=0, t; (t=dt[i]); ++i) { + if (t && !t.destroyed) { + t.dispatchBubble(name, event, sender); + } + } + }; + }), + + /** + * @private + */ + ownerChanged: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + var o = this.owner; + this._dispatchDefaultPath = !! o; + }; + }), + + /** + * @private + */ + constructor: kind.inherit(function (sup) { + return function () { + this._dispatchTargets = []; + return sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + destroy: kind.inherit(function (sup) { + return function () { + this._dispatchTargets = null; + sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + _dispatchDefaultPath: false +}; + +module.exports = MultipleDispatchSupport; + +},{'./kind':'enyo/kind','./utils':'enyo/utils'}],'enyo/Binding':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Binding~Binding} kind. +* @module enyo/Binding +*/ + +var + kind = require('./kind'), + utils = require('./utils'); + +var bindings = []; + +var DIRTY_FROM = 0x01 + , DIRTY_TO = 0x02; + +/** +* Used to determine if an {@link module:enyo/Binding~Binding} is actually ready. +* +* @private +*/ +function ready (binding) { + var rdy = binding.ready; + + if (!rdy) { + + var from = binding.from || '', + to = binding.to || '', + source = binding.source, + target = binding.target, + owner = binding.owner, + twoWay = !binding.oneWay, + toTarget; + + if (typeof from != 'string') from = ''; + if (typeof to != 'string') to = ''; + + if (!source) { + + // the worst case scenario here is for backward compatibility purposes + // we have to at least be able to derive the source via the from string + if (from[0] == '^') { + + // this means we're reaching for a global + var fromParts = from.split('.'); + from = fromParts.pop(); + source = utils.getPath.call(global, fromParts.join('.').slice(1)); + + } else { + source = owner; + } + + } + + if (!target) { + + // same worst case as above, for backwards compatibility purposes + // we have to at least be able to derive the target via the to string + if (to[0] == '^') { + + // this means we're reaching for a global + var toParts = to.split('.'); + to = toParts.pop(); + target = utils.getPath.call(global, toParts.join('.').slice(1)); + } else { + target = owner; + } + } + + // we do this so we don't overwrite the originals in case we need to reset later + binding._target = target; + binding._source = source; + binding._from = from[0] == '.'? from.slice(1): from; + binding._to = to[0] == '.'? to.slice(1): to; + + if (!twoWay) { + toTarget = binding._to.split('.'); + if (toTarget.length > 2) { + toTarget.pop(); + binding._toTarget = toTarget.join('.'); + } + } + + // now our sanitization + rdy = !! ( + (source && (typeof source == 'object')) && + (target && (typeof target == 'object')) && + (from) && + (to) + ); + } + + /*jshint -W093 */ + return (binding.ready = rdy); + /*jshint +W093 */ +} + +var PassiveBinding = kind( + /** @lends enyo.PassiveBinding.prototype */ { + + name: 'enyo.PassiveBinding', + + /** + * @private + */ + kind: null, + + /** + * This property is used extensively for various purposes within a + * [binding]{@link module:enyo/Binding~Binding}. One primary purpose is to serve as a root + * [object]{@glossary Object} from which to search for the binding's ends (the + * [source]{@link module:enyo/Binding~Binding#source} and/or [target]{@link module:enyo/Binding~Binding#target}). + * If the owner created the binding, it will also be responsible for destroying + * it (automatically). + * + * @type {module:enyo/CoreObject~Object} + * @default null + * @public + */ + owner: null, + + /** + * Set this only to a reference for an [object]{@glossary Object} to use + * as the source for the [binding]{@link module:enyo/Binding~Binding}. If this is not a + * [bindable]{@link module:enyo/BindingSupport~BindingSupport} object, the source will be derived + * from the [from]{@link module:enyo/Binding~Binding#from} property during initialization. + * + * @type {Object} + * @default null + * @public + */ + source: null, + + /** + * Set this only to a reference for an [object]{@glossary Object} to use + * as the target for the [binding]{@link module:enyo/Binding~Binding}. If this is not a + * [bindable]{@link module:enyo/BindingSupport~BindingSupport} object, the target will will be + * derived from the [to]{@link module:enyo/Binding~Binding#to} property during initialization. + * + * @type {Object} + * @default null + * @public + */ + target: null, + + /** + * A path in which the property of the [source]{@link module:enyo/Binding~Binding#source} to + * bind from may be found. If the source is explicitly provided and the path + * is relative (i.e., it begins with a `"."`), it is relative to the source; + * otherwise, it is relative to the [owner]{@link module:enyo/Binding~Binding#owner} of the + * [binding]{@link module:enyo/Binding~Binding}. To have a binding be evaluated from the + * global scope, prefix the path with a `"^"`. If the source and the `"^"` + * are used in tandem, the `"^"` will be ignored and the path will be assumed + * to be relative to the provided source. + * + * @type {String} + * @default null + * @public + */ + from: null, + + /** + * A path in which the property of the [target]{@link module:enyo/Binding~Binding#target} to + * bind from may be found. If the target is explicitly provided and the path + * is relative (i.e., it begins with a `"."`), it is relative to the target; + * otherwise, it is relative to the owner of the [binding]{@link module:enyo/Binding~Binding}. + * To have a binding be evaluated from the global scope, prefix the path with + * a `"^"`. If the target and the `"^"` are used in tandem, the `"^"` will be + * ignored and the path will be assumed to be relative to the provided target. + * + * @type {String} + * @default null + * @public + */ + to: null, + + /** + * Set this to a [function]{@glossary Function} or the name of a method on + * the [owner]{@link module:enyo/Binding~Binding#owner} of this [binding]{@link module:enyo/Binding~Binding}. + * The transform is used to programmatically modify the value being synchronized. + * See {@link module:enyo/Binding~Binding~Transform} for detailed information on the parameters + * that are available to `transform`. + * + * @type {module:enyo/Binding~Binding~Transform} + * @default null + * @public + */ + transform: null, + + /** + * Indicates whether the [binding]{@link module:enyo/Binding~Binding} is actually ready. + * + * @returns {Boolean} `true` if ready; otherwise, `false`. + * @public + */ + isReady: function () { + return this.ready || ready(this); + }, + + /** + * Causes a single propagation attempt to fail. Typically not called outside + * the scope of a [transform]{@link module:enyo/Binding~Binding#transform}. + * + * @public + */ + stop: function () { + this._stop = true; + }, + + /** + * Resets all properties to their original state. + * + * @returns {this} The callee for chaining. + * @public + */ + reset: function () { + this.ready = null; + this._source = this._target = this._to = this._from = this._toTarget = null; + return this; + }, + + /** + * Rebuilds the entire [binding]{@link module:enyo/Binding~Binding} and synchronizes + * the value from the [source]{@link module:enyo/Binding~Binding#source} to the + * [target]{@link module:enyo/Binding~Binding#target}. + * + * @returns {this} The callee for chaining. + * @public + */ + rebuild: function () { + return this.reset().sync(); + }, + + /** + * Synchronizes values from the [source]{@link module:enyo/Binding~Binding#source} to the + * [target]{@link module:enyo/Binding~Binding#target}. This usually will not need to be called manually. + * [Two-way bindings]{@link module:enyo/Binding~Binding#oneWay} will automatically synchronize from the + * target end once they are connected. + * + * @returns {this} The callee for chaining. + * @public + */ + sync: function () { + var source, target, from, to, xform, val; + + if (this.isReady()) { + source = this._source; + target = this._target; + from = this._from; + to = this._to; + xform = this.getTransform(); + val = utils.getPath.apply(source, [from]); + + if (xform) val = xform.call(this.owner || this, val, DIRTY_FROM, this); + if (!this._stop) utils.setPath.apply(target, [to, val, {create: false}]); + } + + return this; + }, + + /** + * Releases all of the [binding's]{@link module:enyo/Binding~Binding} parts. Typically, this method will + * not need to be called directly unless the binding was created without an + * [owner]{@link module:enyo/Binding~Binding#owner}. + * + * @returns {this} The callee for chaining. + * @public + */ + destroy: function () { + var owner = this.owner, + idx; + + this.owner = null; + this.source = this._source = null; + this.target = this._target = null; + this.ready = null; + this.destroyed = true; + + // @todo: remove me or postpone operation? + idx = bindings.indexOf(this); + if (idx > -1) bindings.splice(idx, 1); + + if (owner && !owner.destroyed) owner.removeBinding(this); + + return this; + }, + + /** + * @private + */ + getTransform: function () { + return this._didInitTransform ? this.transform : (function (bnd) { + bnd._didInitTransform = true; + + var xform = bnd.transform, + owner = bnd.owner, + xformOwner = owner && owner.bindingTransformOwner; + + if (xform) { + if (typeof xform == 'string') { + if (xformOwner && xformOwner[xform]) { + xform = xformOwner[xform]; + } else if (owner && owner[xform]) { + xform = owner[xform]; + } else { + xform = utils.getPath.call(global, xform); + } + } + + /*jshint -W093 */ + return (bnd.transform = (typeof xform == 'function' ? xform : null)); + /*jshint +W093 */ + } + })(this); + }, + + /** + * @private + */ + constructor: function (props) { + bindings.push(this); + + if (props) utils.mixin(this, props); + + if (!this.euid) this.euid = utils.uid('b'); + + this.sync(); + } +}); + +/** +* The details for an {@link module:enyo/Binding~Binding#transform} [function]{@glossary Function}, +* including the available parameters and how they can be used. +* +* @callback module:enyo/Binding~Binding~Transform +* @param {*} value - The value being synchronized. +* @param {Number} direction - The direction of synchronization; will be either +* 1 (source value has changed and will be written to target) or 2 (target +* value has changed and will be written to source). +* @param {Object} binding - A reference to the associated [binding]{@link module:enyo/Binding~Binding}. In cases +* where the binding should be interrupted and not propagate the synchronization at all, call +* the [stop()]{@link module:enyo/Binding~Binding#stop} method on the passed-in binding reference. +*/ + +/** +* {@link module:enyo/Binding~Binding} is a mechanism used to keep properties synchronized. A +* binding may be used to link two properties on different +* [objects]{@glossary Object}, or even two properties on the same object. +* Once a binding has been established, it will wait for change notifications; +* when a notification arrives, the binding will synchronize the value between +* the two ends. Note that bindings may be either +* [one-way]{@link module:enyo/Binding~Binding#oneWay} (the default) or +* [two-way]{@link module:enyo/Binding~Binding#oneWay}. +* +* Usually, you will not need to create Binding objects arbitrarily, but will +* instead rely on the public [BindingSupport API]{@link module:enyo/BindingSupport~BindingSupport}, +* which is applied to [Object]{@link module:enyo/CoreObject~Object} and so is available on +* all of its [subkinds]{@glossary subkind}. +* +* @class Binding +* @public +*/ +exports = module.exports = kind( + /** @lends module:enyo/Binding~Binding.prototype */ { + + name: 'enyo.Binding', + + /** + * @private + */ + kind: PassiveBinding, + + /** + * If a [binding]{@link module:enyo/Binding~Binding} is one-way, this flag should be `true` (the default). + * If this flag is set to `false`, the binding will be two-way. + * + * @type {Boolean} + * @default true + * @public + */ + oneWay: true, + + /** + * If the [binding]{@link module:enyo/Binding~Binding} was able to resolve both ends (i.e., its + * [source]{@link module:enyo/Binding~Binding#source} and [target]{@link module:enyo/Binding~Binding#target} + * [objects]{@glossary Object}), this value will be `true`. Setting this manually will + * have undesirable effects. + * + * @type {Boolean} + * @default false + * @public + */ + connected: false, + + /** + * By default, a [binding]{@link module:enyo/Binding~Binding} will attempt to connect to both ends + * ([source]{@link module:enyo/Binding~Binding#source} and [target]{@link module:enyo/Binding~Binding#target}). If this + * process should be deferred, set this flag to `false`. + * + * @type {Boolean} + * @default true + * @public + */ + autoConnect: true, + + /** + * By default, a [binding]{@link module:enyo/Binding~Binding} will attempt to synchronize its values from + * its [source]{@link module:enyo/Binding~Binding#source} to its [target]{@link module:enyo/Binding~Binding#target}. If + * this process should be deferred, set this flag to `false`. + * + * @type {Boolean} + * @default true + * @public + */ + autoSync: true, + + /** + * The `dirty` property represents the changed value state of both the property designated by + * the [from]{@link module:enyo/Binding~Binding#from} path and the property designated by the + * [to]{@link module:enyo/Binding~Binding#to} path. + * + * @type {Number} + * @default module:enyo/Binding#DIRTY_FROM + * @public + */ + dirty: DIRTY_FROM, + + /** + * Indicates whether the [binding]{@link module:enyo/Binding~Binding} is currently connected. + * + * @returns {Boolean} `true` if connected; otherwise, `false`. + * @public + */ + isConnected: function () { + var from = this._from, + to = this.oneWay ? (this._toTarget || this._to) : this._to, + source = this._source, + target = this._target, + toChain, + fromChain; + + if (from && to && source && target) { + if (!this.oneWay || this._toTarget) toChain = target.getChains()[to]; + fromChain = source.getChains()[from]; + + return this.connected + && (fromChain ? fromChain.isConnected() : true) + && (toChain ? toChain.isConnected() : true); + } + + return false; + }, + + /** + * Resets all properties to their original state. + * + * @returns {this} The callee for chaining. + * @public + */ + reset: function () { + this.disconnect(); + return PassiveBinding.prototype.reset.apply(this, arguments); + }, + + /** + * Rebuilds the entire [binding]{@link module:enyo/Binding~Binding}. Will synchronize if it is able to + * connect and the [autoSync]{@link module:enyo/Binding~Binding#autoSync} flag is `true`. + * + * @returns {this} The callee for chaining. + * @public + */ + rebuild: function () { + return this.reset().connect(); + }, + + /** + * Connects the ends (i.e., the [source]{@link module:enyo/Binding~Binding#source} and + * [target]{@link module:enyo/Binding~Binding#target}) of the [binding]{@link module:enyo/Binding~Binding}. While you + * typically won't need to call this method, it is safe to call even when the ends are + * already established. Note that if one or both of the ends does become connected and the + * [autoSync]{@link module:enyo/Binding~Binding#autoSync} flag is `true`, the ends will automatically be + * synchronized. + * + * @returns {this} The callee for chaining. + * @public + */ + connect: function () { + if (!this.isConnected()) { + if (this.isReady()) { + this._source.observe(this._from, this._sourceChanged, this, {priority: true}); + + // for two-way bindings we register to observe changes + // from the target + if (!this.oneWay) this._target.observe(this._to, this._targetChanged, this); + else if (this._toTarget) { + this._target.observe(this._toTarget, this._toTargetChanged, this, {priority: true}); + } + + // we flag it as having been connected + this.connected = true; + if (this.isConnected() && this.autoSync) this.sync(true); + } + } + + return this; + }, + + /** + * Disconnects from the ends (i.e., the [source]{@link module:enyo/Binding~Binding#source} and + * [target]{@link module:enyo/Binding~Binding#target}) if a connection exists at either end. This method + * will most likely not need to be called directly. + * + * @returns {this} The callee for chaining. + * @public + */ + disconnect: function () { + if (this.isConnected()) { + this._source.unobserve(this._from, this._sourceChanged, this); + + // for two-way bindings we unregister the observer from + // the target as well + if (!this.oneWay) this._target.unobserve(this._to, this._targetChanged, this); + else if (this._toTarget) { + this._target.unobserve(this._toTarget, this._toTargetChanged, this); + } + + this.connected = false; + } + + return this; + }, + + /** + * Synchronizes values from the [source]{@link module:enyo/Binding~Binding#source} to the + * [target]{@link module:enyo/Binding~Binding#target}. This usually will not need to be called manually. + * [Two-way bindings]{@link module:enyo/Binding~Binding#oneWay} will automatically synchronize from the + * target end once they are connected. + * + * @returns {this} The callee for chaining. + * @public + */ + sync: function (force) { + var source = this._source, + target = this._target, + from = this._from, + to = this._to, + xform = this.getTransform(), + val; + + if (this.isReady() && this.isConnected()) { + + switch (this.dirty || (force && DIRTY_FROM)) { + case DIRTY_TO: + val = target.get(to); + if (xform) val = xform.call(this.owner || this, val, DIRTY_TO, this); + if (!this._stop) source.set(from, val, {create: false}); + break; + case DIRTY_FROM: + + // @TODO: This should never need to happen but is here just in case + // it is ever arbitrarily called not having been dirty? + // default: + val = source.get(from); + if (xform) val = xform.call(this.owner || this, val, DIRTY_FROM, this); + if (!this._stop) target.set(to, val, {create: false}); + break; + } + this.dirty = null; + this._stop = null; + } + + return this; + }, + + /** + * Releases all of the [binding's]{@link module:enyo/Binding~Binding} parts and unregisters its + * [observers]{@link module:enyo/ObserverSupport~ObserverSupport}. Typically, this method will not need to be called + * directly unless the binding was created without an [owner]{@link module:enyo/Binding~Binding#owner}. + * + * @returns {this} The callee for chaining. + * @public + */ + destroy: function () { + this.disconnect(); + + return PassiveBinding.prototype.destroy.apply(this, arguments); + }, + + /** + * @private + */ + constructor: function (props) { + bindings.push(this); + + if (props) utils.mixin(this, props); + + if (!this.euid) this.euid = utils.uid('b'); + if (this.autoConnect) this.connect(); + }, + + /** + * @private + */ + _sourceChanged: function (was, is, path) { + // @TODO: Should it...would it benefit from using these passed in values? + this.dirty = this.dirty == DIRTY_TO ? null : DIRTY_FROM; + return this.dirty == DIRTY_FROM && this.sync(); + }, + + /** + * @private + */ + _targetChanged: function (was, is, path) { + // @TODO: Same question as above, it seems useful but would it affect computed + // properties or stale values? + this.dirty = this.dirty == DIRTY_FROM ? null : DIRTY_TO; + return this.dirty == DIRTY_TO && this.sync(); + }, + + /** + * @private + */ + _toTargetChanged: function (was, is, path) { + this.dirty = DIRTY_FROM; + this.reset().connect(); + } +}); + +/** +* Retrieves a [binding]{@link module:enyo/Binding~Binding} by its global id. +* +* @param {String} euid - The [Enyo global id]{@glossary EUID} by which to retrieve a +* [binding]{@link module:enyo/Binding~Binding}. +* @returns {module:enyo/Binding~Binding|undefined} A reference to the binding if the id +* is found; otherwise, it will return [undefined]{@glossary undefined}. +* +* @static +* @public +*/ +exports.find = function (euid) { + return bindings.find(function (ln) { + return ln.euid == euid; + }); +}; + +/** +* All {@link module:enyo/Binding~Binding} instances are stored in this list and may be retrieved via the +* {@link module:enyo/Binding.find} method using an {@link module:enyo/Binding~Binding#id} identifier. +* +* @type {module:enyo/Binding~Binding[]} +* @default [] +* @public +*/ +exports.bindings = bindings; + +/** +* Possible value of the [dirty]{@link module:enyo/Binding~Binding#dirty} property, indicating that the value +* of the [binding source]{@link module:enyo/Binding~Binding#source} has changed. +* +* @static +* @public +*/ +exports.DIRTY_FROM = DIRTY_FROM; + +/** +* Possible value of the [dirty]{@link module:enyo/Binding~Binding#dirty} property, indicating that the value +* of the [binding target]{@link module:enyo/Binding~Binding#target} has changed. +* +* @static +* @public +*/ +exports.DIRTY_TO = DIRTY_TO; + +/** +* The default [kind]{@glossary kind} that provides [binding]{@link module:enyo/Binding~Binding} +* functionality. +* +* @static +* @public +*/ +exports.defaultBindingKind = exports; + +exports.PassiveBinding = PassiveBinding; + +},{'./kind':'enyo/kind','./utils':'enyo/utils'}],'enyo/Layout':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Layout~Layout} kind. +* @module enyo/Layout +*/ + +var + kind = require('./kind'); + +/** +* {@link module:enyo/Layout~Layout} is the base [kind]{@glossary kind} for layout +* kinds. Layout kinds are used by {@link module:enyo/UiComponent~UiComponent}-based +* [controls]{@link module:enyo/Control~Control} to allow for arranging of child controls by +* setting the [layoutKind]{@link module:enyo/UiComponent~UiComponent#layoutKind} property. +* +* Derived kinds will usually provide their own +* [layoutClass]{@link module:enyo/Layout~Layout#layoutClass} property to affect the CSS +* rules used, and may also implement the [flow()]{@link module:enyo/Layout~Layout#flow} +* and [reflow()]{@link module:enyo/Layout~Layout#reflow} methods. `flow()` is called +* during control rendering, while `reflow()` is called when the associated +* control is resized. +* +* @class Layout +* @public +*/ +module.exports = kind( + /** @lends module:enyo/Layout~Layout.prototype */ { + + name: 'enyo.Layout', + + /** + * @private + */ + kind: null, + + /** + * CSS class that's added to the [control]{@link module:enyo/Control~Control} using this + * [layout]{@link module:enyo/Layout~Layout} [kind]{@glossary kind}. + * + * @type {String} + * @default '' + * @public + */ + layoutClass: '', + + /** + * @private + */ + constructor: function (container) { + this.container = container; + if (container) { + container.addClass(this.layoutClass); + } + }, + + /** + * @private + */ + destroy: function () { + if (this.container) { + this.container.removeClass(this.layoutClass); + } + }, + + /** + * Called during static property layout (i.e., during rendering). + * + * @public + */ + flow: function () { + }, + + /** + * Called during dynamic measuring layout (i.e., during a resize). + * + * May short-circuit and return `true` if the layout needs to be + * redone when the associated Control is next shown. This is useful + * for cases where the Control itself has `showing` set to `true` + * but an ancestor is hidden, and the layout is therefore unable to + * get accurate measurements of the Control or its children. + * + * @public + */ + reflow: function () { + } +}); + +},{'./kind':'enyo/kind'}],'enyo/LinkedListNode':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/LinkedListNode~LinkedListNode} kind. +* @module enyo/LinkedListNode +*/ + +var + kind = require('./kind'), + utils = require('./utils'); + +/** +* An abstract linked-list node. +* +* @class LinkedListNode +* @private +*/ +module.exports = kind( + /** @lends module:enyo/LinkedListNode~LinkedListNode.prototype */ { + + /** + * @private + */ + kind: null, + + /** + * @private + */ + + + /** + * @private + */ + prev: null, + + /** + * @private + */ + next: null, + + /** + * @private + */ + copy: function () { + var cpy = new this.ctor(); + cpy.prev = this.prev; + cpy.next = this.next; + return cpy; + }, + + /** + * @private + */ + constructor: function (props) { + props && utils.mixin(this, props); + }, + + /** + * @private + */ + destroy: function () { + // clear reference to previous node + this.prev = null; + + // if we have a reference to our next node + // we continue down the chain + this.next && this.next.destroy(); + + // clear our reference to the next node + this.next = null; + } +}); + +},{'./kind':'enyo/kind','./utils':'enyo/utils'}],'enyo/AnimationSupport/Tween':[function (module,exports,global,require,request){ +require('enyo'); + +var + frame = require('./Frame'), + matrixUtil = require('./Matrix'), + Vector = require('./Vector'); + +var oldState, newState, node, matrix, cState = []; +/** +* Tween is a module responsible for creating intermediate frames for an animation. +* The responsibilities of this module is to; +* - Interpolating current state of character. +* - Update DOM based on current state, using matrix for tranform and styles for others. +* +* @module enyo/AnimationSupport/Tween +*/ +module.exports = { + /** + * Tweens public API which notifies to change current state of + * a character. This method is normally trigger by the Animation Core to + * update the animating characters state based on the current timestamp. + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * @parameter chrac- Animating character + * ts- DOMHighResTimeStamp + * + * @public + */ + update: function (charc, ts) { + var t, + dur = charc._duration, + since = ts - charc._startTime; + + if (since < 0) return; + if (since <= dur) { + t = since / dur; + this.step(charc, t); + } else { + this.step(charc, 1); + } + }, + + /** + * @private + */ + step: function(charc, t) { + var k, c, pts; + + node = charc.node; + newState = charc._endAnim; + props = charc.getAnimation(), + oldState = charc._startAnim; + charc.currentState = charc.currentState || {}; + + for (k in props) { + cState = frame.copy(charc.currentState[k] || []); + if (charc.ease && (typeof charc.ease !== 'function') && (k !== 'rotate')) { + pts = this.calculateEase(charc.ease, frame.copy(oldState[k]), frame.copy(newState[k])); + cState = this.getBezier(t, pts, cState); + } else { + c = k == 'rotate' ? this.slerp : this.lerp; + cState = t ? c(oldState[k], newState[k], ((typeof charc.ease === 'function') ? charc.ease : this.ease)(t), cState) : newState[k]; + } + + if (!frame.isTransform(k)) { + frame.setProperty(node, k, cState); + } + charc.currentState[k] = cState; + } + + if(charc._transform) { + matrix = frame.recomposeMatrix( + charc.currentState.translate, + charc.currentState.rotate, + charc.currentState.scale, + charc.currentState.skew, + charc.currentState.perspective + ); + frame.accelerate(node, matrix); + } + }, + + /** + * @private + */ + ease: function (t) { + return t; + }, + + calculateEase: function(easeObj, startPoint, endPoint) { + var order = (easeObj && Object.keys(easeObj).length) ? (Object.keys(easeObj).length + 1) : 0; + var controlPoints = [startPoint], + bValues = [], + m1 = [], + m2 = [], + m3 = [], + m4 = [], + l = 0; + + var t, a; + for (var key in easeObj) { + t = parseFloat(key) / 100; + a = parseFloat(easeObj[key]) / 100; + bValues = this.getBezierValues(t, order); + bValues.shift(); + m1.push(a - bValues.pop()); + m2.push(bValues); + } + + m3 = matrixUtil.inverseN(m2, bValues.length); + // console.log("m1", m1); + // console.log("m2", m2); + // console.log("m3", m3); + + m4 = matrixUtil.multiplyN(m3, m1); + l = m4.length; + for (var i = 0; i < l; i++) { + controlPoints.push([m4[i], m4[i], m4[i]]); + } + + console.log("cp", m4); + + controlPoints.push(endPoint); + + //console.log("CP", controlPoints); + return controlPoints; + }, + + complete: function (charc) { + charc.animating = false; + charc._startAnim = undefined; + charc._endAnim = undefined; + }, + + lerp: function (vA, vB, t, vR) { + if (!vR) vR = []; + var i, l = vA.length; + + for (i = 0; i < l; i++) { + vR[i] = (1 - t) * vA[i] + t * vB[i]; + } + return vR; + }, + + slerp: function (qA, qB, t, qR) { + if (!qR) qR = []; + var a, b, w, theta, dot = Vector.dot(qA, qB); + + dot = Math.min(Math.max(dot, -1.0), 1.0); + if (dot == 1.0) { + qR = frame.copy(qA); + return qR; + } + + theta = Math.acos(dot); + w = Math.sin(t * theta) * 1 / Math.sqrt(1 - dot * dot); + for (var i = 0; i < 4; i++) { + a = qA[i] * (Math.cos(t * theta) - dot * w); + b = qB[i] * w; + qR[i] = a + b; + } + return qR; + }, + + /** + * @public + * @params t: time, points: knot and control points, vR: resulting point + */ + getBezier: function (t, points, vR) { + if (!vR) vR = []; + + var i, j, + c = points.length, + l = points[0].length, + lastIndex = (c - 1), + endPoint = points[lastIndex], + values = this.getBezierValues(t, lastIndex); + + for (i = 0; i < l; i++) { + vR[i] = 0; + for (j = 0; j < c; j++) { + if ((j > 0) && (j < (c - 1))) { + vR[i] = vR[i] + (points[j][i] * endPoint[i] * values[j]); + } else { + vR[i] = vR[i] + (points[j][i] * values[j]); + } + } + } + + console.log("vR", vR); + return vR; + }, + + /** + * @private + * @params n: order, k: current position + */ + getCoeff: function (n, k) { + + // Credits + // https://math.stackexchange.com/questions/202554/how-do-i-compute-binomial-coefficients-efficiently#answer-927064 + + if (k > n) + return void 0; + if (k === 0) + return 1; + if (k === n) + return 1; + if (k > n / 2) + return this.getCoeff(n, n - k); + + return n * this.getCoeff(n - 1, k - 1) / k; + }, + + /** + * @public + * @params t: time, n: order + */ + getBezierValues: function (t, n) { + var c, + values = [], + x = (1 - t), + y = t; + + // + // Binomial theorem to expand (x+y)^n + // + for (var k = 0; k <= n; k++) { + c = this.getCoeff(n, k) * Math.pow(x, (n - k)) * Math.pow(y, k); + values.push(c); + } + + return values; + } +}; +},{'./Frame':'enyo/AnimationSupport/Frame','./Matrix':'enyo/AnimationSupport/Matrix','./Vector':'enyo/AnimationSupport/Vector'}],'enyo/BindingSupport':[function (module,exports,global,require,request){ +/** +* Exports the {@link module:enyo/BindingSupport~BindingSupport} mixin +* @module enyo/BindingSupport +*/ + +require('enyo'); + +var + kind = require('./kind'), + utils = require('./utils'); + +var + Binding = require('./Binding'); + +kind.concatenated.push('bindings'); + +/** +* An internally-used {@glossary mixin} that is added to {@link module:enyo/CoreObject~Object} +* and its [subkinds]{@glossary subkind}. It includes public and protected API +* methods for working with [bindings]{@link module:enyo/Binding~Binding}. +* +* @mixin +* @protected +*/ +var BindingSupport = { + + /** + * @private + */ + name: 'BindingSupport', + + /** + * @private + */ + _bindingSupportInitialized: false, + + /** + * Imperatively creates a [binding]{@link module:enyo/Binding~Binding}. Merges a variable + * number of [hashes]{@glossary Object} and instantiates a binding that + * will have its [owner]{@link module:enyo/Binding~Binding#owner} property set to the callee + * (the current {@link module:enyo/CoreObject~Object}). Bindings created in this way will be + * [destroyed]{@link module:enyo/Binding~Binding#destroy} when their `owner` is + * [destroyed]{@link module:enyo/CoreObject~Object#destroy}. + * + * @param {...Object} props A variable number of [hashes]{@glossary Object} that will + * be merged into the properties applied to the {@link module:enyo/Binding~Binding} instance. + * @returns {this} The callee for chaining. + * @public + */ + binding: function () { + var args = utils.toArray(arguments) + , props = utils.mixin(args) + , bindings = this.bindings || (this.bindings = []) + , passiveBindings = this.passiveBindings || (this.passiveBindings = []) + , PBCtor = Binding.PassiveBinding + , Ctor, bnd; + + props.owner = props.owner || this; + Ctor = props.kind = props.kind || this.defaultBindingKind || Binding.defaultBindingKind; + + if (this._bindingSupportInitialized) { + utils.isString(Ctor) && (Ctor = props.kind = kind.constructorForKind(Ctor)); + bnd = new Ctor(props); + bindings.push(bnd); + if (Ctor === PBCtor) { + passiveBindings.push(bnd); + } + return bnd; + } else bindings.push(props); + + return this; + }, + + /** + * Removes and [destroys]{@link module:enyo/Binding~Binding#destroy} all of, or a subset of, + * the [bindings]{@link module:enyo/Binding~Binding} belonging to the callee. + * + * @param {module:enyo/Binding~Binding[]} [subset] - The optional [array]{@glossary Array} of + * [bindings]{@link module:enyo/Binding~Binding} to remove. + * @returns {this} The callee for chaining. + * @public + */ + clearBindings: function (subset) { + var bindings = subset || (this.bindings && this.bindings.slice()); + bindings.forEach(function (bnd) { + bnd.destroy(); + }); + + return this; + }, + + syncBindings: function (opts) { + var all = opts && opts.all, + force = opts && opts.force, + bindings = all ? this.bindings : this.passiveBindings; + + bindings.forEach(function (b) { + b.sync(force); + }); + }, + + /** + * Removes a single {@link module:enyo/Binding~Binding} from the callee. (This does not + * [destroy]{@link module:enyo/Binding~Binding#destroy} the binding.) Also removes the + * [owner]{@link module:enyo/Binding~Binding#owner} reference if it is the callee. + * + * It should be noted that when a binding is destroyed, it is automatically + * removed from its owner. + * + * @param {module:enyo/Binding~Binding} binding - The {@link module:enyo/Binding~Binding} instance to remove. + * @returns {this} The callee for chaining. + * @public + */ + removeBinding: function (binding) { + utils.remove(binding, this.bindings); + if (binding.ctor === Binding.PassiveBinding) { + utils.remove(binding, this.passiveBindings); + } + + if (binding.owner === this) binding.owner = null; + + return this; + }, + + /** + * @private + */ + constructed: kind.inherit(function (sup) { + return function () { + var bindings = this.bindings; + this._bindingSupportInitialized = true; + if (bindings) { + this.bindings = []; + this.passiveBindings = []; + bindings.forEach(function (def) { + this.binding(def); + }, this); + } + sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + destroy: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + this.bindings && this.bindings.length && this.clearBindings(); + this.bindings = null; + this.passiveBindings = null; + }; + }) +}; + +module.exports = BindingSupport; + +/** + Hijack the original so we can add additional default behavior. +*/ +var sup = kind.concatHandler + , flags = {ignore: true}; + +/** +* @private +*/ +kind.concatHandler = function (ctor, props, instance) { + var proto = ctor.prototype || ctor + , kind = props && (props.defaultBindingKind || Binding.defaultBindingKind) + , defaults = props && props.bindingDefaults; + + sup.call(this, ctor, props, instance); + if (props.bindings) { + props.bindings.forEach(function (bnd) { + defaults && utils.mixin(bnd, defaults, flags); + bnd.kind || (bnd.kind = kind); + }); + + proto.bindings = proto.bindings? proto.bindings.concat(props.bindings): props.bindings; + delete props.bindings; + } +}; + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./Binding':'enyo/Binding'}],'enyo/RepeaterChildSupport':[function (module,exports,global,require,request){ +/** +* Exports the {@link module:enyo/RepeaterChildSupport~RepeaterChildSupport} mixin +* @module enyo/RepeaterChildSupport +*/ + +require('enyo'); + +var + kind = require('./kind'), + utils = require('./utils'); + +var + Binding = require('./Binding'); + +/** +* The {@link module:enyo/RepeaterChildSupport~RepeaterChildSupport} [mixin]{@glossary mixin} contains methods and +* properties that are automatically applied to all children of {@link module:enyo/DataRepeater~DataRepeater} +* to assist in selection support. (See {@link module:enyo/DataRepeater~DataRepeater} for details on how to +* use selection support.) This mixin also [adds]{@link module:enyo/Repeater~Repeater#decorateEvent} the +* `model`, `child` ([control]{@link module:enyo/Control~Control} instance), and `index` properties to +* all [events]{@glossary event} emitted from the repeater's children. +* +* @mixin +* @public +*/ +var RepeaterChildSupport = { + + /* + * @private + */ + name: 'RepeaterChildSupport', + + /** + * Indicates whether the current child is selected in the [repeater]{@link module:enyo/DataRepeater~DataRepeater}. + * + * @type {Boolean} + * @default false + * @public + */ + selected: false, + + /** + * Setting cachePoint: true ensures that events from the repeater child's subtree will + * always bubble up through the child, allowing the events to be decorated with repeater- + * related metadata and references. + * + * @type {Boolean} + * @default true + * @private + */ + cachePoint: true, + + /* + * @method + * @private + */ + selectedChanged: kind.inherit(function (sup) { + return function () { + if (this.repeater.selection) { + this.addRemoveClass(this.selectedClass || 'selected', this.selected); + // for efficiency purposes, we now directly call this method as opposed to + // forcing a synchronous event dispatch + var idx = this.repeater.collection.indexOf(this.model); + if (this.selected && !this.repeater.isSelected(this.model)) { + this.repeater.select(idx); + } else if (!this.selected && this.repeater.isSelected(this.model)) { + this.repeater.deselect(idx); + } + } + sup.apply(this, arguments); + }; + }), + + /* + * @method + * @private + */ + modelChanged: kind.inherit(function (sup) { + return function () { + this.syncBindings(); + sup.apply(this, arguments); + }; + }), + + /* + * @method + * @private + */ + decorateEvent: kind.inherit(function (sup) { + return function (sender, event) { + var c = this.repeater.collection; + if (c) { + event.model = this.model; + event.child = this; + event.index = this.repeater.collection.indexOf(this.model); + } + sup.apply(this, arguments); + }; + }), + + /* + * @private + */ + _selectionHandler: function () { + if (this.repeater.selection && !this.get('disabled')) { + if (this.repeater.selectionType != 'group' || !this.selected) { + this.set('selected', !this.selected); + } + } + }, + /** + * Deliberately used to supersede the default method and set + * [owner]{@link module:enyo/Component~Component#owner} to this [control]{@link module:enyo/Control~Control} so that there + * are no name collisions in the instance [owner]{@link module:enyo/Component~Component#owner}, and also so + * that [bindings]{@link module:enyo/Binding~Binding} will correctly map to names. + * + * @method + * @private + */ + createClientComponents: kind.inherit(function () { + return function (components) { + this.createComponents(components, {owner: this}); + }; + }), + /** + * Used so that we don't stomp on any built-in handlers for the `ontap` + * {@glossary event}. + * + * @method + * @private + */ + dispatchEvent: kind.inherit(function (sup) { + return function (name, event, sender) { + var owner; + + // if the event is coming from a child of the repeater-child (this...) and has a + // delegate assigned to it there is a distinct possibility it is supposed to be + // targeting the instanceOwner of repeater-child not the repeater-child itself + // so we have to check this case and treat it as expected - if there is a handler + // and it returns true then we must skip the normal flow + if (event.originator !== this && event.delegate && event.delegate.owner === this) { + if (typeof this[name] != 'function') { + // ok we don't have the handler here let's see if our owner does + owner = this.getInstanceOwner(); + if (owner && owner !== this) { + if (typeof owner[name] == 'function') { + // alright it appears that we're supposed to forward this to the + // next owner instead + return owner.dispatch(name, event, sender); + } + } + } + } + + if (!event._fromRepeaterChild) { + if (!!~utils.indexOf(name, this.repeater.selectionEvents)) { + this._selectionHandler(); + event._fromRepeaterChild = true; + } + } + return sup.apply(this, arguments); + }; + }), + + /* + * @method + * @private + */ + constructed: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + var r = this.repeater, + s = r.selectionProperty; + // this property will only be set if the instance of the repeater needs + // to track the selected state from the view and model and keep them in sync + if (s) { + var bnd = this.binding({ + from: 'model.' + s, + to: 'selected', + oneWay: false/*, + kind: enyo.BooleanBinding*/ + }); + this._selectionBindingId = bnd.euid; + } + }; + }), + + /* + * @method + * @private + */ + destroy: kind.inherit(function (sup) { + return function () { + if (this._selectionBindingId) { + var b$ = Binding.find(this._selectionBindingId); + if (b$) { + b$.destroy(); + } + } + sup.apply(this, arguments); + }; + }), + + /* + * @private + */ + _selectionBindingId: null +}; + +module.exports = RepeaterChildSupport; + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./Binding':'enyo/Binding'}],'enyo/LinkedList':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/LinkedList~LinkedList} kind. +* @module enyo/LinkedList +*/ + +var + kind = require('./kind'); + +var + LinkedListNode = require('./LinkedListNode'); + +/** +* An abstract linked-list. +* +* @class LinkedList +* @private +*/ +module.exports = kind( + /** @lends module:enyo/LinkedList~LinkedList.prototype */ { + + /** + * @private + */ + kind: null, + + /** + * @private + */ + + + /** + * @private + */ + nodeKind: LinkedListNode, + + /** + * @private + */ + head: null, + + /** + * @private + */ + tail: null, + + /** + * @private + */ + length: 0, + + /** + * @private + */ + clear: function () { + if (this.head) { + // this will trigger a chain event down the list + this.head.destroy(); + } + this.head = null; + this.tail = null; + this.length = 0; + }, + + /** + * @private + */ + slice: function (fromNode, toNode) { + var node = fromNode || this.head + , list = new this.ctor() + , cpy; + + // ensure we have a final node or our tail + toNode = toNode || this.tail; + + if (node && node !== toNode) { + do { + cpy = node.copy(); + list.appendNode(cpy); + } while ((node = node.next) && node !== toNode); + } + + return list; + }, + + /** + * @private + */ + destroy: function () { + this.clear(); + this.destroyed = true; + }, + + /** + * @private + */ + createNode: function (props) { + return new this.nodeKind(props); + }, + + /** + * @private + */ + deleteNode: function (node) { + this.removeNode(node); + + // can't chain destruct because we removed its chain references + node.destroy(); + return this; + }, + + /** + * @private + */ + removeNode: function (node) { + var prev = node.prev + , next = node.next; + + prev && (prev.next = next); + next && (next.prev = prev); + this.length--; + node.next = node.prev = null; + return this; + }, + + /** + * @private + */ + appendNode: function (node, targetNode) { + targetNode = targetNode || this.tail; + + if (targetNode) { + if (targetNode.next) { + node.next = targetNode.next; + } + + targetNode.next = node; + node.prev = targetNode; + + if (targetNode === this.tail) { + this.tail = node; + } + + this.length++; + } else { + + this.head = this.tail = node; + node.prev = node.next = null; + this.length = 1; + } + return this; + }, + + /** + * @private + */ + find: function (fn, ctx, targetNode) { + var node = targetNode || this.head; + if (node) { + do { + if (fn.call(ctx || this, node, this)) { + return node; + } + } while ((node = node.next)); + } + // if no node qualified it returns false + return false; + }, + + /** + * @private + */ + forward: function (fn, ctx, targetNode) { + var node = targetNode || this.head; + if (node) { + do { + if (fn.call(ctx || this, node, this)) { + break; + } + } while ((node = node.next)); + } + // returns the last node (if any) that was processed in the chain + return node; + }, + + /** + * @private + */ + backward: function (fn, ctx, targetNode) { + var node = targetNode || this.tail; + if (node) { + do { + if (fn.call(ctx || this, node, this)) { + break; + } + } while ((node = node.prev)); + } + // returns the last node (if any) that was processed in the chain + return node; + }, + + /** + * @private + */ + constructor: function () { + this.nodeType = kind.constructorForKind(this.nodeType); + } +}); + +},{'./kind':'enyo/kind','./LinkedListNode':'enyo/LinkedListNode'}],'enyo/ObserverChainNode':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/ObserverChainNode~ObserverChainNode} kind. +* @module enyo/ObserverChainNode +*/ + +var + kind = require('./kind'); + +var + LinkedListNode = require('./LinkedListNode'); + +function get (base, prop) { + return base && /*isObject(base)*/ (typeof base == 'object')? ( + base.get? base.get(prop): base[prop] + ): undefined; +} + +/** +* An internally used {@glossary kind}. +* +* @class ObserverChainNode +* @extends module:enyo/LinkedListNode~LinkedListNode +* @private +*/ +module.exports = kind( + /** @lends module:enyo/ObserverChainNode~ObserverChainNode.prototype */ { + + /** + * @private + */ + kind: LinkedListNode, + + /** + * @private + */ + + + /** + * @method + * @private + */ + constructor: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + this.connect(); + }; + }), + + /** + * @method + * @private + */ + destroy: kind.inherit(function (sup) { + return function () { + this.disconnect(); + sup.apply(this, arguments); + this.observer = null; + this.list = null; + this.object = null; + }; + }), + + /** + * @private + */ + connect: function () { + var obj = this.object + , obs = this._changed + , prop = this.property; + if (obj) { + if (obj.observe) obj.observe(prop, obs, this, {noChain: true, priority: true}); + this.connected = true; + this.list.connected++; + } + }, + + /** + * @private + */ + disconnect: function () { + var obj = this.object + , obs = this._changed + , prop = this.property + , was = this.connected; + obj && obj.unobserve && obj.unobserve(prop, obs, this); + this.connected = null; + if (was) this.list.connected--; + }, + + /** + * @private + */ + setObject: function (object) { + var cur = this.object + , prop = this.property + , was, is; + + if (cur !== object) { + this.disconnect(); + this.object = object; + this.connect(); + + if (this.list.tail === this) { + was = get(cur, prop); + is = get(object, prop); + // @TODO: It would be better to somehow cache values + // such that it could intelligently derive the difference + // without needing to continuously look it up with get + was !== is && this.list.observed(this, was, is); + } + } + }, + + /** + * @private + */ + _changed: function (was, is) { + this.list.observed(this, was, is); + } +}); + +},{'./kind':'enyo/kind','./LinkedListNode':'enyo/LinkedListNode'}],'enyo/ObserverChain':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/ObserverChain~ObserverChain} kind. +* @module enyo/ObserverChain +*/ + +var + kind = require('./kind'); + +var + LinkedList = require('./LinkedList'), + ObserverChainNode = require('./ObserverChainNode'); + +function get (base, prop) { + return base && /*isObject(base)*/ (typeof base == 'object')? ( + base.get? base.get(prop): base[prop] + ): undefined; +} + +/** +* An internally used {@glossary kind}. +* +* @class ObserverChain +* @extends module:enyo/LinkedList~LinkedList +* @private +*/ +module.exports = kind( + /** @lends module:enyo/ObserverChain~ObserverChain.prototype */ { + + /** + * @private + */ + kind: LinkedList, + + /** + * @private + */ + nodeKind: ObserverChainNode, + + /** + * @private + */ + + + /** + * @private + */ + connected: 0, + + /** + * @method + * @private + */ + constructor: function (path, object) { + this.object = object; + this.path = path; + this.parts = path.split('.'); + this.createChain(); + }, + + /** + * @method + * @private + */ + destroy: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + this.object = null; + this.parts = null; + this.path = null; + }; + }), + + /** + * @private + */ + rebuild: function (target) { + if (!this.rebuilding) { + this.rebuilding = true; + this.forward(function (node) { + if (node !== this.head) { + var src = node.prev.object + , prop = node.prev.property; + node.setObject(get(src, prop)); + } + }, this, target); + this.rebuilding = false; + } + }, + + /** + * @private + */ + isConnected: function () { + return !! (this.connected === this.length && this.length); + }, + + /** + * @private + */ + buildPath: function (target) { + var str = ''; + + this.backward(function (node) { + str = node.property + (str? ('.' + str): str); + }, this, target); + + return str; + }, + + /** + * @private + */ + createChain: function () { + var parts = this.parts + , next = this.object + , $ = false + , node, prop; + + for (var i=0; (prop=parts[i]); ++i) { + + // forEach(parts, function (prop, idx) { + // we create a special case for the $ hash property + if (prop == '$') { + $ = true; + } else { + // in cases where the chain has the $ property we arbitrarily + // force it onto our current nodes property and let the special handling + // in ObserverChainNode and ObserverSupport handle the rest + $ && (prop = '$.' + prop); + node = this.createNode({property: prop, object: next, list: this}); + this.appendNode(node); + next = get(next, prop); + $ = false; + } + // }, this); + } + }, + + /** + * @private + */ + observed: function (node, was, is) { + this.object.stopNotifications(); + // @NOTE: About the following two cases, they are mutually exclusive and this seems perfect + // that we don't see double notifications + // @TODO: Only notify if it was the full property path? This is far more efficient after + // testing but not as flexible... + node === this.tail /*&& was !== is*/ && this.object.notify(this.buildPath(node), was, is); + // @TODO: It seems the same case across the board that the rebuild only needs to take place + // from the beginning to the second-to-last elem + node !== this.tail && was !== is && this.rebuild(node); + this.object.startNotifications(); + } +}); + +},{'./kind':'enyo/kind','./LinkedList':'enyo/LinkedList','./ObserverChainNode':'enyo/ObserverChainNode'}],'enyo/ObserverSupport':[function (module,exports,global,require,request){ +/** +* Exports the {@link module:enyo/ObserverSupport~ObserverSupport} mixin +* @module enyo/ObserverSupport +*/ +require('enyo'); + +var + kind = require('./kind'), + utils = require('./utils'); + +var + ObserverChain = require('./ObserverChain'); + +var observerTable = {}; + +kind.concatenated.push("observers"); + +/** +* Responds to changes in one or more properties. +* [Observers]{@link module:enyo/ObserverSupport~ObserverSupport#observers} may be registered in +* several different ways. See the {@link module:enyo/ObserverSupport} documentation +* for more details. Also note that, while observers should not be called +* directly, if defined on a [kind]{@glossary kind}, they may be +* overloaded for special behavior. +* +* @see {@link module:enyo/ObserverSupport} +* @see {@link module:enyo/ObserverSupport~ObserverSupport#observe} +* @callback module:enyo/ObserverSupport~ObserverSupport~Observer +* @param {*} was - The previous value of the property that has changed. +* @param {*} is - The current value of the property that has changed. +* @param {String} prop - The name of the property that has changed. +* @public +*/ + +function addObserver (path, fn, ctx, opts) { + + var observers = this.getObservers(), + chains = this.getChains(), + parts = path.split('.'), + prio = opts && opts.priority, + entries, + noChain; + + noChain = (opts && opts.noChain) || + chains[path] || + parts.length < 2 || + (parts.length === 2 && path[0] == '$'); + + if (observers[path] && !observers.hasOwnProperty(path)) { + observers[path] = observers[path].slice(); + } + + entries = observers[path] || (observers[path] = []); + entries[prio ? 'unshift' : 'push']({method: fn, ctx: ctx || this}); + + if (!noChain) { + this.getChains()[path] = new ObserverChain(path, this); + } + + return this; +} + +function removeObserver (obj, path, fn, ctx) { + var observers = obj.getObservers(path) + , chains = obj.getChains() + , idx, chain; + + if (observers && observers.length) { + idx = observers.findIndex(function (ln) { + return ln.method === fn && (ctx? ln.ctx === ctx: true); + }); + idx > -1 && observers.splice(idx, 1); + } + + if ((chain = chains[path]) && !observers.length) { + chain.destroy(); + } + + return obj; +} + +function notifyObservers (obj, path, was, is, opts) { + if (obj.isObserving()) { + var observers = obj.getObservers(path); + + if (observers && observers.length) { + for (var i=0, ln; (ln=observers[i]); ++i) { + if (typeof ln.method == "string") obj[ln.method](was, is, path, opts); + else ln.method.call(ln.ctx || obj, was, is, path, opts); + } + } + } else enqueue(obj, path, was, is, opts); + + return obj; +} + +function enqueue (obj, path, was, is, opts) { + if (obj._notificationQueueEnabled) { + var queue = obj._notificationQueue || (obj._notificationQueue = {}) + , ln = queue[path] || (queue[path] = {}); + + ln.was = was; + ln.is = is; + ln.opts = opts; + } +} + +function flushQueue (obj) { + var queue = obj._notificationQueue + , path, ln; + + if (queue) { + obj._notificationQueue = null; + + for (path in queue) { + ln = queue[path]; + obj.notify(path, ln.was, ln.is, ln.opts); + } + } +} + +/** +* Adds support for notifications on property changes. Most +* [kinds]{@glossary kind} (including all kinds that inherit from +* {@link module:enyo/CoreObject~Object}) already have this {@glossary mixin} applied. +* This allows for +* [observers]{@link module:enyo/ObserverSupport~ObserverSupport~Observer} to be +* [declared]{@link module:enyo/ObserverSupport~ObserverSupport#observers} or "implied" (see below). +* +* Implied observers are not declared, but derived from their `name`. They take +* the form `Changed`, where `` is the property to +* [observe]{@link module:enyo/ObserverSupport~ObserverSupport#observe}. For example: +* +* ```javascript +* var +* kind = require('enyo/kind'); +* +* module.exports = kind({ +* name: 'MyKind', +* +* // some local property +* value: true, +* +* // and the implied observer of that property +* valueChanged: function (was, is) { +* // do something now that it has changed +* enyo.log('value was "' + was + '" but now it is "' + is + '"'); +* } +* }); +* +* var mine = new MyKind(); +* mine.set('value', false); // -> value was "true" but now it is "false" +* ``` +* +* Using the `observers` property for its declarative syntax, an observer may +* observe any property (or properties), regardless of its `name`. For example: +* +* ```javascript +* var +* kind = require('enyo/kind'); +* +* module.exports = kind({ +* name: 'MyKind', +* +* // some local property +* value: true, +* +* // another local property +* count: 1, +* +* // declaring the observer +* observers: [ +* // the path can be a single string or an array of strings +* {method: 'myObserver', path: ['value', 'count']} +* ], +* +* // now this observer will be notified of changes to both properties +* myObserver: function (was, is, prop) { +* // do something now that it changed +* enyo.log(prop + ' was "' + was + '" but now it is "' + is + '"'); +* } +* }); +* +* var mine = new MyKind(); +* mine.set('value', false); // -> value was "true" but now it is "false" +* mine.set('count', 2); // -> count was "1" but now it is "2" +* ``` +* +* While observers may be [notified]{@link module:enyo/ObserverSupport~ObserverSupport#notify} of +* changes to multiple properties, this is not a typical use case for implied +* observers, since, by convention, they are only registered for the named +* property. +* +* There is one additional way to use observers, if necessary. You may use the +* API methods [observe()]{@link module:enyo/ObserverSupport~ObserverSupport#observe} and +* [unobserve()]{@link module:enyo/ObserverSupport~ObserverSupport#unobserve} to dynamically +* register and unregister observers as needed. For example: +* +* ```javascript +* var +* Object = require('enyo/CoreObject').Object; +* +* var object = new Object({value: true}); +* var observer = function (was, is) { +* enyo.log('value was "' + was + '" but now it is "' + is + '"'); +* }; +* +* object.observe('value', observer); +* object.set('value', false); // -> value was "true" but now it is "false" +* object.unobserve('value', observer); +* object.set('value', true); // no output because there is no observer +* ``` +* +* Be sure to read the documentation for these API methods; proper usage of +* these methods is important for avoiding common pitfalls and memory leaks. +* +* @mixin +* @public +*/ +var ObserverSupport = { + + /** + * @private + */ + name: "ObserverSupport", + + /** + * @private + */ + _observing: true, + + /** + * @private + */ + _observeCount: 0, + + /** + * @private + */ + _notificationQueue: null, + + /** + * @private + */ + _notificationQueueEnabled: true, + + /** + * Determines whether `_observing` is enabled. If + * [stopNotifications()]{@link module:enyo/ObserverSupport~ObserverSupport#stopNotifications} has + * been called, then this will return `false`. + * + * @see {@link module:enyo/ObserverSupport~ObserverSupport#stopNotifications} + * @see {@link module:enyo/ObserverSupport~ObserverSupport#startNotifications} + * @returns {Boolean} Whether or not the callee is observing. + */ + isObserving: function () { + return this._observing; + }, + + /** + * Returns an immutable list of [observers]{@link module:enyo/ObserverSupport~ObserverSupport~Observer} + * for the given `path`, or all observers for the callee. + * + * @param {String} [path] - Path or property path for which + * [observers]{@link module:enyo/ObserverSupport~ObserverSupport~Observer} will be returned. If not + * specified, all observers for the callee will be returned. + * + * @returns {module:enyo/ObserverSupport~ObserverSupport~Observer[]} The immutable + * [array]{@glossary Array} of observers. + * @public + */ + getObservers: function (path) { + var euid = this.euid || (this.euid = utils.uid('o')), + ret, + loc; + + loc = observerTable[euid] || (observerTable[euid] = ( + this._observers? Object.create(this._observers): {} + )); + + if (!path) return loc; + + ret = loc[path]; + + // if the special property exists... + if (loc['*']) ret = ret ? ret.concat(loc['*']) : loc['*'].slice(); + return ret; + }, + + /** + * @private + */ + getChains: function () { + return this._observerChains || (this._observerChains = {}); + }, + + /** + * @deprecated + * @alias {@link module:enyo/ObserverSupport~ObserverSupport#observe} + * @public + */ + addObserver: function () { + // @NOTE: In this case we use apply because of internal variable use of parameters + return addObserver.apply(this, arguments); + }, + + /** + * Registers an [observer]{@link module:enyo/ObserverSupport~ObserverSupport~Observer} to be + * [notified]{@link module:enyo/ObserverSupport~ObserverSupport#notify} when the given property has + * been changed. It is important to note that it is possible to register the + * same observer multiple times (although this is never the intention), so + * care should be taken to avoid that scenario. It is also important to + * understand how observers are stored and unregistered + * ([unobserved]{@link module:enyo/ObserverSupport~ObserverSupport#unobserve}). The `ctx` (context) + * parameter is stored with the observer reference. **If used when + * registering, it should also be used when unregistering.** + * + * @see {@link module:enyo/ObserverSupport~ObserverSupport#unobserve} + * @param {String} path - The property or property path to observe. + * @param {module:enyo/ObserverSupport~ObserverSupport~Observer} fn - The + * [observer]{@link module:enyo/ObserverSupport~ObserverSupport~Observer} method that responds to changes. + * @param {*} [ctx] - The `this` (context) under which to execute the observer. + * + * @returns {this} The callee for chaining. + * @public + */ + observe: function () { + // @NOTE: In this case we use apply because of internal variable use of parameters + return addObserver.apply(this, arguments); + }, + + /** + * @deprecated + * @alias {@link module:enyo/ObserverSupport~ObserverSupport#unobserve} + * @public + */ + removeObserver: function (path, fn, ctx) { + return removeObserver(this, path, fn); + }, + + /** + * Unregisters an [observer]{@link module:enyo/ObserverSupport~ObserverSupport~Observer}. If a `ctx` + * (context) was supplied to [observe()]{@link module:enyo/ObserverSupport~ObserverSupport#observe}, + * then it should also be supplied to this method. + * + * @see {@link module:enyo/ObserverSupport~ObserverSupport#observe} + * @param {String} path - The property or property path to unobserve. + * @param {module:enyo/ObserverSupport~ObserverSupport~Observer} fn - The + * [observer]{@link module:enyo/ObserverSupport~ObserverSupport~Observer} method that responds to changes. + * @param {*} [ctx] - The `this` (context) under which to execute the observer. + * + * @returns {this} The callee for chaining. + * @public + */ + unobserve: function (path, fn, ctx) { + return removeObserver(this, path, fn, ctx); + }, + + /** + * Removes all [observers]{@link module:enyo/ObserverSupport~ObserverSupport~Observer} from the + * callee. If a `path` parameter is provided, observers will only be removed + * from that path (or property). + * + * @param {String} [path] - A property or property path from which to remove all + * [observers]{@link module:enyo/ObserverSupport~ObserverSupport~Observer}. + * @returns {this} The callee for chaining. + */ + removeAllObservers: function (path) { + var euid = this.euid + , loc = euid && observerTable[euid]; + + if (loc) { + if (path) { + loc[path] = null; + } else { + observerTable[euid] = null; + } + } + + return this; + }, + + /** + * @deprecated + * @alias module:enyo/ObserverSupport~ObserverSupport#notify + * @public + */ + notifyObservers: function (path, was, is, opts) { + return notifyObservers(this, path, was, is, opts); + }, + + /** + * Triggers any [observers]{@link module:enyo/ObserverSupport~ObserverSupport~Observer} for the + * given `path`. The previous and current values must be supplied. This + * method is typically called automatically, but it may also be called + * forcibly by [setting]{@link module:enyo/CoreObject~Object#set} a value with the + * `force` option. + * + * @param {String} path - The property or property path to notify. + * @param {*} was - The previous value. + * @param {*} is - The current value. + * @returns {this} The callee for chaining. + */ + notify: function (path, was, is, opts) { + return notifyObservers(this, path, was, is, opts); + }, + + /** + * Stops all [notifications]{@link module:enyo/ObserverSupport~ObserverSupport#notify} from + * propagating. By default, all notifications will be queued and flushed once + * [startNotifications()]{@link module:enyo/ObserverSupport~ObserverSupport#startNotifications} + * has been called. Setting the optional `noQueue` flag will also disable the + * queue, or you can use the + * [disableNotificationQueue()]{@link module:enyo/ObserverSupport~ObserverSupport#disableNotificationQueue} and + * [enableNotificationQueue()]{@link module:enyo/ObserverSupport~ObserverSupport#enableNotificationQueue} + * API methods. `startNotifications()` will need to be called the same number + * of times that this method has been called. + * + * @see {@link module:enyo/ObserverSupport~ObserverSupport#startNotifications} + * @see {@link module:enyo/ObserverSupport~ObserverSupport#disableNotificationQueue} + * @see {@link module:enyo/ObserverSupport~ObserverSupport#enableNotificationQueue} + * @param {Boolean} [noQueue] - If `true`, this will also disable the notification queue. + * @returns {this} The callee for chaining. + */ + stopNotifications: function (noQueue) { + this._observing = false; + this._observeCount++; + noQueue && this.disableNotificationQueue(); + return this; + }, + + /** + * Starts [notifications]{@link module:enyo/ObserverSupport~ObserverSupport#notify} if they have + * been [disabled]{@link module:enyo/ObserverSupport~ObserverSupport#stopNotifications}. If the + * notification queue was not disabled, this will automatically flush the + * queue of all notifications that were encountered while stopped. This + * method must be called the same number of times that + * [stopNotifications()]{@link module:enyo/ObserverSupport~ObserverSupport#stopNotifications} was + * called. + * + * @see {@link module:enyo/ObserverSupport~ObserverSupport#stopNotifications} + * @see {@link module:enyo/ObserverSupport~ObserverSupport#disableNotificationQueue} + * @see {@link module:enyo/ObserverSupport~ObserverSupport#enableNotificationQueue} + * @param {Boolean} [queue] - If `true` and the notification queue is disabled, + * the queue will be re-enabled. + * @returns {this} The callee for chaining. + */ + startNotifications: function (queue) { + this._observeCount && this._observeCount--; + this._observeCount === 0 && (this._observing = true); + queue && this.enableNotificationQueue(); + this.isObserving() && flushQueue(this); + return this; + }, + + /** + * Re-enables the notification queue, if it was disabled. + * + * @see {@link module:enyo/ObserverSupport~ObserverSupport#disableNotificationQueue} + * @returns {this} The callee for chaining. + */ + enableNotificationQueue: function () { + this._notificationQueueEnabled = true; + return this; + }, + + /** + * If the notification queue is enabled (the default), it will be disabled + * and any notifications in the queue will be removed. + * + * @see {@link module:enyo/ObserverSupport~ObserverSupport#enableNotificationQueue} + * @returns {this} The callee for chaining. + */ + disableNotificationQueue: function () { + this._notificationQueueEnabled = false; + this._notificationQueue = null; + return this; + }, + + /** + * @private + */ + constructor: kind.inherit(function (sup) { + return function () { + var chains, chain, path, entries, i; + + // if there are any observers that need to create dynamic chains + // we look for and instance those now + if (this._observerChains) { + chains = this._observerChains; + this._observerChains = {}; + for (path in chains) { + entries = chains[path]; + for (i = 0; (chain = entries[i]); ++i) this.observe(path, chain.method); + } + } + + sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + destroy: kind.inherit(function (sup) { + return function () { + var chains = this._observerChains, + path, + chain; + + sup.apply(this, arguments); + + if (chains) { + for (path in chains) { + chain = chains[path]; + chain.destroy(); + } + + this._observerChains = null; + } + }; + }) + +}; + +module.exports = ObserverSupport; + +/** +* Hijack the original so we can add additional default behavior. +* +* @private +*/ +var sup = kind.concatHandler; + +// @NOTE: It seems like a lot of work but it really won't happen that much and the more +// we push to kind-time the better for initialization time + +/** @private */ +kind.concatHandler = function (ctor, props, instance) { + + sup.call(this, ctor, props, instance); + + if (props === ObserverSupport) return; + + var proto = ctor.prototype || ctor + , observers = proto._observers? Object.create(proto._observers): null + , incoming = props.observers + , chains = proto._observerChains && Object.create(proto._observerChains); + + if (!observers) { + if (proto.kindName) observers = {}; + else return; + } + + if (incoming && !(incoming instanceof Array)) { + (function () { + var tmp = [], deps, name; + // here is the slow iteration over the properties... + for (name in props.observers) { + // points to the dependencies of the computed method + deps = props.observers[name]; + // create a single entry now for the method/computed with all dependencies + tmp.push({method: name, path: deps}); + } + incoming = tmp; + }()); + // we need to ensure we don't modify the fixed array of a mixin or reused object + // because it could wind up inadvertantly adding the same entry multiple times + } else if (incoming) incoming = incoming.slice(); + + // this scan is required to figure out what auto-observers might be present + for (var key in props) { + if (key.slice(-7) == "Changed") { + incoming || (incoming = []); + incoming.push({method: key, path: key.slice(0, -7)}); + } + } + + var addObserverEntry = function (path, method) { + var obs; + // we have to make sure that the path isn't a chain because if it is we add it + // to the chains instead + if (path.indexOf(".") > -1) { + if (!chains) chains = {}; + obs = chains[path] || (chains[path] = []); + obs.push({method: method}); + } else { + if (observers[path] && !observers.hasOwnProperty(path)) observers[path] = observers[path].slice(); + obs = observers[path] || (observers[path] = []); + if (!obs.find(function (ln) { return ln.method == method; })) obs.push({method: method}); + } + }; + + if (incoming) { + incoming.forEach(function (ln) { + // first we determine if the path itself is an array of paths to observe + if (ln.path && ln.path instanceof Array) ln.path.forEach(function (en) { addObserverEntry(en, ln.method); }); + else addObserverEntry(ln.path, ln.method); + }); + } + + // we clear the key so it will not be added to the prototype + // delete props.observers; + // we update the properties to whatever their new values may be + proto._observers = observers; + proto._observerChains = chains; +}; + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./ObserverChain':'enyo/ObserverChain'}],'enyo/CoreObject':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/CoreObject~Object} kind. +* @module enyo/CoreObject +*/ + +var + kind = require('./kind'), + logger = require('./logger'), + utils = require('./utils'); + +var + MixinSupport = require('./MixinSupport'), + ObserverSupport = require('./ObserverSupport'), + BindingSupport = require('./BindingSupport'); + +// ComputedSupport is applied to all kinds at creation time but must be require()'d somewhere to be +// included in builds. This is that somewhere. +require('./ComputedSupport'); + +/** +* Used by all [objects]{@link module:enyo/CoreObject~Object} and [subkinds]{@glossary subkind} when using the +* {@link module:enyo/CoreObject~Object#log}, {@link module:enyo/CoreObject~Object#warn} and {@link module:enyo/CoreObject~Object#error} methods. +* +* @private +*/ +function log (method, args) { + if (logger.shouldLog(method)) { + try { + throw new Error(); + } catch(err) { + logger._log(method, [args.callee.caller.displayName + ': '] + .concat(utils.cloneArray(args))); + logger.log(err.stack); + } + } +} + +/** +* {@link module:enyo/CoreObject~Object} lies at the heart of the Enyo framework's implementations of property +* publishing, computed properties (via the [ComputedSupport]{@link module:enyo/ComputedSupport} +* {@glossary mixin}), and data binding (via the {@link module:enyo/BindingSupport~BindingSupport} mixin and +* {@link module:enyo/Binding~Binding} object). It also provides several utility [functions]{@glossary Function} +* for its [subkinds]{@glossary subkind}. +* +* @class Object +* @mixes module:enyo/MixinSupport +* @mixes module:enyo/ObserverSupport +* @mixes module:enyo/BindingSupport +* @public +*/ +var CoreObject = module.exports = kind( + /** @lends module:enyo/CoreObject~Object.prototype */ { + + /** + * @private + */ + name: 'enyo.Object', + + /** + * @private + */ + kind: null, + + /** + * @private + */ + + + /** + * Will be `true` if the [destroy()]{@link module:enyo/CoreObject~Object#destroy} method has been called; + * otherwise, `false`. + * + * @readonly + * @type {Boolean} + * @default false + * @public + */ + destroyed: false, + + /** + * @private + */ + mixins: [MixinSupport, ObserverSupport, BindingSupport], + + /** + * @private + */ + constructor: function (props) { + this.importProps(props); + }, + + /** + * Imports the values from the given [object]{@glossary Object}. Automatically called + * from the [constructor]{@link module:enyo/CoreObject~Object#constructor}. + * + * @param {Object} props - If provided, the [object]{@glossary Object} from which to + * retrieve [keys/values]{@glossary Object.keys} to mix in. + * @returns {this} The callee for chaining. + * @public + */ + importProps: function (props) { + var key; + + if (props) { + kind.concatHandler(this, props, true); + // if props is a default hash this is significantly faster than + // requiring the hasOwnProperty check every time + if (!props.kindName) { + for (key in props) { + kind.concatenated.indexOf(key) === -1 && (this[key] = props[key]); + } + } else { + for (key in props) { + if (kind.concatenated.indexOf(key) === -1 && props.hasOwnProperty(key)) { + this[key] = props[key]; + } + } + } + } + + return this; + }, + + /** + * Calls the [destroy()]{@link module:enyo/CoreObject~Object#destroy} method for the named {@link module:enyo/CoreObject~Object} + * property. + * + * @param {String} name - The name of the property to destroy, if possible. + * @returns {this} The callee for chaining. + * @public + */ + destroyObject: function (name) { + if (this[name] && this[name].destroy) { + this[name].destroy(); + } + this[name] = null; + + return this; + }, + + /** + * Sends a log message to the [console]{@glossary console}, prepended with the name + * of the {@glossary kind} and method from which `log()` was invoked. Multiple + * {@glossary arguments} are coerced to {@glossary String} and + * [joined with spaces]{@glossary Array.join}. + * + * ```javascript + * var kind = require('enyo/kind'), + * Object = require('enyo/CoreObject'); + * kind({ + * name: 'MyObject', + * kind: Object, + * hello: function() { + * this.log('says', 'hi'); + * // shows in the console: MyObject.hello: says hi + * } + * }); + * ``` + * @public + */ + log: function () { + var acc = arguments.callee.caller, + nom = ((acc ? acc.displayName : '') || '(instance method)') + ':', + args = Array.prototype.slice.call(arguments); + args.unshift(nom); + logger.log('log', args); + }, + + /** + * Same as [log()]{@link module:enyo/CoreObject~Object#log}, except that it uses the + * console's [warn()]{@glossary console.warn} method (if it exists). + * + * @public + */ + warn: function () { + log('warn', arguments); + }, + + /** + * Same as [log()]{@link module:enyo/CoreObject~Object#log}, except that it uses the + * console's [error()]{@glossary console.error} method (if it exists). + * + * @public + */ + error: function () { + log('error', arguments); + }, + + /** + * Retrieves the value for the given path. The value may be retrieved as long as the given + * path is resolvable relative to the given {@link module:enyo/CoreObject~Object}. See + * [getPath()]{@link module:enyo/utils#getPath} for complete details. + * + * This method is backwards-compatible and will automatically call any existing getter + * method that uses the "getProperty" naming convention. (Moving forward, however, Enyo code + * should use [computed properties]{@link module:enyo/ComputedSupport} instead of relying on the + * getter naming convention.) + * + * @param {String} path - The path from which to retrieve a value. + * @returns {*} The value for the given path or [undefined]{@glossary undefined} if + * the path could not be completely resolved. + * @public + */ + get: function () { + return utils.getPath.apply(this, arguments); + }, + + /** + * Updates the value for the given path. The value may be set as long as the + * given path is resolvable relative to the given {@link module:enyo/CoreObject~Object}. See + * [setPath()]{@link module:enyo/utils#setPath} for complete details. + * + * @param {String} path - The path for which to set the given value. + * @param {*} value - The value to set. + * @param {Object} [opts] - An options hash. + * @returns {this} The callee for chaining. + * @public + */ + set: function () { + return utils.setPath.apply(this, arguments); + }, + + /** + * Binds a [callback]{@glossary callback} to this [object]{@link module:enyo/CoreObject~Object}. + * If the object has been destroyed, the bound method will be aborted cleanly, + * with no value returned. + * + * This method should generally be used instead of {@link module:enyo/utils#bind} for running + * code in the context of an instance of {@link module:enyo/CoreObject~Object} or one of its + * [subkinds]{@glossary subkind}. + * + * @public + */ + bindSafely: function () { + var args = Array.prototype.slice.call(arguments); + args.unshift(this); + return utils.bindSafely.apply(null, args); + }, + + /** + * An abstract method (primarily) that sets the [destroyed]{@link module:enyo/CoreObject~Object#destroyed} + * property to `true`. + * + * @returns {this} The callee for chaining. + * @public + */ + destroy: function () { + + // Since JS objects are never truly destroyed (GC'd) until all references are + // gone, we might have some delayed action on this object that needs access + // to this flag. + // Using this.set to make the property observable + return this.set('destroyed', true); + } +}); + +/** +* @private +*/ +CoreObject.concat = function (ctor, props) { + var pubs = props.published, + cpy, + prop; + + if (pubs) { + cpy = ctor.prototype || ctor; + for (prop in pubs) { + // need to make sure that even though a property is 'published' + // it does not overwrite any computed properties + if (props[prop] && typeof props[prop] == 'function') continue; + addGetterSetter(prop, pubs[prop], cpy); + } + } +}; + +/** +* This method creates a getter/setter for a published property of an {@link module:enyo/CoreObject~Object}, but is +* deprecated. It is maintained for purposes of backwards compatibility. The preferred method is +* to mark public and protected (private) methods and properties using documentation or other +* means and rely on the [get]{@link module:enyo/CoreObject~Object#get} and [set]{@link module:enyo/CoreObject~Object#set} methods of +* {@link module:enyo/CoreObject~Object} instances. +* +* @private +*/ +function addGetterSetter (prop, value, proto) { + + // so we don't need to re-execute this over and over and over... + var cap = utils.cap(prop), + getName = 'get' + cap, + setName = 'set' + cap, + getters = proto._getters || (proto._getters = {}), + setters = proto._setters || (proto._setters = {}), + fn; + + // we assign the default value from the published block to the prototype + // so it will be initialized properly + proto[prop] = value; + + // check for a supplied getter and if there isn't one we create one otherwise + // we mark the supplied getter in the tracking object so the global getPath will + // know about it + if (!(fn = proto[getName]) || typeof fn != 'function') { + fn = proto[getName] = function () { + return utils.getPath.fast.call(this, prop); + }; + + // and we mark it as generated + fn.generated = true; + } else if (fn && typeof fn == 'function' && !fn.generated) getters[prop] = getName; + + // we need to do the same thing for the setters + if (!(fn = proto[setName]) || typeof fn != 'function') { + fn = proto[setName] = function (val) { + return utils.setPath.fast.call(this, prop, val); + }; + + // and we mark it as generated + fn.generated = true; + } else if (fn && typeof fn == 'function' && !fn.generated) setters[prop] = setName; +} + +},{'./kind':'enyo/kind','./logger':'enyo/logger','./utils':'enyo/utils','./MixinSupport':'enyo/MixinSupport','./ObserverSupport':'enyo/ObserverSupport','./BindingSupport':'enyo/BindingSupport','./ComputedSupport':'enyo/ComputedSupport'}],'enyo/AnimationSupport/Core':[function (module,exports,global,require,request){ +require('enyo'); + +var + kind = require('../kind'), + animation = require('../animation'), + utils = require('../utils'), + tween = require('./Tween'); + +var + CoreObject = require('../CoreObject'); + +/** +* This module returns the Loop singleton +* Core module is responsible for handling all animations happening in Enyo. +* The responsibilities of this module is to; +* - Trigger vendor specific rAF. +* - Knowing all elements which have requested for animation. +* - Tween animation frames for each characters. +* +* @module enyo/Core +*/ +module.exports = kind.singleton({ + /** @lends module:enyo/Core */ + + /** + * @private + */ + name: 'enyo.Core', + /** + * @private + */ + kind: CoreObject, + + /** + * @private + */ + chracs: [], + + /** + * @private + */ + evnts: [], + + /** + * @private + */ + req: 0, + + /** + * @private + */ + running: false, + + /** + * Core base API to start animation functionalities. + * The purpose of this method is to check if the animation is already started or not + * otherwise wake up core to handle animation for a character. + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * @parameter charc- Animation character + * + * @public + */ + trigger: function (charc) { + if (!charc.animating) { + this.chracs.push(charc); + } + if (!this.running) { + this.running = true; + this.start(); + } + }, + + /** + * Core public API to check if core is handling animation for particular + * document element. + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * @parameter charc- Animation character + * + * @public + */ + exists: function (eventTarget) { + for (var i = 0; i < this.chracs.length; i++) { + if (this.chracs[i].hasNode() === eventTarget) { // Already Animating + return this.chracs[i]; + } + } + }, + + /** + * Animator public API to remove animation happening on a particular + * document element. + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * @parameter charc- Animation character + * + * @public + */ + remove: function (curr) { + this.chracs.splice(this.chracs.indexOf(curr), 1); + }, + + /** + * Animator public API to register character with event + * + * @parameter charc- Animation character + * + * @public + */ + register: function (charc) { + this.evnts.push(charc); + + /*if (!this.running) { + this.running = true; + this.start(); + }*/ + this.start(); + }, + + /** + * @private + */ + start: function () { + this.req = animation.requestAnimationFrame(this.bindSafely(this.loop)); + }, + + /** + * @private + */ + cancel: function () { + animation.cancelRequestAnimationFrame(this.req); + }, + + /** + * @private + */ + loop: function () { + var i, curr, + len = this.chracs.length, + ts = utils.perfNow(); + + if (len <= 0) { + this.cancel(); + this.running = false; + return; + } + + for (i = 0; i < len; i++) { + curr = this.chracs[i]; + if (curr && curr.ready()) { + tween.update(curr, ts); + if (!curr._lastTime || ts >= curr._lastTime) { + tween.complete(curr); + curr.completed(curr); + if(!curr.active) { + this.remove(curr); + } + } + } + } + + len = this.evnts.length; + for (i = 0; i < len; i++) { + if (typeof this.evnts[i].commitAnimation === 'function') { + this.evnts[i].commitAnimation(); + } + } + this.start(); + }, + + /** + * @private + */ + dummy: function () { + animation.requestAnimationFrame(function() {}); + } +}); +},{'../kind':'enyo/kind','../animation':'enyo/animation','../utils':'enyo/utils','./Tween':'enyo/AnimationSupport/Tween','../CoreObject':'enyo/CoreObject'}],'enyo/Store':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Store~Store} kind. +* @module enyo/Store +*/ + +var + kind = require('./kind'), + utils = require('./utils'); + +var + ModelList = require('./ModelList'), + EventEmitter = require('./EventEmitter'), + CoreObject = require('./CoreObject'); + +/** +* Only necessary because of the order in which mixins are applied. +* +* @class +* @private +*/ +var BaseStore = kind({ + kind: CoreObject, + mixins: [EventEmitter] +}); + +/** +* This method should determine whether the given [model]{@link module:enyo/Model~Model} +* should be included in the filtered set for the [find()]{@link module:enyo/Store~Store#find} +* method. +* +* @callback enyo.Store~Filter +* @param {module:enyo/Model~Model} model - The [model]{@link module:enyo/Model~Model} to filter. +* @returns {Boolean} `true` if the model meets the filter requirements; +* otherwise, `false`. +*/ + +/** +* The configuration options for the [find()]{@link module:enyo/Store~Store#find} method. +* +* @typedef {Object} enyo.Store~FindOptions +* @property {Boolean} all=true - Whether or not to include more than one match for the +* filter method. If `true`, an array of matches is returned; otherwise, a single match. +* @property {Object} context - If provided, it will be used as the `this` (context) of +* the filter method. +*/ + +/** +* An anonymous kind used internally for the singleton {@link module:enyo/Store~Store}. +* +* @class Store +* @mixes module:enyo/EventEmitter +* @extends module:enyo/CoreObject~Object +* @protected +*/ +var Store = kind( + /** @lends module:enyo/Store~Store.prototype */ { + + name: 'enyo.Store', + + /** + * @private + */ + kind: BaseStore, + + /** + * Finds a [model (or models)]{@link module:enyo/Model~Model} of a certain [kind]{@glossary kind}. + * It uses the return value from a filter method to determine whether a particular + * model will be included. Set the optional `all` flag to `true` to ensure that + * the method looks for all matches; otherwise, it will return the first positive + * match. + * + * @see {@glossary Array.find} + * @param {module:enyo/Model~Model} ctor - The constructor for the [kind]{@glossary kind} of + * [model]{@link module:enyo/Model~Model} to be filtered. + * @param {module:enyo/Store~Store~Filter} fn - The filter method. + * @param {module:enyo/Store~Store~FindOptions} [opts] - The options parameter. + * @returns {(module:enyo/Model~Model|module:enyo/Model~Model[]|undefined)} If the `all` flag is `true`, + * returns an array of models; otherwise, returns the first model that returned + * that returned `true` from the filter method. Returns `undefined` if `all` is + * `false` and no match could be found. + * @public + */ + find: function (ctor, fn, opts) { + var kindName = ctor.prototype.kindName, + list = this.models[kindName], + options = {all: true, context: this}; + + // allows the method to be called with a constructor only and will return an + // immutable copy of the array of all models of that type or an empty array + if (arguments.length == 1 || typeof fn != 'function') { + return list ? list.slice() : []; + } + + // ensure we use defaults with any provided options + opts = opts ? utils.mixin({}, [options, opts]) : options; + + if (list) return opts.all ? list.filter(fn, opts.context) : list.find(fn, opts.context); + + // if it happens it could not find a list for the requested kind we fudge the return + // so it can keep on executing + else return opts.all ? [] : undefined; + }, + + /** + * This method is an alias for [find()]{@link module:enyo/Store~Store#find}. + * + * @deprecated + * @public + */ + findLocal: function () { + return this.find.apply(this, arguments); + }, + + /** + * @private + */ + add: function (models, opts) { + var ctor = models && (models instanceof Array ? models[0].ctor : models.ctor), + kindName = ctor && ctor.prototype.kindName, + list = kindName && this.models[kindName], + added, + i; + + // if we were able to find the list then we go ahead and attempt to add the models + if (list) { + added = list.add(models); + // if we successfully added models and this was a default operation (not being + // batched by a collection or other feature) we emit the event needed primarily + // by relational models but could be useful other places + if (added.length && (!opts || !opts.silent)) { + for (i = 0; i < added.length; ++i) { + this.emit(ctor, 'add', {model: added[i]}); + } + } + } + + return this; + }, + + /** + * @private + */ + remove: function (models, opts) { + var ctor = models && (models instanceof Array ? models[0].ctor : models.ctor), + kindName = ctor && ctor.prototype.kindName, + list = kindName && this.models[kindName], + removed, + i; + + // if we were able to find the list then we go ahead and attempt to remove the models + if (list) { + removed = list.remove(models); + // if we successfully removed models and this was a default operation (not being + // batched by a collection or other feature) we emit the event. Needed primarily + // by relational models but could be useful other places + if (removed.length && (!opts || !opts.silent)) { + for (i = 0; i < removed.length; ++i) { + this.emit(ctor, 'remove', {model: removed[i]}); + } + } + } + + return this; + }, + + /** + * Determines, from the given parameters, whether the [store]{@link module:enyo/Store~Store} + * has a specific [model]{@link module:enyo/Model~Model}. + * + * @param {(Function|module:enyo/Model~Model)} ctor Can be the constructor for an {@link module:enyo/Model~Model} + * or a model instance. Must be a constructor unless a model instance is passed as the + * optional `model` parameter. + * @param {(String|Number|module:enyo/Model~Model)} [model] If the `ctor` parameter is a + * constructor, this may be a [Number]{@glossary Number} or a [String]{@glossary String} + * representing a [primaryKey]{@link module:enyo/Model~Model#primaryKey} for the given model, or an + * instance of a model. + * @returns {Boolean} Whether or not the [store]{@link module:enyo/Store~Store} has the given + * [model]{@link module:enyo/Model~Model}. + * @public + */ + has: function (ctor, model) { + var list; + + if (!model) { + model = ctor; + ctor = model.ctor; + } + + list = this.models[ctor.prototype.kindName]; + return list ? list.has(model) : false; + }, + + /** + * @private + */ + resolve: function (ctor, model) { + var list = this.models[ctor && ctor.prototype.kindName]; + return list? list.resolve(model): undefined; + }, + + /** + * @private + */ + constructor: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + + this._scopeListeners = []; + + // all future sub-kinds of enyo.Model that are processed will automatically + // create/add their entries to this object in their concat method + this.models = { + 'enyo.Model': new ModelList() + }; + }; + }), + + /** + * @private + */ + scopeListeners: function (scope, e) { + return !scope ? this._scopeListeners : this._scopeListeners.filter(function (ln) { + return ln.scope === scope ? !e ? true : ln.event == e : false; + }); + }, + + /** + * @private + */ + on: kind.inherit(function (sup) { + return function (ctor, e, fn, ctx) { + if (typeof ctor == 'function') { + this.scopeListeners().push({ + scope: ctor, + event: e, + method: fn, + ctx: ctx || this + }); + + return this; + } + + return sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + off: kind.inherit(function (sup) { + return function (ctor, e, fn) { + var listeners, + idx; + + if (typeof ctor == 'function') { + listeners = this.scopeListeners(ctor); + if (listeners.length) { + idx = listeners.findIndex(function (ln) { + return ln.event == e && ln.method === fn; + }); + + // if it found the entry we remove it + if (idx >= 0) listeners.splice(idx, 1); + } + return this; + } + }; + }), + + /** + * @private + */ + emit: kind.inherit(function (sup) { + return function (ctor, e) { + var listeners, + args; + + if (typeof ctor == 'function') { + listeners = this.scopeListeners(ctor, e); + + if (listeners.length) { + args = utils.toArray(arguments).slice(1); + args.unshift(this); + listeners.forEach(function (ln) { + ln.method.apply(ln.ctx, args); + }); + return true; + } + return false; + } + + return sup.apply(this, arguments); + }; + }) +}); + +/** +* A runtime database for working with [models]{@link module:enyo/Model~Model}. It is primarily used +* internally by data layer [kinds]{@glossary kind} ({@link module:enyo/Model~Model}, +* {@link module:enyo/Collection~Collection}, and {@link module:enyo/RelationalModel~RelationalModel}). +* +* @see module:enyo/Model~Model +* @see module:enyo/Collection~Collection +* @see module:enyo/RelationalModel~RelationalModel +* @type enyo.Store +* @memberof enyo +* @public +*/ +module.exports = new Store(); + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./ModelList':'enyo/ModelList','./EventEmitter':'enyo/EventEmitter','./CoreObject':'enyo/CoreObject'}],'enyo/jobs':[function (module,exports,global,require,request){ +require('enyo'); + +var + utils = require('./utils'), + kind = require('./kind'); + +var CoreObject = require('./CoreObject'); + +/** +* The {@link module:enyo/jobs} singleton provides a mechanism for queueing tasks +* (i.e., functions) for execution in order of priority. The execution of the +* current job stack may be blocked programmatically by setting a priority +* level (run level) below which no jobs are executed. +* +* At the moment, only {@link module:enyo/Animator~Animator} uses this interface, setting a +* priority of 4, which blocks all low priority tasks from executing during +* animations. To maintain backward compatibility, jobs are assigned a priority +* of 5 by default; thus they are not blocked by animations. +* +* Normally, application code will not use `enyo.jobs` directly, but will +* instead use the [job()]{@link module:enyo/Component~Component#job} method of +* {@link module:enyo/Component~Component}. +* +* @module enyo/jobs +* @public +*/ +module.exports = kind.singleton( + /** @lends module:enyo/jobs */ { + + kind: CoreObject, + + /** + * @private + */ + published: /** @lends module:enyo/jobs~jobs */ { + + /** + * The current priority level. + * + * @type {Number} + * @default 0 + * @public + */ + priorityLevel: 0 + }, + + /** + * Prioritized by index. + * + * @private + */ + _jobs: [ [], [], [], [], [], [], [], [], [], [] ], + + /** + * @private + */ + _priorities: {}, + + /** + * @private + */ + _namedJobs: {}, + + /** + * @private + */ + _magicWords: { + 'low': 3, + 'normal': 5, + 'high': 7 + }, + + /** + * Adds a [job]{@link module:enyo/job} to the job queue. If the current priority + * level is higher than this job's priority, this job gets deferred until the + * job level drops; if it is lower, this job is run immediately. + * + * @param {Function} job - The actual {@glossary Function} to execute as the + * [job]{@link module:enyo/job}. + * @param {Number} priority - The priority of the job. + * @param {String} nom - The name of the job for later reference. + * @public + */ + add: function (job, priority, nom) { + priority = priority || 5; + + // magic words: low = 3, normal = 5, high = 7 + priority = utils.isString(priority) ? this._magicWords[priority] : priority; + + // if a job of the same name exists, remove it first (replace it) + if(nom){ + this.remove(nom); + this._namedJobs[nom] = priority; + } + + // if the job is of higher priority than the current priority level then + // there's no point in queueing it + if(priority >= this.priorityLevel){ + job(); + } else { + this._jobs[priority - 1].push({fkt: job, name: nom}); + } + }, + + /** + * Will remove the named [job]{@link module:enyo/job} from the queue. + * + * @param {String} nom - The name of the [job]{@link module:enyo/job} to remove. + * @returns {Array} An {@glossary Array} that will contain the removed job if + * it was found, or empty if it was not found. + * @public + */ + remove: function (nom) { + var jobs = this._jobs[this._namedJobs[nom] - 1]; + if(jobs){ + for(var j = jobs.length-1; j >= 0; j--){ + if(jobs[j].name === nom){ + return jobs.splice(j, 1); + } + } + } + }, + + /** + * Adds a new priority level at which jobs will be executed. If it is higher than the + * highest current priority, the priority level rises. Newly added jobs below that priority + * level are deferred until the priority is removed (i.e., unregistered). + * + * @param {Number} priority - The priority value to register. + * @param {String} id - The name of the priority. + * @public + */ + registerPriority: function(priority, id) { + this._priorities[id] = priority; + this.setPriorityLevel( Math.max(priority, this.priorityLevel) ); + }, + + /** + * Removes a priority level. If the removed priority was previously the + * highest priority, the priority level drops to the next highest priority + * and queued jobs with a higher priority are executed. + * + * @param {String} id - The name of the priority level to remove. + * @public + */ + unregisterPriority: function (id) { + var highestPriority = 0; + + // remove priority + delete this._priorities[id]; + + // find new highest current priority + for( var i in this._priorities ){ + highestPriority = Math.max(highestPriority, this._priorities[i]); + } + + this.setPriorityLevel( highestPriority ); + }, + + /** + * Tries to run next job if priority level has dropped. + * + * @type {module:enyo/ObserverSupport~ObserverSupport~Observer} + * @private + */ + priorityLevelChanged: function (was) { + if(was > this.priorityLevel){ + this._doJob(); + } + }, + + /** + * Finds and executes the job of highest priority; in this way, all jobs with priority + * greater than or equal to the current level are run, in order of their priority (highest + * to lowest). + * + * @private + */ + _doJob: function () { + var job; + // find the job of highest priority above the current priority level + // and remove from the job list + for (var i = 9; i >= this.priorityLevel; i--){ + if (this._jobs[i].length) { + job = this._jobs[i].shift(); + break; + } + } + + // allow other events to pass through + if (job) { + job.fkt(); + delete this._namedJobs[job.name]; + setTimeout(utils.bind(this, '_doJob'), 10); + } + } +}); + +},{'./utils':'enyo/utils','./kind':'enyo/kind','./CoreObject':'enyo/CoreObject'}],'enyo/AnimationSupport/Fadeable':[function (module,exports,global,require,request){ +var + kind = require('../kind'), + animation = require('./Core'); + +/** + * Interface to achieve fade animation + * + * @module enyo/AnimationSupport/Fadeable + * @public + */ +module.exports = { + + /** + * @private + */ + name: 'Fadeable', + + /** + * To start animation + */ + animate: true, + + /** + * @private + */ + fadableValue: 0, + + /** + * @public + * Make the character invisible + */ + invisible: function() { + this.addAnimation({ + opacity: 0 + }); + }, + + /** + * @public + * Make the character transparent + * @default 0.5 + * @parameter value - set transparency value + */ + transparent: function(value) { + value = value || 0.5; + this.addAnimation({ + opacity: value + }); + }, + + /** + * @public + * Make the character visible + */ + opaque: function() { + this.addAnimation({ + opacity: 1 + }); + }, + + /** + * @public + * Fade element based on event trigger + */ + fadeByDelta: function(deltaValue) { + if (deltaValue !== 0) { + this.fadableValue = this.fadableValue + deltaValue * 0.1; + if (this.fadableValue <= 0) { + this.fadableValue = 0; + } else if (this.fadableValue >= 1) { + this.fadableValue = 1; + } + } + this.addAnimation({ + opacity: this.fadableValue + }); + }, + + /** + * @public + * Bubble the fadeable event + */ + triggerEvent: function(e) { + console.log("TODO: Trigger the fadeable event" + e); + //this.doFadeStart(); + } +}; + +},{'../kind':'enyo/kind','./Core':'enyo/AnimationSupport/Core'}],'enyo/AnimationSupport/Slideable':[function (module,exports,global,require,request){ +var + kind = require('../kind'), + animation = require('./Core'); + +/** + * Interface to achieve slide animation + * + * @module enyo/AnimationSupport/Slideable + * @public + */ +module.exports = { + + /** + * @private + */ + name: 'Slideable', + + /** + * To start animation + */ + animate: true, + + /** + * @public + * slide animation in left direction + * @parameter: slideDistance - distance in pixels to slide in left direction + */ + left: function(slideDistance) { + this.slide((-1 * slideDistance), 0, 0); + }, + + /** + * @public + * slide animation in right direction + * @parameter: slideDistance - distance in pixels to slide in right direction + */ + right: function(slideDistance) { + this.slide(slideDistance, 0, 0); + }, + + /** + * @public + * slide animation upward + * @parameter: slideDistance - distance in pixels to slide upward + */ + up: function(slideDistance) { + this.slide(0, (-1 * slideDistance), 0); + }, + + /** + * @public + * slide animation downward + * @parameter: slideDistance - distance in pixels to slide downward + */ + down: function(slideDistance) { + this.slide(0, slideDistance, 0); + }, + + /** + * @public + * slide animation in custom direction + * @parameter: x - css property to slide in x-axis direction + * @parameter: y - css property to slide in y-axis direction + * @parameter: z - css property to slide in z-axis direction + */ + slide: function(x, y, z) { + x = x || 0; + y = y || 0; + z = z || 0; + switch (this.direction) { + case "horizontal": + this.addAnimation({ + translate: x + "," + 0 + "," + 0 + }); + break; + case "vertical": + this.addAnimation({ + translate: 0 + "," + y + "," + 0 + }); + break; + default: + this.addAnimation({ + translate: x + "," + y + "," + z + }); + } + } +}; + +},{'../kind':'enyo/kind','./Core':'enyo/AnimationSupport/Core'}],'enyo/AnimationSupport/KeyFrame':[function (module,exports,global,require,request){ +require('enyo'); + +var + kind = require('../kind'), + animation = require('./Core'), + utils = require('../utils'), + CoreObject = require('../CoreObject'); + +/** +* This module returns the Loop singleton +* @module enyo/KeyFrame +*/ +var keyFrame = module.exports = kind.singleton({ + /** @lends module:enyo/KeyFrame */ + + /** + * @private + */ + name: 'enyo.KeyFrame', + /** + * @private + */ + kind: CoreObject, + + /** + * KeyFrame base API to perform animation on any document element + * repersented as a Character. The purpose of this method is to add a new + * character to Animation Core based on animation properties passed as + * parameter to this function and also to manage the frames allocated to + * each of individual poses. + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * @parameter charc- Character responsible for animation. + * keyframe- Key frame Animation propeties represented as CSS objects. + * like: {0: {"rotateX": "0"}, 50: {"rotateX": "90"}, 100: {"rotateX": "180"}} + * @public + */ + animate: function (charc, proto) { + var prop, + cb = proto.completed, + keyframe = proto.keyFrame; + + charc.keyProps = []; + charc.keyTime = []; + charc.currentIndex = 0; + for (prop in keyframe) { + charc.keyTime.push(prop); + charc.keyProps.push(keyframe[prop]); + } + charc.keyframeCallback = cb; + charc.initialTime = utils.perfNow(); + charc.totalDuration = proto.duration; + charc.completed = this.bindSafely(this.reframe); + + this.keyFraming(charc); + }, + + /** + * KeyFrame's public API to reverse an animation. + * The purpose of this method is to find the animating character based on + * the DOM provided and reversing a keyframe animation by interchanging its intial + * state with final state and final state with current state + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * @parameter dom- Document element on which animation will be reversed. + * + * @public + */ + reverse: function (dom) { + var charc = animation.exists(dom), + finalState, duration; + if (charc) { + finalState = charc._startAnim; + duration = utils.perfNow() - charc.initialTime; + animation.remove(charc); + + charc.setAnimation(finalState); + charc.setInitial(charc.currentState); + charc.setDuration(duration); + charc.totalDuration = duration; + charc.keyProps = []; + charc.keyTime = []; + charc.animating = false; + animation.trigger(charc); + } + }, + + trigger: function (charc) { + animation.trigger(charc); + } +}); + +/** +* @private +*/ +keyFrame.keyFraming = function (charc, callback) { + var index = charc.currentIndex || 0, + old = charc.keyTime[index -1], + next = charc.keyTime[index], + total = charc.totalDuration, + time = charc.currentIndex ? total * ((next - old)/100) : "0"; + + charc.setAnimation(charc.keyProps[index]); + charc.setInitial(charc.currentState); + charc.setDuration(time); + charc.animating = false; + charc.currentIndex = index; + animation.trigger(charc); +}; + +/** +* @private +*/ +keyFrame.reframe = function (charc) { + charc.currentIndex++; + if (charc.currentIndex < charc.keyTime.length) { + this.keyFraming(charc); + charc.start(); + } else { + //Tigerring callback function at end of animation + charc.keyframeCallback && charc.keyframeCallback(this); + } +}; + +},{'../kind':'enyo/kind','./Core':'enyo/AnimationSupport/Core','../utils':'enyo/utils','../CoreObject':'enyo/CoreObject'}],'enyo/Model':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Model~Model} kind. +* @module enyo/Model +*/ + +var + kind = require('./kind'), + utils = require('./utils'); + +var + ObserverSupport = require('./ObserverSupport'), + ComputedSupport = require('./ComputedSupport'), + BindingSupport = require('./BindingSupport'), + EventEmitter = require('./EventEmitter'), + StateSupport = require('./StateSupport'), + ModelList = require('./ModelList'), + Source = require('./Source'), + States = require('./States'), + Store = require('./Store'); + +/** +* This is only necessary because of the order in which mixins are applied. +* +* @class +* @private +*/ +var BaseModel = kind({ + kind: null, + mixins: [ObserverSupport, ComputedSupport, BindingSupport, EventEmitter, StateSupport] +}); + +/** +* The event emitted when [attributes]{@link module:enyo/Model~Model#attributes} have been modified. +* The event [object]{@glossary Object} will consist of key/value pairs of attributes +* that changed and their new values. +* +* @event module:enyo/Model~Model#change +* @type {Object} +* @public +*/ + +/** +* The default configurable [options]{@link module:enyo/Model~Model#options} used in certain API methods +* of {@link module:enyo/Model~Model}. +* +* @typedef {Object} module:enyo/Model~Model~Options +* @property {Boolean} silent=false - Keep events and notifications from being emitted. +* @property {Boolean} commit=false - Immediately [commit]{@link module:enyo/Model~Model#commit} changes +* after they have occurred. Also note that, if `true`, when the [model]{@link module:enyo/Model~Model} +* is [destroyed]{@link module:enyo/Model~Model#destroy}, it will also be destroyed via any +* [sources]{@link module:enyo/Model~Model#source} it has. +* @property {Boolean} parse=false - During initialization, [parse]{@link module:enyo/Model~Model#parse} +* any given [attributes]{@link module:enyo/Model~Model#attributes}; after +* [fetching]{@link module:enyo/Model~Model#fetch}, parse the data before calling +* [set()]{@link module:enyo/Model~Model#set}. +* @property {Boolean} fetch=false - Automatically call [fetch()]{@link module:enyo/Model~Model#fetch} +* during initialization. +*/ + +/** +* The configurable options for [fetch()]{@link module:enyo/Model~Model#fetch}, +* [commit()]{@link module:enyo/Model~Model#commit}, and [destroy()]{@link module:enyo/Model~Model#destroy}. +* +* @typedef {module:enyo/Model~Model~Options} module:enyo/Model~Model~ActionOptions +* @property {module:enyo/Model~Model~Success} success - The callback executed upon successful +* completion. +* @property {module:enyo/Model~Model~Error} error - The callback executed upon a failed attempt. +*/ + +/** +* @callback module:enyo/Model~Model~Success +* @param {module:enyo/Model~Model} model - The [model]{@link module:enyo/Model~Model} that is returning successfully. +* @param {module:enyo/Model~Model~ActionOptions} opts - The original options passed to the action method +* that is returning successfully. +* @param {*} res - The result, if any, returned by the [source]{@link module:enyo/Source~Source} that +* executed it. +* @param {String} source - The name of the [source]{@link module:enyo/Model~Model#source} that has +* returned successfully. +*/ + +/** +* @callback module:enyo/Model~Model~Error +* @param {module:enyo/Model~Model} model - The model that is returning an error. +* @param {String} action - The name of the action that failed, one of `'FETCHING'`, +* `'COMMITTING'`, or `'DESTROYING'`. +* @param {module:enyo/Model~Model~Options} opts - The original options passed to the action method +* that is returning an error. +* @param {*} res - The result, if any, returned by the [source]{@link module:enyo/Source~Source} that +* executed it. +* @param {String} source - The name of the [source]{@link module:enyo/Model~Model#source} that has +* returned an error. +*/ + +/** +* An [object]{@glossary Object} used to represent and maintain state. Usually, +* an {@link module:enyo/Model~Model} is used to expose data to the view layer. It keeps logic +* related to the data (retrieving it, updating it, storing it, etc.) out of the +* view, and the view can automatically update based on changes in the model. +* Models have the ability to work with other data layer [kinds]{@glossary kind} +* to provide more sophisticated implementations. +* +* Models have [bindable]{@link module:enyo/BindingSupport~BindingSupport} +* [attributes]{@link module:enyo/Model~Model#attributes}. Models differs from other +* bindable kinds in that attribute values are proxied from an internal +* [hash]{@glossary Object} instead of being set on the target properties +* directly. +* +* @see module:enyo/Store~Store +* @see module:enyo/Collection~Collection +* @see module:enyo/RelationalModel~RelationalModel +* @see module:enyo/ModelController~ModelController +* @class Model +* @mixes module:enyo/ObserverSupport~ObserverSupport +* @mixes module:enyo/ComputedSupport~ComputedSupport +* @mixes module:enyo/BindingSupport~BindingSupport +* @mixes module:enyo/EventEmitter +* @mixes module:enyo/StateSupport~StateSupport +* @public +*/ +var Model = module.exports = kind( + /** @lends module:enyo/Model~Model.prototype */ { + + name: 'enyo.Model', + + /** + * @private + */ + kind: BaseModel, + + /** + * @private + */ + + + /** + * Used by various [sources]{@link module:enyo/Model~Model#source} as part of the + * [URI]{@glossary URI} from which they may be [fetched]{@link module:enyo/Model~Model#fetch}, + * [committed]{@link module:enyo/Model~Model#commit}, or [destroyed]{@link module:enyo/Model~Model#destroy}. + * Some sources may use this property in other ways. + * + * @see module:enyo/Model~Model#getUrl + * @see module:enyo/Source~Source + * @see module:enyo/AjaxSource~AjaxSource + * @see module:enyo/JsonpSource~JsonpSource + * @type {String} + * @default '' + * @public + */ + url: '', + + /** + * Implement this method to be used by [sources]{@link module:enyo/Model~Model#source} to + * dynamically derive the [URI]{@glossary URI} from which they may be + * [fetched]{@link module:enyo/Model~Model#fetch}, [committed]{@link module:enyo/Model~Model#commit}, + * or [destroyed]{@link module:enyo/Model~Model#destroy}. Some sources may use this + * property in other ways. Note that, if this method is implemented, the + * [url]{@link module:enyo/Model~Model#url} will not be used. + * + * @see module:enyo/Model~Model#url + * @see module:enyo/Source~Source + * @see module:enyo/AjaxSource~AjaxSource + * @see module:enyo/JsonpSource~JsonpSource + * @type {Function} + * @default null + * @virtual + * @public + */ + getUrl: null, + + /** + * The [hash]{@glossary Object} of properties proxied by this [model]{@link module:enyo/Model~Model}. + * If defined on a [subkind]{@glossary subkind}, it may be assigned default values and + * all instances will share its default structure. If no attributes are defined, an + * empty [hash]{@glossary Object} will be assigned during initialization. It is not + * necessary to pre-define the structure of a model; depending on the model's complexity, + * pre-defining the structure may possibly hinder performance. + * + * It should also be noted that calls to [get()]{@link module:enyo/Model~Model#get} or + * [set()]{@link module:enyo/Model~Model#set} will access and modify this property. This includes + * the values to which (or from which) [bindings]{@link module:enyo/BindingSupport~BindingSupport} are bound. + * + * @type {Object} + * @default null + * @public + */ + attributes: null, + + /** + * The [source(s)]{@link module:enyo/Source~Source} to use when [fetching]{@link module:enyo/Model~Model#fetch}, + * [committing]{@link module:enyo/Model~Model#commit}, or [destroying]{@link module:enyo/Model~Model#destroy}. + * Any method that uses sources may override this default value in its configuration + * options. This value may be a [string]{@glossary String}, an + * [Array]{@glossary Array} of strings, an instance of {@link module:enyo/Source~Source}, or an + * array of `enyo/Source` instances. + * + * @see module:enyo/Source~Source + * @see module:enyo/Model~Model#fetch + * @see module:enyo/Model~Model#commit + * @see module:enyo/Model~Model#destroy + * @type {(String|String[]|module:enyo/Source~Source|module:enyo/Source~Source[])} + * @default null + * @public + */ + source: null, + + /** + * These [keys]{@glossary Object.keys} will be the only + * [attributes]{@link module:enyo/Model~Model#attributes} included if the + * [model]{@link module:enyo/Model~Model} is [committed]{@link module:enyo/Model~Model#commit}. This + * directly modifies the result of calling [raw()]{@link module:enyo/Model~Model#raw}. If + * not defined, all keys from the [attributes]{@link module:enyo/Model~Model#attributes} + * [hash]{@glossary Object} will be used. + * + * @see module:enyo/Model~Model#raw + * @see module:enyo/Model~Model#toJSON + * @type {String[]} + * @default null + * @public + */ + includeKeys: null, + + /** + * The inheritable default configuration options. These specify the behavior of particular + * API features of {@link module:enyo/Model~Model}. Any method that uses these options may override + * the default values in its own configuration options. Note that setting an + * [options hash]{@glossary Object} on a [subkind]{@glossary subkind} will result in + * the new values' being merged with--not replacing--the + * [superkind's]{@glossary superkind} own `options`. + * + * @type {module:enyo/Model~Model~Options} + * @public + */ + options: { + silent: false, + commit: false, + parse: false, + fetch: false + }, + + /** + * The current [state(s)]{@link module:enyo/States} possessed by the [model]{@link module:enyo/Model~Model}. + * There are limitations as to which state(s) the model may possess at any given time. + * By default, a model is [NEW]{@link module:enyo/States#NEW} and [CLEAN]{@link module:enyo/States#CLEAN}. + * Note that this is **not** a [bindable]{@link module:enyo/BindingSupport~BindingSupport} property. + * + * @see module:enyo/States~States + * @see {@link module:enyo/StateSupport~StateSupport} + * @type {module:enyo/States~States} + * @readonly + * @public + */ + status: States.NEW | States.CLEAN, + + /** + * The unique attribute by which the [model]{@link module:enyo/Model~Model} may be indexed. The + * attribute's value must be unique across all instances of the specific model + * [kind]{@glossary kind} + * + * @type {String} + * @default 'id' + * @public + */ + primaryKey: 'id', + + /** + * Inspects and restructures incoming data prior to [setting]{@link module:enyo/Model~Model#set} it on + * the [model]{@link module:enyo/Model~Model}. While this method may be called directly, it is most + * often used via the [parse]{@link module:enyo/Model~Model~Options#parse} option and executed + * automatically, either during initialization or when [fetched]{@link module:enyo/Model~Model#fetch} + * (or, in some cases, both). This is a virtual method and must be provided to suit a + * given implementation's needs. + * + * @see module:enyo/Model~Model~Options#parse + * @param {*} data - The incoming data that may need to be restructured or reduced prior to + * being [set]{@link module:enyo/Model~Model#set} on the [model]{@link module:enyo/Model~Model}. + * @returns {Object} The [hash]{@glossary Object} to apply to the + * model via [set()]{@link module:enyo/Model~Model#set}. + * @virtual + * @public + */ + parse: function (data) { + return data; + }, + + /** + * Returns an [Object]{@glossary Object} that represents the underlying data structure + * of the [model]{@link module:enyo/Model~Model}. This is dependent on the current + * [attributes]{@link module:enyo/Model~Model#attributes} as well as the + * [includeKeys]{@link module:enyo/Model~Model#includeKeys}. + * [Computed properties]{@link module:enyo/ComputedSupport} are **never** included. + * + * @see module:enyo/Model~Model#includeKeys + * @see module:enyo/Model~Model#attributes + * @returns {Object} The formatted [hash]{@glossary Object} representing the underlying + * data structure of the [model]{@link module:enyo/Model~Model}. + * @public + */ + raw: function () { + var inc = this.includeKeys + , attrs = this.attributes + , keys = inc || Object.keys(attrs) + , cpy = inc? utils.only(inc, attrs): utils.clone(attrs); + keys.forEach(function (key) { + var ent = this.get(key); + if (typeof ent == 'function') cpy[key] = ent.call(this); + else if (ent && ent.raw) cpy[key] = ent.raw(); + else cpy[key] = ent; + }, this); + return cpy; + }, + + /** + * Returns the [JSON]{@glossary JSON} serializable [raw()]{@link module:enyo/Model~Model#raw} output + * of the [model]{@link module:enyo/Model~Model}. Will automatically be executed by + * [JSON.parse()]{@glossary JSON.parse}. + * + * @see module:enyo/Model~Model#raw + * @returns {Object} The return value of [raw()]{@link module:enyo/Model~Model#raw}. + * @public + */ + toJSON: function () { + + // @NOTE: Because this is supposed to return a JSON parse-able object + return this.raw(); + }, + + /** + * Restores an [attribute]{@link module:enyo/Model~Model#attributes} to its previous value. If no + * attribute is specified, all previous values will be restored. + * + * @see module:enyo/Model~Model#set + * @see module:enyo/Model~Model#previous + * @param {String} [prop] - The [attribute]{@link module:enyo/Model~Model#attributes} to + * [restore]{@link module:enyo/Model~Model#restore}. If not provided, all attributes will be + * restored to their previous values. + * @returns {this} The callee for chaining. + * @public + */ + restore: function (prop) { + + // we ensure that the property is forcibly notified (when possible) to ensure that + // bindings or other observers will know it returned to that value + if (prop) this.set(prop, this.previous[prop], {force: true}); + else this.set(this.previous); + + return this; + }, + + /** + * Commits the [model]{@link module:enyo/Model~Model} to a [source or sources]{@link module:enyo/Model~Model#source}. + * A model cannot be [committed]{@link module:enyo/Model~Model#commit} if it is in an + * [error]{@link module:enyo/States#ERROR} ({@link module:enyo/StateSupport~StateSupport#isError}) or + * [busy]{@link module:enyo/States#BUSY} ({@link module:enyo/StateSupport~StateSupport#isBusy}) + * [state]{@link module:enyo/Model~Model#status}. While executing, it will add the + * [COMMITTING]{@link module:enyo/States#COMMITTING} flag to the model's + * [status]{@link module:enyo/Model~Model#status}. Once it has completed execution, it will + * remove this flag (even if it fails). + * + * @see module:enyo/Model~Model#committed + * @see module:enyo/Model~Model#status + * @param {module:enyo/Model~Model~ActionOptions} [opts] - Optional configuration options. + * @returns {this} The callee for chaining. + * @public + */ + commit: function (opts) { + var options, + source, + it = this; + + // if the current status is not one of the error or busy states we can continue + if (!(this.status & (States.ERROR | States.BUSY))) { + + // if there were options passed in we copy them quickly so that we can hijack + // the success and error methods while preserving the originals to use later + options = opts ? utils.clone(opts, true) : {}; + + // make sure we keep track of how many sources we're requesting + source = options.source || this.source; + if (source && ((source instanceof Array) || source === true)) { + this._waiting = source.length ? source.slice() : Object.keys(Source.sources); + } + + options.success = function (source, res) { + it.committed(opts, res, source); + }; + + options.error = function (source, res) { + it.errored('COMMITTING', opts, res, source); + }; + + // set the state + this.status = this.status | States.COMMITTING; + + // now pass this on to the source to execute as it sees fit + Source.execute('commit', this, options); + } else this.errored(this.status, opts); + + return this; + }, + + /** + * Fetches the [model]{@link module:enyo/Model~Model} from a + * [source or sources]{@link module:enyo/Model~Model#source}. A model cannot be + * [fetched]{@link module:enyo/Model~Model#fetch} if it is in an + * [error]{@link module:enyo/States#ERROR} ({@link module:enyo/StateSupport~StateSupport#isError}) or + * [busy]{@link module:enyo/States#BUSY} ({@link module:enyo/StateSupport~StateSupport#isBusy}) + * [state]{@link module:enyo/Model~Model#status}. While executing, it will add the + * [FETCHING]{@link module:enyo/States#FETCHING} flag to the model's + * [status]{@link module:enyo/Model~Model#status}. Once it has completed execution, it will + * remove this flag (even if it fails). + * + * @see module:enyo/Model~Model#fetched + * @see module:enyo/Model~Model#status + * @param {module:enyo/Model~Model~ActionOptions} [opts] - Optional configuration options. + * @returns {this} The callee for chaining. + * @public + */ + fetch: function (opts) { + var options, + source, + it = this; + + // if the current status is not one of the error or busy states we can continue + if (!(this.status & (States.ERROR | States.BUSY))) { + + // if there were options passed in we copy them quickly so that we can hijack + // the success and error methods while preserving the originals to use later + options = opts ? utils.clone(opts, true) : {}; + + // make sure we keep track of how many sources we're requesting + source = options.source || this.source; + if (source && ((source instanceof Array) || source === true)) { + this._waiting = source.length ? source.slice() : Object.keys(Source.sources); + } + + options.success = function (source, res) { + it.fetched(opts, res, source); + }; + + options.error = function (source, res) { + it.errored('FETCHING', opts, res, source); + }; + + // set the state + this.status = this.status | States.FETCHING; + + // now pass this on to the source to execute as it sees fit + Source.execute('fetch', this, options); + } else this.errored(this.status, opts); + + return this; + }, + + /** + * Destroys the [model]{@link module:enyo/Model~Model}. By default, the model will only + * be [destroyed]{@glossary destroy} in the client. To execute with a + * [source or sources]{@link module:enyo/Model~Model#source}, either the + * [commit default option]{@link module:enyo/Model~Model#options} must be `true` or a + * `source` property must be explicitly provided in the `opts` parameter. + * A model cannot be destroyed (using a source) if it is in an + * [error]{@link module:enyo/States#ERROR} ({@link module:enyo/StateSupport~StateSupport#isError}) + * or [busy]{@link module:enyo/States#BUSY} ({@link module:enyo/StateSupport~StateSupport#isBusy}) + * [state]{@link module:enyo/Model~Model#status}. While executing, it will add the + * [DESTROYING]{@link module:enyo/States#DESTROYING} flag to the model's + * [status]{@link module:enyo/Model~Model#status}. Once it has completed execution, it + * will remove this flag (even if it fails). + * + * @see module:enyo/Model~Model#status + * @param {module:enyo/Model~Model~ActionOptions} [opts] - Optional configuration options. + * @returns {this} The callee for chaining. + * @public + */ + destroy: function (opts) { + var options = opts ? utils.mixin({}, [this.options, opts]) : this.options, + it = this, + idx; + + // this becomes an (potentially) async operation if we are committing this destroy + // to a source and its kind of tricky to figure out because there are several ways + // it could be flagged to do this + + if (options.commit || options.source) { + + // if the current status is not one of the error states we can continue + if (!(this.status & (States.ERROR | States.BUSY))) { + + // remap to the originals + options = opts ? utils.clone(opts, true) : {}; + + options.success = function (source, res) { + + if (it._waiting) { + idx = it._waiting.findIndex(function (ln) { + return (ln instanceof Source ? ln.name : ln) == source; + }); + if (idx > -1) it._waiting.splice(idx, 1); + if (!it._waiting.length) it._waiting = null; + } + + // continue the operation this time with commit false explicitly + if (!it._waiting) { + options.commit = options.source = null; + it.destroy(options); + } + if (opts && opts.success) opts.success(this, opts, res, source); + }; + + options.error = function (source, res) { + + if (it._waiting) { + idx = it._waiting.findIndex(function (ln) { + return (ln instanceof Source ? ln.name : ln) == source; + }); + if (idx > -1) it._waiting.splice(idx, 1); + if (!it._waiting.length) it._waiting = null; + } + + // continue the operation this time with commit false explicitly + if (!it._waiting) { + options.commit = options.source = null; + it.destroy(options); + } + + // we don't bother setting the error state if we aren't waiting because it + // will be cleared to DESTROYED and it would be pointless + else this.errored('DESTROYING', opts, res, source); + }; + + this.status = this.status | States.DESTROYING; + + Source.execute('destroy', this, options); + } else if (this.status & States.ERROR) this.errored(this.status, opts); + + // we don't allow the destroy to take place and we don't forcibly break-down + // the collection errantly so there is an opportuniy to resolve the issue + // before we lose access to the collection's content! + return this; + } + + + // we flag this early so objects that receive an event and process it + // can optionally check this to support faster cleanup in some cases + // e.g. Collection/Store don't need to remove listeners because it will + // be done in a much quicker way already + this.destroyed = true; + this.status = States.DESTROYED; + this.unsilence(true).emit('destroy'); + this.removeAllListeners(); + this.removeAllObservers(); + + // if this does not have the the batching flag (that would be set by a collection) + // then we need to do the default of removing it from the store + if (!opts || !opts.batching) this.store.remove(this); + }, + + /** + * Retrieves the value for the given property or path. If the property is a + * [computed property]{@link module:enyo/ComputedSupport}, then it will return + * that value; otherwise, it will attempt to retrieve the value from the + * [attributes hash]{@link module:enyo/Model~Model#attributes}. + * + * @param {String} path - The property to retrieve. + * @returns {*} The value for the requested property or path, or `undefined` if + * it cannot be found or does not exist. + * @public + */ + get: function (path) { + return this.isComputed(path) ? this._getComputed(path) : this.attributes[path]; + }, + + /** + * Sets the requested `path` or [hash]{@glossary Object} of properties on the + * [model]{@link module:enyo/Model~Model}. Properties are applied to the + * [attributes hash]{@link module:enyo/Model~Model#attributes} and are retrievable via + * [get()]{@link module:enyo/Model~Model#get}. If properties were updated and the `silent` + * option is not `true`, this method will emit a `change` event, as well as + * individual [notifications]{@link module:enyo/ObserverSupport~ObserverSupport.notify} for the + * properties that were modified. + * + * @fires module:enyo/Model~Model#change + * @see {@link module:enyo/ObserverSupport~ObserverSupport} + * @see {@link module:enyo/BindingSupport~BindingSupport} + * @param {(String|Object)} path - Either the property name or a [hash]{@glossary Object} + * of properties and values to set. + * @param {(*|module:enyo/Model~Options)} is If `path` is a [string]{@glossary String}, + * this should be the value to set for the given property; otherwise, it should be + * an optional hash of available [configuration options]{@link module:enyo/Model~Model~Options}. + * @param {module:enyo/Model~Options} [opts] - If `path` is a string, this should be the + * optional hash of available configuration options; otherwise, it will not be used. + * @returns {this} The callee for chaining. + * @public + */ + set: function (path, is, opts) { + if (!this.destroyed) { + + var attrs = this.attributes, + options = this.options, + changed, + incoming, + force, + silent, + key, + value, + commit, + fetched; + + // the default case for this setter is accepting an object of key->value pairs + // to apply to the model in which case the second parameter is the optional + // configuration hash + if (typeof path == 'object') { + incoming = path; + opts = opts || is; + } + + // otherwise in order to have a single path here we flub it so it will keep on + // going as expected + else { + incoming = {}; + incoming[path] = is; + } + + // to maintain backward compatibility with the old setters that allowed the third + // parameter to be a boolean to indicate whether or not to force notification of + // change even if there was any + if (opts === true) { + force = true; + opts = {}; + } + + opts = opts ? utils.mixin({}, [options, opts]) : options; + silent = opts.silent; + force = force || opts.force; + commit = opts.commit; + fetched = opts.fetched; + + for (key in incoming) { + value = incoming[key]; + + if (value !== attrs[key] || force) { + // to ensure we have an object to work with + // note that we check inside this loop so we don't have to examine keys + // later only the local variable changed + changed = this.changed || (this.changed = {}); + //store the previous attr value + this.previous[key] = attrs[key]; + //set new value + changed[key] = attrs[key] = value; + } + } + + if (changed) { + + // we add dirty as a value of the status but clear the CLEAN bit if it + // was set - this would allow it to be in the ERROR state and NEW and DIRTY + if (!fetched) this.status = (this.status | States.DIRTY) & ~States.CLEAN; + + if (!silent) this.emit('change', changed, this); + + if (commit && !fetched) this.commit(opts); + + // reset value so subsequent changes won't be added to this change-set + this.changed = null; + } + } + + return this; + }, + + /** + * A bit of hackery to facade the normal [getter]{@link module:enyo/ComputedSupport~ComputedSupport#get}. Note that + * we pass an arbitrary super-method that automatically returns `undefined`, which is + * consistent with this use case and its intended purpose. + * + * @private + */ + _getComputed: ComputedSupport.get.fn(function () { return undefined; }), + + /** + * Initializes the [model]{@link module:enyo/Model~Model}. Unlike some methods, the parameters are not + * interchangeable. If you are not using a particular (optional) parameter, pass in `null`. + * + * @param {Object} [attrs] - Optionally initialize the [model]{@link module:enyo/Model~Model} with some + * [attributes]{@link module:enyo/Model~Model#attributes}. + * @param {Object} [props] - Properties to apply directly to the [model]{@link module:enyo/Model~Model} and + * not the [attributes hash]{@link module:enyo/Model~Model#attributes}. If these properties contain an + * `options` property (a [hash]{@glossary Object}) it will be merged with existing + * [options]{@link module:enyo/Model~Model#options}. + * @param {module:enyo/Model~Model~Options} [opts] - This is a one-time [options hash]{@link module:enyo/Model~Model~Options} that + * is only used during initialization and not applied as defaults. + * @public + */ + constructor: function (attrs, props, opts) { + + // in cases where there is an options hash provided in the _props_ param + // we need to integrate it manually... + if (props && props.options) { + this.options = utils.mixin({}, [this.options, props.options]); + delete props.options; + } + + // the _opts_ parameter is a one-hit options hash it does not leave + // behind its values as default options... + opts = opts? utils.mixin({}, [this.options, opts]): this.options; + + // go ahead and mix all of the properties in + props && utils.mixin(this, props); + + var noAdd = opts.noAdd + , commit = opts.commit + , parse = opts.parse + , fetch = this.options.fetch + , defaults; + + // defaults = this.defaults && (typeof this.defaults == 'function'? this.defaults(attrs, opts): this.defaults); + defaults = this.defaults && typeof this.defaults == 'function'? this.defaults(attrs, opts): null; + + // ensure we have a unique identifier that could potentially + // be used in remote systems + this.euid = this.euid || utils.uid('m'); + + // if necessary we need to parse the incoming attributes + attrs = attrs? parse? this.parse(attrs): attrs: null; + + // ensure we have the updated attributes + this.attributes = this.attributes? defaults? utils.mixin({}, [defaults, this.attributes]): utils.clone(this.attributes, true): defaults? utils.clone(defaults, true): {}; + attrs && utils.mixin(this.attributes, attrs); + this.previous = utils.clone(this.attributes); + + // now we need to ensure we have a store and register with it + this.store = this.store || Store; + + // @TODO: The idea here is that when batch instancing records a collection + // should be intelligent enough to avoid doing each individually or in some + // cases it may be useful to have a record that is never added to a store? + if (!noAdd) this.store.add(this, opts); + + commit && this.commit(); + fetch && this.fetch(); + }, + + /** + * Overloaded. We funnel arbitrary notification updates through here, as this + * is faster than using the built-in notification updates for batch operations. + * + * @private + */ + emit: kind.inherit(function (sup) { + return function (e, props) { + if (e == 'change' && props && this.isObserving()) { + for (var key in props) this.notify(key, this.previous[key], props[key]); + } + return sup.apply(this, arguments); + }; + }), + + /** + * Overloaded to alias the (also overloaded) [emit()]{@link module:enyo/Model~Model#emit} method. + * + * @private + */ + triggerEvent: function () { + return this.emit.apply(this, arguments); + }, + + /** + * When a [fetch]{@link module:enyo/Model~Model#fetch} has completed successfully, it is returned + * to this method. This method handles special and important behavior; it should not be + * called directly and, when overloading, care must be taken to ensure that you call + * the super-method. This correctly sets the [status]{@link module:enyo/Model~Model#status} and, in + * cases where multiple [sources]{@link module:enyo/Model~Model#source} were used, it waits until + * all have responded before clearing the [FETCHING]{@link module:enyo/States#FETCHING} flag. + * If a [success]{@link module:enyo/Model~Model~Success} callback was provided, it will be called + * once for each source. + * + * @param {module:enyo/Model~Model~ActionOptions} opts - The original options passed to + * [fetch()]{@link module:enyo/Model~Model#fetch}, merged with the defaults. + * @param {*} [res] - The result provided from the given [source]{@link module:enyo/Model~Model#source}, + * if any. This will vary depending on the source. + * @param {String} source - The name of the source that has completed successfully. + * @public + */ + fetched: function (opts, res, source) { + var idx, + options = this.options; + + if (this._waiting) { + idx = this._waiting.findIndex(function (ln) { + return (ln instanceof Source ? ln.name : ln) == source; + }); + if (idx > -1) this._waiting.splice(idx, 1); + if (!this._waiting.length) this._waiting = null; + } + + // normalize options so we have values and ensure it knows it was just fetched + opts = opts ? utils.mixin({}, [options, opts]) : options; + opts.fetched = true; + + // for a special case purge to only use the result sub-tree of the fetched data for + // the model attributes + if (opts.parse) res = this.parse(res); + + // note this will not add the DIRTY state because it was fetched but also note that it + // will not clear the DIRTY flag if it was already DIRTY + if (res) this.set(res, opts); + + // clear the FETCHING and NEW state (if it was NEW) we do not set it as dirty as this + // action alone doesn't warrant a dirty flag that would need to be set in the set method + if (!this._waiting) this.status = this.status & ~(States.FETCHING | States.NEW); + + // now look for an additional success callback + if (opts.success) opts.success(this, opts, res, source); + }, + + /** + * When a [commit]{@link module:enyo/Model~Model#commit} has completed successfully, it is returned + * to this method. This method handles special and important behavior; it should not be + * called directly and, when overloading, care must be taken to ensure that you call the + * super-method. This correctly sets the [status]{@link module:enyo/Model~Model#status} and, in cases + * where multiple [sources]{@link module:enyo/Model~Model#source} were used, it waits until all have + * responded before clearing the [COMMITTING]{@link module:enyo/States#COMMITTING} flag. If a + * [success]{@link module:enyo/Model~Model~Success} callback was provided, it will be called once for + * each source. + * + * @param {module:enyo/Model~Model~ActionOptions} opts - The original options passed to + * [commit()]{@link module:enyo/Model~Model#commit}, merged with the defaults. + * @param {*} [res] - The result provided from the given [source]{@link module:enyo/Model~Model#source}, + * if any. This will vary depending on the source. + * @param {String} source - The name of the source that has completed successfully. + * @public + */ + committed: function (opts, res, source) { + var idx; + + if (this._waiting) { + idx = this._waiting.findIndex(function (ln) { + return (ln instanceof Source ? ln.name : ln) == source; + }); + if (idx > -1) this._waiting.splice(idx, 1); + if (!this._waiting.length) this._waiting = null; + } + + if (!this._waiting) { + // we need to clear the COMMITTING bit and DIRTY bit as well as ensure that the + // 'previous' hash is whatever the current attributes are + this.previous = utils.clone(this.attributes); + this.status = (this.status | States.CLEAN) & ~(States.COMMITTING | States.DIRTY); + } + + if (opts && opts.success) opts.success(this, opts, res, source); + }, + + /** + * When an action ([fetch()]{@link module:enyo/Model~Model#fetch}, [commit()]{@link module:enyo/Model~Model#commit}, + * or [destroy()]{@link module:enyo/Model~Model#destroy}) has failed, it will be passed to this method. + * This method handles special and important behavior; it should not be called directly + * and, when overloading, care must be taken to ensure that you call the super-method. + * This correctly sets the [status]{@link module:enyo/Model~Model#status} to the known + * [error state]{@link module:enyo/States#ERROR}, or to the + * [unknown error state]{@link module:enyo/States#ERROR_UNKNOWN} if it the error state could not + * be determined. If an [error callback]{@link module:enyo/Model~Model~Error} was provided, this method + * will execute it. + * + * @see {@link module:enyo/StateSupport~StateSupport#clearError} + * @param {String} action - The action (one of `'FETCHING'`, `'COMMITTING'`, or + * `'DESTROYING'`) that failed and is now in an [error state]{@link module:enyo/States#ERROR}. + * @param {module:enyo/Model~Model~ActionOptions} opts - The original options passed to the `action` + * method, merged with the defaults. + * @param {*} [res] - The result provided from the given [source]{@link module:enyo/Model~Model#source}, + * if any. This will vary depending on the source. + * @param {String} source - The name of the source that has returned an error. + * @public + */ + errored: function (action, opts, res, source) { + var stat, + idx; + + // if the error action is a status number then we don't need to update it otherwise + // we set it to the known state value + if (typeof action == 'string') { + + // all built-in errors will pass this as their values are > 0 but we go ahead and + // ensure that no developer used the 0x00 for an error code + stat = States['ERROR_' + action]; + } else stat = action; + + if (isNaN(stat) || (stat & ~States.ERROR)) stat = States.ERROR_UNKNOWN; + + // correctly set the current status and ensure we clear any busy flags + this.status = (this.status | stat) & ~States.BUSY; + + if (this._waiting) { + idx = this._waiting.findIndex(function (ln) { + return (ln instanceof Source ? ln.name : ln) == source; + }); + if (idx > -1) this._waiting.splice(idx, 1); + if (!this._waiting.length) this._waiting = null; + } + + // we need to check to see if there is an options handler for this error + if (opts && opts.error) opts.error(this, action, opts, res, source); + } + +}); + +/** +* @name module:enyo/Model~Model.concat +* @static +* @private +*/ +Model.concat = function (ctor, props) { + var proto = ctor.prototype || ctor; + + if (props.options) { + proto.options = utils.mixin({}, [proto.options, props.options]); + delete props.options; + } +}; + +/** +* @private +*/ +kind.features.push(function (ctor) { + if (ctor.prototype instanceof Model) { + !Store.models[ctor.prototype.kindName] && (Store.models[ctor.prototype.kindName] = new ModelList()); + } +}); + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./ObserverSupport':'enyo/ObserverSupport','./ComputedSupport':'enyo/ComputedSupport','./BindingSupport':'enyo/BindingSupport','./EventEmitter':'enyo/EventEmitter','./StateSupport':'enyo/StateSupport','./ModelList':'enyo/ModelList','./Source':'enyo/Source','./States':'enyo/States','./Store':'enyo/Store'}],'enyo/Component':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Component~Component} kind. +* @module enyo/Component +*/ + +var + kind = require('./kind'), + utils = require('./utils'), + logger = require('./logger'); + +var + CoreObject = require('./CoreObject'), + ApplicationSupport = require('./ApplicationSupport'), + ComponentBindingSupport = require('./ComponentBindingSupport'), + Jobs = require('./jobs'); + +var + kindPrefix = {}, + unnamedCounter = 0; + +/** +* @callback module:enyo/Component~Component~EventHandler +* @param {module:enyo/Component~Component} sender - The [component]{@link module:enyo/Component~Component} that most recently +* propagated the {@glossary event}. +* @param {Object} event - An [object]{@glossary Object} containing +* event information. +* @returns {Boolean} A value indicating whether the event has been +* handled or not. If `true`, then bubbling is stopped. +*/ + +/** +* A [hash]{@glossary Object} of references to all the [components]{@link module:enyo/Component~Component} +* owned by this component. This property is updated whenever a new +* component is added; the new component may be accessed via its +* [name]{@link module:enyo/Component~Component#name} property. We may also observe changes on +* properties of components referenced by the `$` property. +* +* Component access via the `$` hash: +* ```javascript +* var Component = require('enyo/Component'); +* var c = new Component({ +* name: 'me', +* components: [ +* {kind: 'Component', name: 'other'} +* ] +* }); +* +* // We can now access 'other' on the $ hash of 'c', via c.$.other +* ``` +* +* Observing changes on a component referenced by the `$` property: +* ```javascript +* var c = new Component({ +* name: 'me', +* components: [ +* {kind: 'Component', name: 'other'} +* ] +* }); +* +* c.addObserver('$.other.active', function() { +* // do something to respond to the "active" property of "other" changing +* }) +* +* c.$.other.set('active', true); // this will trigger the observer to run its callback +* ``` +* +* @name $ +* @type {Object} +* @default null +* @memberof module:enyo/Component~Component.prototype +* @readonly +* @public +*/ + +/** +* If `true`, this [component's]{@link module:enyo/Component~Component} [owner]{@link module:enyo/Component~Component#owner} will +* have a direct name reference to the owned component. +* +* @example +* var Component = require('enyo/Component'); +* var c = new Component({ +* name: 'me', +* components: [ +* {kind: 'Component', name: 'other', publish: true} +* ] +* }); +* +* // We can now access 'other' directly, via c.other +* +* @name publish +* @type {Boolean} +* @default undefined +* @memberOf module:enyo/Component~Component.prototype +* @public +*/ + +/** +* If `true`, the [layout]{@glossary layout} strategy will adjust the size of this +* [component]{@link module:enyo/Component~Component} to occupy the remaining available space. +* +* @name fit +* @type {Boolean} +* @default undefined +* @memberOf module:enyo/Component~Component.prototype +* @public +*/ + +/** +* {@link module:enyo/Component~Component} is the fundamental building block for Enyo applications. +* Components are designed to fit together, allowing complex behaviors to +* be fashioned from smaller bits of functionality. +* +* Component [constructors]{@glossary constructor} take a single +* argument (sometimes called a [component configuration]{@glossary configurationBlock}), +* a JavaScript [object]{@glossary Object} that defines various properties to be initialized on the +* component. For example: +* +* ```javascript +* // create a new component, initialize its name property to 'me' +* var Component = require('enyo/Component'); +* var c = new Component({ +* name: 'me' +* }); +* ``` +* +* When a component is instantiated, items configured in its +* `components` property are instantiated, too: +* +* ```javascript +* // create a new component, which itself has a component +* var c = new Component({ +* name: 'me', +* components: [ +* {kind: Component, name: 'other'} +* ] +* }); +* ``` +* +* In this case, when `me` is created, `other` is also created, and we say that `me` owns `other`. +* In other words, the [owner]{@link module:enyo/Component~Component#owner} property of `other` equals `me`. +* Notice that you can specify the [kind]{@glossary kind} of `other` explicitly in its +* configuration block, to tell `me` what constructor to use to create `other`. +* +* To move a component, use the `setOwner()` method to change the +* component's owner. If you want a component to be unowned, use `setOwner(null)`. +* +* If you make changes to `Component`, be sure to add or update the appropriate +* {@linkplain https://github.com/enyojs/enyo/tree/master/tools/test/core/tests unit tests}. +* +* For more information, see the documentation on +* [Components]{@linkplain $dev-guide/key-concepts/components.html} in the +* Enyo Developer Guide. +* +* @class Component +* @extends module:enyo/CoreObject~Object +* @mixes module:enyo/ApplicationSupport~ApplicationSupport +* @mixes module:enyo/ComponentBindingSupport~ComponentBindingSupport +* @public +*/ +var Component = module.exports = kind( + /** @lends module:enyo/Component~Component.prototype */ { + + name: 'enyo.Component', + + /** + * @private + */ + kind: CoreObject, + + /** + * @private + */ + + + /** + * @private + */ + cachedBubble: true, + + /** + * @private + */ + cachePoint: false, + + /** + * @private + */ + published: + /** @lends module:enyo/Component~Component.prototype */ { + + /** + * A unique name for the [component]{@link module:enyo/Component~Component} within its + * [owner]{@link module:enyo/Component~Component#owner}. This is used to set the access name in the + * owner's [$ hash]{@link module:enyo/Component~Component#$}. If not + * specified, a default name will be provided based on the name of the + * [object's]{@link module:enyo/CoreObject~Object} [kind]{@glossary kind}, with a numeric + * suffix appended if more than one instance exists in the owner. + * + * @type {String} + * @default '' + * @public + */ + name: '', + + /** + * A unique id for the [component]{@link module:enyo/Component~Component}, usually automatically generated + * based on its position within the component hierarchy, although + * it may also be directly specified. {@link module:enyo/Control~Control} uses this `id` value for the + * DOM [id]{@link module:enyo/Control~Control#id} attribute. + * + * @type {String} + * @default '' + * @public + */ + id: '', + + /** + * The [component]{@link module:enyo/Component~Component} that owns this component. + * It is usually defined implicitly at creation time based on the + * [createComponent()]{@link module:enyo/Component~Component#createComponent} call or + * the `components` hash. + * + * @type {module:enyo/Component~Component} + * @default null + * @public + */ + owner: null, + + /** + * This can be a [hash]{@glossary Object} of features to apply to + * [chrome]{@glossary chrome} [components]{@link module:enyo/Component~Component} of the base + * [kind]{@glossary kind}. They are matched by [name]{@link module:enyo/Component~Component#name} + * (if the component you wish to modify does not have a name, this will not work). + * You can modify any properties of the component except for methods. Setting a + * value for `componentOverrides` at runtime will have no effect. + * + * @type {Object} + * @default null + * @public + */ + componentOverrides: null + }, + + /** + * @private + */ + handlers: {}, + + /** + * @private + */ + mixins: [ApplicationSupport, ComponentBindingSupport], + + /** + * @private + */ + toString: function () { + return this.id + ' [' + this.kindName + ']'; + }, + + /** + * @method + * @private + */ + constructor: kind.inherit(function (sup) { + return function (props) { + // initialize instance objects + this._componentNameMap = {}; + this.$ = {}; + this.cachedBubbleTarget = {}; + sup.apply(this, arguments); + }; + }), + + /** + * @method + * @private + */ + constructed: kind.inherit(function (sup) { + return function (props) { + // perform initialization + this.create(props); + sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + create: function () { + // stop and queue all of the notifications happening synchronously to allow + // responders to only do single passes on work traversing the tree + this.stopNotifications(); + this.ownerChanged(); + this.initComponents(); + // release the kraken! + this.startNotifications(); + }, + + /** + * @private + */ + initComponents: function () { + // The _components_ property in kind declarations is renamed to + // _kindComponents_ by the Component subclass mechanism. This makes it + // easy for the developer to distinguish kindComponents from the components + // in _this.components_, without having to worry about the actual difference. + // + // Specifically, the difference is that kindComponents are constructed as + // owned by this control (whereas components in _this.components_ are not). + // In addition, kindComponents are marked with the _isChrome: true_ flag. + this.createChrome(this.kindComponents); + this.createClientComponents(this.components); + }, + + /** + * @private + */ + createChrome: function (comps) { + this.createComponents(comps, {isChrome: true}); + }, + + /** + * @private + */ + createClientComponents: function (comps) { + this.createComponents(comps, {owner: this.getInstanceOwner()}); + }, + + /** + * @private + */ + getInstanceOwner: function () { + return (!this.owner || this.owner.notInstanceOwner) ? this : this.owner; + }, + + /** + * Removes this [component]{@link module:enyo/Component~Component} from its + * [owner]{@link module:enyo/Component~Component#owner} (setting `owner` to `null`) + * and does any necessary cleanup. The component is flagged with + * `destroyed: true`. Usually, the component will be suitable for garbage + * collection after being destroyed, unless user code keeps a reference + * to it. + * + * @returns {this} The callee for chaining. + * @method + * @public + */ + destroy: kind.inherit(function (sup) { + return function () { + this.destroyComponents(); + this.setOwner(null); + sup.apply(this, arguments); + this.stopAllJobs(); + return this; + }; + }), + + /** + * Destroys all owned [components]{@link module:enyo/Component~Component}. + * + * @returns {this} The callee for chaining. + * @public + */ + destroyComponents: function () { + var comps = this.getComponents(), + comp, + i; + + for (i = 0; i < comps.length; ++i) { + comp = comps[i]; + // @todo: previous comment said list might be stale and ownership may have caused + // components to be destroyed as a result of some inner-container...look into this + // because that seems incorrect or avoidable + if (!comp.destroyed) comp.destroy(); + } + + return this; + }, + + /** + * @private + */ + makeId: function() { + var delim = '_', pre = this.owner && this.owner.getId(), + baseName = this.name || ('@@' + (++unnamedCounter)); + return (pre ? pre + delim : '') + baseName; + }, + + /** + * @private + */ + ownerChanged: function (was) { + if (was && was.removeComponent) was.removeComponent(this); + if (this.owner && this.owner.addComponent) this.owner.addComponent(this); + if (!this.id) this.id = this.makeId(); + }, + + /** + * @private + */ + nameComponent: function (comp) { + var pre = prefixFromKindName(comp.kindName), + last = this._componentNameMap[pre] || 0, + nom; + + do { + nom = pre + (++last > 1 ? String(last) : ''); + } while (this.$[nom]); + + this._componentNameMap[pre] = Number(last); + /*jshint -W093 */ + return (comp.name = nom); + }, + + /** + * Adds a [component]{@link module:enyo/Component~Component} to the list of components + * owned by the current component (i.e., [this.$]{@link module:enyo/Component~Component#$}). + * + * @param {module:enyo/Component~Component} comp - The [component]{@link module:enyo/Component~Component} to add. + * @returns {this} The callee for chaining. + * @public + */ + addComponent: function (comp) { + var nom = comp.get('name'); + + // if there is no name we have to come up with a generic name + if (!nom) nom = this.nameComponent(comp); + + // if there already was a component by that name we issue a warning + // @todo: if we're going to name rules being violated we need to normalize this approach + // and ensure we have one for every warning/error we throw + if (this.$[nom]) this.warn( + 'Duplicate component name ' + nom + ' in owner ' + this.id + ' violates ' + + 'unique-name-under-owner rule, replacing existing component in the hash and ' + + 'continuing, but this is an error condition and should be fixed.' + ); + + this.$[nom] = comp; + this.notify('$.' + nom, null, comp); + + // if the component has the `publish` true property then we also create a reference to + // it directly on the owner (this) + if (comp.publish) { + this[nom] = comp; + + // and to ensure that bindings are aware we have to notify them as well + this.notify(nom, null, comp); + } + + return this; + }, + + /** + * Removes the passed-in [component]{@link module:enyo/Component~Component} from those known + * to be owned by this component. The component will be removed from the + * [$ hash]{@link module:enyo/Component~Component#$}, and from the [owner]{@link module:enyo/Component~Component#owner} + * directly if [publish]{@link module:enyo/Component~Component#publish} is set to `true`. + * + * @param {module:enyo/Component~Component} comp - The component to remove. + * @returns {this} The callee for chaining. + * @public + */ + removeComponent: function (comp) { + var nom = comp.get('name'); + + // remove it from the hash if it existed + delete this.$[nom]; + + // if it was published remove it from the component proper + if (comp.publish) delete this[nom]; + + return this; + }, + + /** + * Returns an [array]{@glossary Array} of owned [components]{@link module:enyo/Component~Component}; in + * other words, converts the [$ hash]{@link module:enyo/Component~Component#$} into an array + * and returns the array. + * + * @returns {module:enyo/Component~Component[]} The [components]{@link module:enyo/Component~Component} found in the + * [$ hash]{@link module:enyo/Component~Component#$}. + * @public + */ + getComponents: function () { + return utils.values(this.$); + }, + + /** + * @private + */ + adjustComponentProps: function (props) { + if (this.defaultProps) utils.mixin(props, this.defaultProps, {ignore: true}); + props.kind = props.kind || props.isa || this.defaultKind; + props.owner = props.owner || this; + }, + + /** + * @private + */ + _createComponent: function (props, ext) { + var def = ext ? utils.mixin({}, [ext, props]) : utils.clone(props); + + // always adjust the properties according to the needs of the kind and parent kinds + this.adjustComponentProps(def); + + // pass along for the final stage + return Component.create(def); + }, + + /** + * Creates and returns a [component]{@link module:enyo/Component~Component} as defined by the combination of + * a base and an additional property [hash]{@glossary Object}. The properties provided + * in the standard property hash override those provided in the + * additional property hash. + * + * The created component passes through initialization machinery + * provided by the creating component, which may supply special + * handling. Unless the [owner]{@link module:enyo/Component~Component#owner} is explicitly specified, the new + * component will be owned by the instance on which this method is called. + * + * @example + * // Create a new component named 'dynamic', owned by 'this' + * // (will be available as this.$.dynamic). + * this.createComponent({name: 'dynamic'}); + * + * @example + * // Create a new component named 'another' owned by 'other' + * // (will be available as other.$.another). + * this.createComponent({name: 'another'}, {owner: other}); + * + * @param {Object} props - The declarative [kind]{@glossary kind} definition. + * @param {Object} ext - Additional properties to be applied (defaults). + * @returns {module:enyo/Component~Component} The instance created with the given parameters. + * @public + */ + createComponent: function (props, ext) { + // createComponent and createComponents both delegate to the protected method + // (_createComponent), allowing overrides to customize createComponent and + // createComponents separately. + return this._createComponent(props, ext); + }, + + /** + * Creates [components]{@link module:enyo/Component~Component} as defined by the [arrays]{@glossary Array} + * of base and additional property [hashes]{@glossary Object}. The standard and + * additional property hashes are combined as described in + * [createComponent()]{@link module:enyo/Component~Component#createComponent}. + * + * @example + * // ask foo to create components 'bar' and 'zot', but set the owner of + * // both components to 'this'. + * this.$.foo.createComponents([ + * {name: 'bar'}, + * {name: 'zot'} + * ], {owner: this}); + * + * @param {Object[]} props The array of {@link module:enyo/Component~Component} definitions to be created. + * @param {Object} ext - Additional properties to be supplied as defaults for each. + * @returns {module:enyo/Component~Component[]} The array of [components]{@link module:enyo/Component~Component} that were + * created. + * @public + */ + createComponents: function (props, ext) { + var comps = [], + comp, + i; + + if (props) { + for (i = 0; i < props.length; ++i) { + comp = props[i]; + comps.push(this._createComponent(comp, ext)); + } + } + + return comps; + }, + + /** + * @private + */ + getBubbleTarget: function (nom, event) { + if (event.delegate) return this.owner; + else { + return ( + this.bubbleTarget + || (this.cachedBubble && this.cachedBubbleTarget[nom]) + || this.owner + ); + } + }, + + /** + * Bubbles an {@glossary event} up an [object]{@glossary Object} chain, + * starting with `this`. + * + * A handler for an event may be specified. See {@link module:enyo/Component~Component~EventHandler} + * for complete details. + * + * @param {String} nom - The name of the {@glossary event} to bubble. + * @param {Object} [event] - The event [object]{@glossary Object} to be passed along + * while bubbling. + * @param {module:enyo/Component~Component} [sender=this] - The {@link module:enyo/Component~Component} responsible for + * bubbling the event. + * @returns {Boolean} `false` if unhandled or uninterrupted; otherwise, `true`. + * @public + */ + bubble: function (nom, event, sender) { + if (!this._silenced) { + event = event || {}; + event.lastHandledComponent = null; + event.bubbling = true; + // deliberately done this way + if (event.originator == null) event.originator = sender || this; + return this.dispatchBubble(nom, event, sender || this); + } + return false; + }, + + /** + * Bubbles an {@glossary event} up an [object]{@glossary Object} chain, + * starting **above** `this`. + * + * A handler for an event may be specified. See {@link module:enyo/Component~Component~EventHandler} + * for complete details. + * + * @param {String} nom - The name of the {@glossary event}. + * @param {Object} [event] - The event properties to pass along while bubbling. + * @returns {Boolean} `false` if unhandled or uninterrupted; otherwise, `true`. + * @public + */ + bubbleUp: function (nom, event) { + var next; + + if (!this._silenced) { + event = event || {}; + event.bubbling = true; + next = this.getBubbleTarget(nom, event); + if (next) { + // use delegate as sender if it exists to preserve illusion + // that event is dispatched directly from that, but we still + // have to bubble to get decorations + return next.dispatchBubble(nom, event, event.delegate || this); + } + } + return false; + }, + + /** + * Sends an {@glossary event} to a named [delegate]{@glossary delegate}. + * This [object]{@glossary Object} may dispatch an event to + * itself via a [handler]{@link module:enyo/Component~Component~EventHandler}, or to its + * [owner]{@link module:enyo/Component~Component#owner} via an event property, e.g.: + * + * handlers { + * // 'tap' events dispatched to this.tapHandler + * ontap: 'tapHandler' + * } + * + * // 'tap' events dispatched to 'tapHandler' delegate in this.owner + * ontap: 'tapHandler' + * + * @private + */ + dispatchEvent: function (nom, event, sender) { + var delegate, + ret; + + if (!this._silenced) { + // if the event has a delegate associated with it we grab that + // for reference + // NOTE: This is unfortunate but we can't use a pooled object here because + // we don't know where to release it + delegate = (event || (event = {})).delegate; + + // bottleneck event decoration w/ optimization to avoid call to empty function + if (this.decorateEvent !== Component.prototype.decorateEvent) { + this.decorateEvent(nom, event, sender); + } + + // first, handle any delegated events intended for this object + if (delegate && delegate.owner === this) { + // the most likely case is that we have a method to handle this + if (this[nom] && 'function' === typeof this[nom]) { + return this.dispatch(nom, event, sender); + } + // but if we don't, just stop the event from going further + return false; + } + + // for non-delgated events, try the handlers block if possible + if (!delegate) { + var bHandler = this.handlers && this.handlers[nom]; + var bDelegatedFunction = this[nom] && utils.isString(this[nom]); + var cachePoint = this.cachePoint || bHandler || bDelegatedFunction || this.id === "master" ; + + if (event.bubbling) { + if (event.lastHandledComponent && cachePoint) { + event.lastHandledComponent.cachedBubbleTarget[nom] = this; + event.lastHandledComponent = null; + } + if (!event.lastHandledComponent && this.id !== "master") { + event.lastHandledComponent = this; + } + } + if (bHandler && this.dispatch(bHandler, event, sender)) { + return true; + } + if (bDelegatedFunction) { + // we dispatch it up as a special delegate event with the + // component that had the delegation string property stored in + // the 'delegate' property + event.delegate = this; + ret = this.bubbleUp(this[nom], event, sender); + delete event.delegate; + return ret; + } + } + } + return false; + }, + + /** + * Internal - try dispatching {@glossary event} to self; if that fails, + * [bubble it up]{@link module:enyo/Component~Component#bubbleUp} the tree. + * + * @private + */ + dispatchBubble: function (nom, event, sender) { + if (!this._silenced) { + // Try to dispatch from here, stop bubbling on truthy return value + if (this.dispatchEvent(nom, event, sender)) { + return true; + } + // Bubble to next target + return this.bubbleUp(nom, event, sender); + } + return false; + }, + + /** + * @private + */ + decorateEvent: function (nom, event, sender) { + // an event may float by us as part of a dispatchEvent chain + // both call this method so intermediaries can decorate inEvent + }, + + /** + * @private + */ + stopAllJobs: function () { + var job; + + if (this.__jobs) for (job in this.__jobs) this.stopJob(job); + }, + + /** + * Dispatches the {@glossary event} to named [delegate]{@glossary delegate} `nom`, + * if it exists. [Subkinds]{@glossary subkind} may re-route dispatches. Note that + * both 'handlers' events and events delegated from owned controls arrive here. + * If you need to handle these types of events differently, you may also need to + * override [dispatchEvent()]{@link module:enyo/Component~Component#dispatchEvent}. + * + * @param {String} nom - The method name to dispatch the {@glossary event}. + * @param {Object} [event] - The event [object]{@glossary Object} to pass along. + * @param {module:enyo/Component~Component} [sender=this] - The originator of the event. + * @public + */ + dispatch: function (nom, event, sender) { + var fn; + + if (!this._silenced) { + fn = nom && this[nom]; + if (fn && typeof fn == 'function') { + // @todo: deprecate sender + return fn.call(this, sender || this, event); + } + } + return false; + }, + + /** + * Triggers the [handler]{@link module:enyo/Component~Component~EventHandler} for a given + * {@glossary event} type. + * + * @example + * myControl.triggerHandler('ontap'); + * + * @param {String} nom - The name of the {@glossary event} to trigger. + * @param {Object} [event] - The event object to pass along. + * @param {module:enyo/Component~Component} [sender=this] - The originator of the event. + * @returns {Boolean} `false` if unhandled or uninterrupted, `true` otherwise. + * @public + */ + triggerHandler: function () { + return this.dispatchEvent.apply(this, arguments); + }, + + /** + * Sends a message to myself and all of my [components]{@link module:enyo/Component~Component}. + * You can stop a waterfall into components owned by a receiving object + * by returning a truthy value from the {@glossary event} + * [handler]{@link module:enyo/Component~Component~EventHandler}. + * + * @param {String} nom - The name of the {@glossary event} to waterfall. + * @param {Object} [event] - The event [object]{@glossary Object} to pass along. + * @param {module:enyo/Component~Component} [sender=this] - The originator of the event. + * @returns {this} The callee for chaining. + * @public + */ + waterfall: function(nom, event, sender) { + if (!this._silenced) { + event = event || {}; + event.bubbling = false; + + // give the locals an opportunity to interrupt the event + if (this.dispatchEvent(nom, event, sender)) return true; + + // otherwise carry on + this.waterfallDown(nom, event, sender || this); + } + + return this; + }, + + /** + * Sends a message to all of my [components]{@link module:enyo/Component~Component}, but not myself. You can + * stop a [waterfall]{@link module:enyo/Component~Component#waterfall} into [components]{@link module:enyo/Component~Component} + * owned by a receiving [object]{@glossary Object} by returning a truthy value from the + * {@glossary event} [handler]{@link module:enyo/Component~Component~EventHandler}. + * + * @param {String} nom - The name of the {@glossary event}. + * @param {Object} [event] - The event [object]{@glossary Object} to pass along. + * @param {module:enyo/Component~Component} [sender=this] - The event originator. + * @returns {this} The callee for chaining. + * @public + */ + waterfallDown: function(nom, event, sender) { + var comp; + event = event || {}; + event.bubbling = false; + + if (!this._silenced) { + for (comp in this.$) this.$[comp].waterfall(nom, event, sender || this); + } + + return this; + }, + + /** + * @private + */ + _silenced: false, + + /** + * @private + */ + _silenceCount: 0, + + /** + * Sets a flag that disables {@glossary event} propagation for this + * [component]{@link module:enyo/Component~Component}. Also increments an internal counter that tracks + * the number of times the [unsilence()]{@link module:enyo/Component~Component#unsilence} method must + * be called before event propagation will continue. + * + * @returns {this} The callee for chaining. + * @public + */ + silence: function () { + this._silenced = true; + this._silenceCount += 1; + + return this; + }, + + /** + * Determines if the [object]{@glossary Object} is currently + * [silenced]{@link module:enyo/Component~Component#_silenced}, which will prevent propagation of + * [events]{@glossary event} (of any kind). + * + * @returns {Boolean} `true` if silenced; otherwise, `false`. + * @public + */ + isSilenced: function () { + return this._silenced; + }, + + /** + * Allows {@glossary event} propagation for this [component]{@link module:enyo/Component~Component} + * if the internal silence counter is `0`; otherwise, decrements the counter by one. + * For event propagation to resume, this method must be called one time each call to + * [silence()]{@link module:enyo/Component~Component#silence}. + * + * @returns {Boolean} `true` if the {@link module:enyo/Component~Component} is now unsilenced completely; + * `false` if it remains silenced. + * @public + */ + unsilence: function () { + if (0 !== this._silenceCount) --this._silenceCount; + if (0 === this._silenceCount) this._silenced = false; + return !this._silenced; + }, + + /** + * Creates a new [job]{@link module:enyo/job} tied to this instance of the + * [component]{@link module:enyo/Component~Component}. If the component is + * [destroyed]{@link module:enyo/Component~Component#destroy}, any jobs associated with it + * will be stopped. + * + * If you start a job with the same name as a pending job, + * the original job will be stopped; this can be useful for resetting + * timeouts. + * + * You may supply a priority level (1-10) at which the job should be + * executed. The default level is `5`. Setting the priority lower than `5` (or setting it to + * the string `"low"`) will defer the job if an animation is in progress, + * which can help to avoid stuttering. + * + * @param {String} nom - The name of the [job]{@link module:enyo/job} to start. + * @param {(Function|String)} job - Either the name of a method or a + * [function]{@glossary Function} to execute as the requested job. + * @param {Number} wait - The number of milliseconds to wait before starting + * the job. + * @param {Number} [priority=5] The priority value to be associated with this + * job. + * @returns {this} The callee for chaining. + * @public + */ + startJob: function (nom, job, wait, priority) { + var jobs = (this.__jobs = this.__jobs || {}); + priority = priority || 5; + // allow strings as job names, they map to local method names + if (typeof job == 'string') job = this[job]; + // stop any existing jobs with same name + this.stopJob(nom); + jobs[nom] = setTimeout(this.bindSafely(function() { + Jobs.add(this.bindSafely(job), priority, nom); + }), wait); + + return this; + }, + + /** + * Stops a [component]{@link module:enyo/Component~Component}-specific [job]{@link module:enyo/job} before it has + * been activated. + * + * @param {String} nom - The name of the [job]{@link module:enyo/job} to be stopped. + * @returns {this} The callee for chaining. + * @public + */ + stopJob: function (nom) { + var jobs = (this.__jobs = this.__jobs || {}); + if (jobs[nom]) { + clearTimeout(jobs[nom]); + delete jobs[nom]; + } + Jobs.remove(nom); + }, + + /** + * Executes the specified [job]{@link module:enyo/job} immediately, then prevents + * any other calls to `throttleJob()` with the same job name from running for + * the specified amount of time. + * + * @param {String} nom - The name of the [job]{@link module:enyo/job} to throttle. + * @param {(Function|String)} job - Either the name of a method or a + * [function]{@glossary Function} to execute as the requested job. + * @param {Number} wait - The number of milliseconds to wait before executing the + * job again. + * @returns {this} The callee for chaining. + * @public + */ + throttleJob: function (nom, job, wait) { + var jobs = (this.__jobs = this.__jobs || {}); + // if we still have a job with this name pending, return immediately + if (!jobs[nom]) { + // allow strings as job names, they map to local method names + if (typeof job == 'string') job = this[job]; + job.call(this); + jobs[nom] = setTimeout(this.bindSafely(function() { + this.stopJob(nom); + }), wait); + } + return this; + } +}); + +Component.prototype.defaultKind = Component; + +/** +* @private +*/ +kind.setDefaultCtor(Component); + +/** +* Creates new instances from [config]{@glossary configurationBlock} +* [objects]{@glossary Object}. This method looks up the proper +* [constructor]{@glossary constructor} based on the provided [kind]{@glossary kind} +* attribute. +* +* @name module:enyo/Compoment~Component.create +* @param {Object} props - The properties that define the [kind]{@glossary kind}. +* @returns {*} An instance of the requested [kind]{@glossary kind}. +* @public +*/ +Component.create = function (props) { + var theKind, + Ctor; + + if (!props.kind && props.hasOwnProperty('kind')) throw new Error( + 'enyo.create: Attempt to create a null kind. Check dependencies for [' + props.name + ']' + ); + + theKind = props.kind || props.isa || kind.getDefaultCtor(); + Ctor = kind.constructorForKind(theKind); + + if (!Ctor) { + logger.error('No constructor found for kind ' + theKind); + Ctor = Component; + } + + return new Ctor(props); +}; + +/** +* @name module:enyo/Component~Component.subclass +* @static +* @private +*/ +Component.subclass = function (ctor, props) { + // Note: To reduce API surface area, sub-components are declared only as + // 'components' in both kind and instance declarations. + // + // However, 'components' from kind declarations must be handled separately + // at creation time. + // + // We rename the property here to avoid having + // to interrogate the prototype at creation time. + // + var proto = ctor.prototype; + // + if (props.components) { + proto.kindComponents = props.components; + delete proto.components; + } else { + // Feature to mixin overrides of super-kind component properties from named hash + // (only applied when the sub-kind doesn't supply its own components block) + if (props.componentOverrides) { + proto.kindComponents = Component.overrideComponents( + proto.kindComponents, + props.componentOverrides, + proto.defaultKind + ); + } + } +}; + +/** +* @name module:enyo/Component~Component.concat +* @static +* @private +*/ +Component.concat = function (ctor, props) { + var proto = ctor.prototype || ctor, + handlers; + if (props.handlers) { + handlers = proto.handlers ? utils.clone(proto.handlers) : {}; + proto.handlers = utils.mixin(handlers, props.handlers); + delete props.handlers; + } + if (props.events) Component.publishEvents(proto, props); +}; + +/** +* @name module:enyo/Component~Component.overrideComponents +* @static +* @private +*/ +Component.overrideComponents = function (components, overrides, defaultKind) { + var omitMethods = function (k, v) { + var isMethod = + // If it's a function, then it's a method (unless it's + // a constructor passed as value for 'kind') + (utils.isFunction(v) && (k !== 'kind')) || + // If it isInherited(), then it's also a method (since + // Inherited is an object wrapper for a function) + kind.isInherited(v); + + return !isMethod; + }; + components = utils.clone(components); + for (var i=0; i= 0) ? nom.slice(last+1) : nom; + pre = pre.charAt(0).toLowerCase() + pre.slice(1); + kindPrefix[nom] = pre; + } + + return pre; +} + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./logger':'enyo/logger','./CoreObject':'enyo/CoreObject','./ApplicationSupport':'enyo/ApplicationSupport','./ComponentBindingSupport':'enyo/ComponentBindingSupport','./jobs':'enyo/jobs'}],'enyo/AnimationSupport/AnimationSupport':[function (module,exports,global,require,request){ +require('enyo'); + +var + kind = require('../kind'), + core = require('./Core'), + activator = require('./KeyFrame'), + frame = require('./Frame'), + utils = require('../utils'); + +var extend = kind.statics.extend; + +kind.concatenated.push('animation'); + +var AnimationSupport = { + + /** + * @private + */ + //name: 'AnimationSupport', + animating: false, + + active: false, + + animationState: "", + + /** + * Check if the character is suitable for animation + * @public + */ + ready: function() { + var ret = this.generated && this.animating; + if (ret && this._startTime) + ret = this._startTime <= utils.perfNow(); + + if(ret) this.set('animationState', 'started'); + return ret; + }, + + /** + * Sets current animation state for this character + * @public + */ + setInitial: function (initial) { + this._startAnim = initial; + }, + + /** + * Gets current state of animation for this character + * @parameter accelerate- Turns on/off hardware acceleration + * @public + */ + initiate: function (current) { + var dom = this.hasNode(), + prop = this.getAnimation(), + init = frame.getCompoutedProperty(dom, prop, current); + + utils.mixin(this, init); + }, + + /** + * Gets animations applied to this chracter. + * @public + */ + getAnimation: function() { + return this._prop || (this._prop = this.animate); + }, + + /** + * Adds new animation on already existing animation for this character. + * @public + */ + addAnimation: function (newProp) { + if (this._prop === undefined || this._prop == true) { + this._prop = newProp; + } else { + utils.mixin(this._prop, newProp); + } + }, + + /** + * Sets new animation for this character. + * @public + */ + setAnimation: function (newProp) { + this._prop = newProp; + }, + + /** + * Gets how long animation is active on this character + * @public + */ + getDuration: function() { + return this._duration || (this._duration = this.duration); + }, + + /** + * Sets how long animation should be active on this character + * @public + */ + setDuration: function (newDuration) { + this._duration = newDuration; + }, + + /** + * Idnetify when the character has done animating. + * This triggers "onAnimated" event on this character + * @public + */ + completed: function() { + return this.onAnimated && this.onAnimated(this); + }, + + /** + * Trigger animation for this character. + * @public + */ + start: function (active, delay) { + this._duration = parseInt(this.getDuration(), 10); + this._startTime = utils.perfNow() + (delay || 0) ; + this._lastTime = this._startTime + this._duration; + this.animating = true; + this.active = active; + this.initiate(this.currentState); + }, + + /** + * Halt existing animation of this character + * @public + */ + pause: function () { + this.animating = false; + }, + + /** + * @private + */ + rendered: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + this.initiate(); + frame.accelerate(this.hasNode(), this.matrix); + }; + }), + + /** + * @private + */ + destroy: kind.inherit(function(sup) { + return function() { + core.remove(this); + sup.apply(this, arguments); + }; + }), +}; + +module.exports = AnimationSupport; + +/** + Hijacking original behaviour as in other Enyo supports. +*/ +var sup = kind.concatHandler; + +/** +* @private +*/ +kind.concatHandler = function (ctor, props, instance) { + sup.call(this, ctor, props, instance); + if (props.animate || props.keyFrame || props.pattern) { + var proto = ctor.prototype || ctor; + extend(AnimationSupport, proto); + if (props.keyFrame && typeof props.keyFrame != 'function') { + activator.animate(proto, props); + } + if (props.animate && typeof props.animate != 'function') { + activator.trigger(proto); + } + } +}; +},{'../kind':'enyo/kind','./Core':'enyo/AnimationSupport/Core','./KeyFrame':'enyo/AnimationSupport/KeyFrame','./Frame':'enyo/AnimationSupport/Frame','../utils':'enyo/utils'}],'enyo/Collection':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Collection~Collection} kind. +* @module enyo/Collection +*/ + +var + kind = require('./kind'), + utils = require('./utils'); + +var + Component = require('./Component'), + EventEmitter = require('./EventEmitter'), + Model = require('./Model'), + ModelList = require('./ModelList'), + StateSupport = require('./StateSupport'), + Source = require('./Source'), + Store = require('./Store'), + States = require('./States'); + +/** +* This is only necessary because of the order in which mixins are applied. +* +* @class +* @private +*/ +var BaseCollection = kind({ + kind: Component, + mixins: [EventEmitter, StateSupport] +}); + +/** +* Fires when [models]{@link module:enyo/Model~Model} have been [added]{@link module:enyo/Collection~Collection#add} +* to the [collection]{@link module:enyo/Collection~Collection}. +* +* @event module:enyo/Collection~Collection#add +* @type {Object} +* @property {module:enyo/Model~Model[]} models - An [array]{@glossary Array} of +* [models]{@link module:enyo/Model~Model} that were [added]{@link module:enyo/Collection~Collection#add} to the +* [collection]{@link module:enyo/Collection~Collection}. +* @property {module:enyo/Collection~Collection} collection - A reference to the +* collection that [emitted]{@link module:enyo/EventEmitter~EventEmitter#emit} the event. +* @property {Number} index - The index in the given collection where the models were inserted. +* @public +*/ + +/** +* Fires when [models]{@link module:enyo/Model~Model} have been [removed]{@link module:enyo/Collection~Collection#remove} +* from the [collection]{@link module:enyo/Collection~Collection}. +* +* @event module:enyo/Collection~Collection#remove +* @type {Object} +* @property {module:enyo/Model~Model[]} models - An [array]{@glossary Array} of +* [models]{@link module:enyo/Model~Model} that were [removed]{@link module:enyo/Collection~Collection#remove} from the +* [collection]{@link module:enyo/Collection~Collection}. +* @property {module:enyo/Collection~Collection} collection - A reference to the +* collection that [emitted]{@link module:enyo/EventEmitter~EventEmitter#emit} the event. +* @public +*/ + +/** +* Fires when the [collection]{@link module:enyo/Collection~Collection} has been +* [sorted]{@link module:enyo/Collection~Collection#sort}. +* +* @event module:enyo/Collection~Collection#sort +* @type {Object} +* @property {module:enyo/Model~Model[]} models - An [array]{@glossary Array} of all +* [models]{@link module:enyo/Model~Model} in the correct, [sorted]{@link module:enyo/Collection~Collection#sort} order. +* @property {module:enyo/Collection~Collection} collection - A reference to the +* [collection]{@link module:enyo/Collection~Collection} that [emitted]{@link module:enyo/EventEmitter~EventEmitter#emit} the event. +* @property {Function} comparator - A reference to the +* [comparator]{@link module:enyo/Collection~Collection#comparator} that was used when +* sorting the collection. +* @public +*/ + +/** +* Fires when the [collection]{@link module:enyo/Collection~Collection} has been reset and its +* contents have been updated arbitrarily. +* +* @event module:enyo/Collection~Collection#reset +* @type {Object} +* @property {module:enyo/Model~Model[]} models - An [array]{@glossary Array} of all +* [models]{@link module:enyo/Model~Model} as they are currently. +* @property {module:enyo/Collection~Collection} collection - A reference to the +* [collection]{@link module:enyo/Collection~Collection} that [emitted]{@link module:enyo/EventEmitter~EventEmitter#emit} the event. +* @public +*/ + +/** +* The default configurable [options]{@link module:enyo/Collection~Collection#options} used by certain API +* methods of {@link module:enyo/Collection~Collection}. +* +* @typedef {Object} module:enyo/Collection~Options +* @property {Boolean} merge=true - If `true`, when data is being added to the +* [collection]{@link module:enyo/Collection~Collection} that already exists (i.e., is matched by +* [primaryKey]{@link module:enyo/Model~Model#primaryKey}), the new data values will be set +* with the current [model]{@link module:enyo/Model~Model} instance. This means that the +* existing values will be updated with the new ones by calling +* [set()]{@link module:enyo/Model~Model#set} on the model. +* @property {Boolean} silent=false - Many accessor methods of the collection +* will emit events and/or notifications. This value indicates whether or not +* those events or notifications will be suppressed at times when that behavior +* is necessary. Typically, you will not want to modify this value. +* @property {Boolean} purge=false - When [adding]{@link module:enyo/Collection~Collection#add} +* models, this flag indicates whether or not to [remove]{@link module:enyo/Collection~Collection#remove} +* (purge) the existing models that are not included in the new dataset. +* @property {Boolean} parse=false - The collection's [parse()]{@link module:enyo/Collection~Collection#parse} +* method can be executed automatically when incoming data is added via the +* [constructor()]{@link module:enyo/Collection~Collection#constructor} method, or, later, via a +* [fetch]{@link module:enyo/Collection~Collection#fetch}. You may need to examine the runtime +* configuration options of the method(s) to determine whether parsing is needed. +* In cases where parsing will always be necessary, this may be set to `true`. +* @property {Boolean} create=true - This value determines whether a new +* model will be created when data being added to the collection cannot be found +* (or the [find]{@link module:enyo/Collection~Collection#options#find} flag is `false`). Models +* that are created by a collection have their [owner]{@link module:enyo/Model~Model#owner} +* property set to the collection that instanced them. +* @property {Boolean} find=true - When data being added to the collection is not +* already a model instance, the collection will attempt to find an existing model +* by its `primaryKey`, if it exists. In most cases, this is the preferred behavior, +* but if the model [kind]{@glossary kind} being instanced does not have a +* `primaryKey`, it is unnecessary and this value may be set to `false`. +* @property {Boolean} sort=false - When adding models to the collection, the +* collection can also be sorted. If the [comparator]{@link module:enyo/Collection~Collection#comparator} +* is a [function]{@glossary Function} and this value is `true`, the comparator +* will be used to sort the entire collection. It may also be a function that +* will be used to sort the collection, instead of (or in the place of) a defined +* comparator. +* @property {Boolean} commit=false - When modifications are made to the +* collection, this flag ensures that those changes are +* [committed]{@link module:enyo/Collection~Collection#commit} according to the configuration and +* availability of a [source]{@link module:enyo/Collection~Collection#source}. This may also be +* configured per-call to methods that use it. +* @property {Boolean} destroy=false - When models are removed from the collection, +* this flag indicates whether or not they will be [destroyed]{@link module:enyo/Model~Model#destroy} +* as well. Note that this could have a significant impact if the same models are +* used in other collections. +* @property {Boolean} complete=false - When models are removed from the +* collection, this flag indicates whether or not they will also be removed from +* the [store]{@link module:enyo/Collection~Collection#store}. This is rarely necessary and can +* cause problems if the models are used in other collections. In addition, this +* value will be ignored if the [destroy]{@link module:enyo/Collection~Collection#options#destroy} +* flag is `true`. +* @property {Boolean} fetch=false - If `true`, when the collection is initialized, +* it will automatically attempt to fetch data if the +* [source]{@link module:enyo/Collection~Collection#source} and [url]{@link module:enyo/Collection~Collection#url} +* or [getUrl]{@link module:enyo/Collection~Collection#getUrl} properties are properly configured. +* @property {Boolean} modelEvents=true - If `false`, this will keep the collection from +* registering with each model for individual model events. +*/ + +/** +* The configuration options for [add()]{@link module:enyo/Collection~Collection#add}. For complete +* descriptions of the options and their default values, see +* {@link module:enyo/Collection~Collection#options}. Note that some properties have different +* meanings in different contexts. Please review the descriptions below to see +* how each property is used in this context. +* +* @typedef {module:enyo/Collection~Options} module:enyo/Collection~AddOptions +* @property {Boolean} merge - Update existing [models]{@link module:enyo/Model~Model} when found. +* @property {Boolean} purge - Remove existing models not in the new dataset. +* @property {Boolean} silent - Emit [events]{@glossary event} and notifications. +* @property {Boolean} parse - Parse the incoming dataset before evaluating. +* @property {Boolean} find - Look for an existing model. +* @property {(Boolean|Function)} sort - Sort the finalized dataset. +* @property {Boolean} commit - [Commit]{@link module:enyo/Collection~Collection#commit} changes to the +* {@link module:enyo/Collection~Collection} after completing the [add]{@link module:enyo/Collection~Collection#add} +* operation. +* @property {Boolean} create - When an existing {@link module:enyo/Model~Model} instance cannot be +* resolved, a new instance should be created. +* @property {number} index - The index at which to add the new dataset. Defaults to the +* end of the current dataset if not explicitly set or invalid. +* @property {Boolean} destroy - If `purge` is `true`, this will +* [destroy]{@link module:enyo/Model~Model#destroy} any models that are +* [removed]{@link module:enyo/Collection~Collection#remove}. +* @property {Object} modelOptions - When instancing a model, this +* [object]{@glossary Object} will be passed to the constructor as its `options` +* parameter. +*/ + +/** +* The configuration options for [remove()]{@link module:enyo/Collection~Collection#remove}. For +* complete descriptions of the options and their defaults, see +* {@link module:enyo/Collection~Options}. Note that some properties have different +* meanings in different contexts. Please review the descriptions below to see +* how each property is used in this context. +* +* @typedef {module:enyo/Collection~Options} module:enyo/Collection~RemoveOptions +* @property {Boolean} silent - Emit [events]{@glossary event} and notifications. +* @property {Boolean} commit - [Commit]{@link module:enyo/Collection~Collection#commit} changes to the +* [collection]{@link module:enyo/Collection~Collection} after completing the +* [remove]{@link module:enyo/Collection~Collection#remove} operation. +* @property {Boolean} complete - Remove the [model]{@link module:enyo/Model~Model} from the +* [store]{@link module:enyo/Collection~Collection#store} as well as the collection. +* @property {Boolean} destroy - [Destroy]{@link module:enyo/Model~Model#destroy} models +* that are removed from the collection. +*/ + +/** +* The configurable options for [fetch()]{@link module:enyo/Collection~Collection#fetch}, +* [commit()]{@link module:enyo/Collection~Collection#commit}, and [destroy()]{@link module:enyo/Collection~Collection#destroy}. +* +* @typedef {module:enyo/Collection~Options} module:enyo/Collection~ActionOptions +* @property {module:enyo/Collection~Collection~Success} success - The callback executed upon successful +* completion. +* @property {module:enyo/Collection~Collection~Error} error - The callback executed upon a failed attempt. +*/ + +/** +* @callback module:enyo/Collection~Collection~Success +* @param {module:enyo/Collection~Collection} collection - The [collection]{@link module:enyo/Collection~Collection} +* that is returning successfully. +* @param {module:enyo/Collection~ActionOptions} - opts The original options passed to the action method +* that is returning successfully. +* @param {*} - res The result, if any, returned by the [source]{@link module:enyo/Source~Source} that +* executed it. +* @param {String} source - The name of the [source]{@link module:enyo/Collection~Collection#source} that has +* returned successfully. +*/ + +/** +* @callback module:enyo/Collection~Collection~Error +* @param {module:enyo/Collection~Collection} collection - The [collection]{@link module:enyo/Collection~Collection} +* that is returning an error. +* @param {String} action - The name of the action that failed, one of `'FETCHING'`, +* `'COMMITTING'`, or `'DESTROYING'`. +* @param {module:enyo/Collection~ActionOptions} opts - The original options passed to the +* action method that is returning an error. +* @param {*} res - The result, if any, returned by the [source]{@link module:enyo/Source~Source} +* that executed it. +* @param {String} source - The name of the [source]{@link module:enyo/Collection~Collection#source} +* that has returned an error. +*/ + +/** +* A method used to compare two elements in an {@link module:enyo/Collection~Collection}. Should be +* implemented like callbacks used with [Array.sort()]{@glossary Array.sort}. +* +* @see {@glossary Array.sort} +* @see module:enyo/Collection~Collection#sort +* @see module:enyo/Collection~Collection#comparator +* @callback module:enyo/Collection~Collection~Comparator +* @param {module:enyo/Model~Model} a - The first [model]{@link module:enyo/Model~Model} to compare. +* @param {module:enyo/Model~Model} b - The second model to compare. +* @returns {Number} `-1` if `a` should have the lower index, `0` if they are the same, +* or `1` if `b` should have the lower index. +*/ + +/** +* An array-like structure designed to store instances of {@link module:enyo/Model~Model}. +* +* @class Collection +* @extends module:enyo/Component~Component +* @mixes module:enyo/StateSupport~StateSupport +* @mixes module:enyo/EventEmitter~EventEmitter +* @public +*/ +exports = module.exports = kind( + /** @lends module:enyo/Collection~Collection.prototype */ { + + name: 'enyo.Collection', + + /** + * @private + */ + kind: BaseCollection, + + /** + * @private + */ + + + /** + * Used by various [sources]{@link module:enyo/Collection~Collection#source} as part of the + * [URI]{@glossary URI} from which they may be [fetched]{@link module:enyo/Collection~Collection#fetch}, + * [committed]{@link module:enyo/Collection~Collection#commit}, or [destroyed]{@link module:enyo/Collection~Collection#destroy}. + * Some sources may use this property in other ways. + * + * @see module:enyo/Collection~Collection#getUrl + * @see module:enyo/Source~Source + * @see module:enyo/AjaxSource~AjaxSource + * @see module:enyo/JsonpSource~JsonpSource + * @type {String} + * @default '' + * @public + */ + url: '', + + /** + * Implement this method to be used by [sources]{@link module:enyo/Model~Model#source} to + * dynamically derive the [URI]{@glossary URI} from which they may be + * [fetched]{@link module:enyo/Collection~Collection#fetch}, [committed]{@link module:enyo/Collection~Collection#commit}, + * or [destroyed]{@link module:enyo/Collection~Collection#destroy}. Some + * [sources]{@link module:enyo/Collection~Collection#source} may use this property in other ways. + * Note that if this method is implemented, the [url]{@link module:enyo/Collection~Collection#url} + * property will not be used. + * + * @see module:enyo/Collection~Collection#url + * @see module:enyo/Source~Source + * @see module:enyo/AjaxSource~AjaxSource + * @see module:enyo/JsonpSource~JsonpSource + * @type {Function} + * @default null + * @virtual + * @public + */ + getUrl: null, + + /** + * The [kind]{@glossary kind) of {@link module:enyo/Model~Model} that this + * [collection]{@link module:enyo/Collection~Collection} will contain. This is important to set properly so + * that when [fetching]{@link module:enyo/Collection~Collection#fetch}, the returned data will be instanced + * as the correct model [subkind]{@glossary subkind}. + * + * @type {(module:enyo/Model~Model|String)} + * @default module:enyo/Model~Model + * @public + */ + model: Model, + + /** + * A special type of [array]{@glossary Array} used internally by + * {@link module:enyo/Collection~Collection}. The array should not be modified directly, nor + * should the property be set directly. It is used as a container by the + * collection. If [set]{@link module:enyo/Collection~Collection#set} directly, it will + * [emit]{@link module:enyo/EventEmitter~EventEmitter#emit} a [reset]{@link module:enyo/Collection~Collection#reset} + * event. + * + * @see module:enyo/Collection~Collection#modelsChanged + * @type module:enyo/ModelList~ModelList + * @default null + * @readonly + * @protected + */ + models: null, + + /** + * The current [state]{@link module:enyo/States} of the [collection]{@link module:enyo/Collection~Collection}. + * This value changes automatically and may be observed for more complex state + * monitoring. The default value is [READY]{@link module:enyo/States.READY}. + * @type module:enyo/States + * @default module:enyo/States.READY + * @readonly + * @public + * @see module:enyo/States + * @see module:enyo/StateSupport + */ + status: States.READY, + + /** + * The configurable default [options]{@link module:enyo/Collection~Options}. These values will be + * used to modify the behavior of the [collection]{@link module:enyo/Collection~Collection} unless additional + * options are passed into the methods that use them. When modifying these values in a + * [subkind]{@glossary subkind} of {@link module:enyo/Collection~Collection}, they will be merged with + * existing values. + * + * @type {module:enyo/Collection~Options} + * @public + */ + options: { + merge: true, + silent: false, + purge: false, + parse: false, + create: true, + find: true, + sort: false, + commit: false, + destroy: false, + complete: false, + fetch: false, + modelEvents: true + }, + + /** + * Modifies the structure of data so that it can be used by the + * [add()]{@link module:enyo/Collection~Collection#add} method. This method will only be used + * during initialization or after a successful [fetch]{@link module:enyo/Collection~Collection#fetch} + * if the [parse]{@link module:enyo/Collection~Options#parse} flag is set to `true`. + * It may be used for simple remapping, renaming, or complex restructuring of + * data coming from a [source]{@link module:enyo/Collection~Collection#source} that requires + * modification before it can be added to the [collection]{@link module:enyo/Collection~Collection}. + * This is a virtual method and must be implemented. + * + * @param {*} data - The incoming data passed to the + * [constructor]{@link module:enyo/Collection~Collection#constructor} or returned by a successful + * [fetch]{@link module:enyo/Collection~Collection#fetch}. + * @returns {Array} The properly formatted data to be accepted by the + * [add()]{@link module:enyo/Collection~Collection#add} method. + * @virtual + * @public + */ + parse: function (data) { + return data; + }, + + /** + * Adds data to the [collection]{@link module:enyo/Collection~Collection}. This method can add an + * individual [model]{@link module:enyo/Model~Model} or an [array]{@glossary Array} of models. + * It can splice them into the dataset at a designated index or remove models + * from the existing dataset that are not included in the new one. + * See {@link module:enyo/Collection~AddOptions} for detailed information on the + * configuration options available for this method. This method is heavily + * optimized for batch operations on arrays of models. For better performance, + * ensure that loops do not consecutively call this method but instead + * build an array to pass as the first parameter. + * + * @fires module:enyo/Collection~Collection#add + * @param {(Object|Object[]|module:enyo/Model~Model|module:enyo/Model~Model[])} models The data to add to the + * {@link module:enyo/Collection~Collection} that can be a [hash]{@glossary Object}, an array of + * hashes, an {@link module:enyo/Model~Model} instance, or and array of `Model` instances. + * Note that if the [parse]{@link module:enyo/Collection~Collection#options#parse} configuration + * option is `true`, it will use the returned value as this parameter. + * @param {module:enyo/Collection~AddOptions} [opts] - The configuration options that modify + * the behavior of this method. The default values will be merged with these options + * before evaluating. + * @returns {module:enyo/Model~Model[]} The models that were added, if any. + * @public + */ + add: function (models, opts) { + var loc = this.models + , len = this.length + , ctor = this.model + , options = this.options + , pkey = ctor.prototype.primaryKey + , idx = len + , removedBeforeIdx = 0 + , added, keep, removed, model, attrs, found, id; + + // for backwards compatibility with earlier api standards we allow the + // second paramter to be the index and third param options when + // necessary + !isNaN(opts) && (idx = opts); + arguments.length > 2 && (opts = arguments[2]); + + // normalize options so we have values + opts = opts? utils.mixin({}, [options, opts]): options; + + // our flags + var merge = opts.merge + , purge = opts.purge + , silent = opts.silent + , parse = opts.parse + , find = opts.find + , sort = opts.sort + , commit = opts.commit + , create = opts.create !== false + , modelOpts = opts.modelOptions + , index = opts.index; + + idx = !isNaN(index) ? Math.max(0, Math.min(len, index)) : idx; + + /*jshint -W018 */ + sort && !(typeof sort == 'function') && (sort = this.comparator); + /*jshint +W018 */ + + // for a special case purge to remove records that aren't in the current + // set being added + + if (parse) models = this.parse(models); + + // we treat all additions as an array of additions + !(models instanceof Array) && (models = [models]); + + for (var i=0, end=models.length; i -1) it._waiting.splice(idx, 1); + if (!it._waiting.length) it._waiting = null; + } + + // continue the operation this time with commit false explicitly + if (!it._waiting) { + options.commit = options.source = null; + it.destroy(options); + } + if (opts && opts.success) opts.success(this, opts, res, source); + }; + + options.error = function (source, res) { + + if (it._waiting) { + idx = it._waiting.findIndex(function (ln) { + return (ln instanceof Source ? ln.name : ln) == source; + }); + if (idx > -1) it._waiting.splice(idx, 1); + if (!it._waiting.length) it._waiting = null; + } + + // continue the operation this time with commit false explicitly + if (!it._waiting) { + options.commit = options.source = null; + it.destroy(options); + } + + // we don't bother setting the error state if we aren't waiting because + // it will be cleared to DESTROYED and it would be pointless + else this.errored('DESTROYING', opts, res, source); + }; + + this.set('status', (this.status | States.DESTROYING) & ~States.READY); + + Source.execute('destroy', this, options); + } else if (this.status & States.ERROR) this.errored(this.status, opts); + + // we don't allow the destroy to take place and we don't forcibly break-down + // the collection errantly so there is an opportuniy to resolve the issue + // before we lose access to the collection's content! + return this; + } + + if (this.length && options.destroy) this.empty(options); + + // set the final resting state of this collection + this.set('status', States.DESTROYED); + + sup.apply(this, arguments); + }; + }), + + /** + * This is a virtual method that, when provided, will be used for sorting during + * [add()]{@link module:enyo/Collection~Collection#add} when the `sort` flag is `true` or when the + * [sort()]{@link module:enyo/Collection~Collection#sort} method is called without a passed-in + * [function]{@glossary Function} parameter. + * + * @see module:enyo/Collection~Collection~Comparator + * @type {module:enyo/Collection~Collection~Comparator} + * @default null + * @virtual + * @method + * @public + */ + comparator: null, + + /** + * Used during [add()]{@link module:enyo/Collection~Collection#add} when `create` is `true` and + * the data is a [hash]{@glossary Object}. + * + * @private + */ + prepareModel: function (attrs, opts) { + var Ctor = this.model + , options = this.options + , model; + + attrs instanceof Ctor && (model = attrs); + if (!model) { + opts = opts || {}; + opts.noAdd = true; + model = new Ctor(attrs, null, opts); + } + + if (options.modelEvents) model.on('*', this._modelEvent, this); + + return model; + }, + + /** + * When a [commit]{@link module:enyo/Collection~Collection#commit} has completed successfully, it is returned + * to this method. This method handles special and important behavior; it should not be + * called directly and, when overloading, care must be taken to ensure that the + * super-method is called. This correctly sets the [status]{@link module:enyo/Collection~Collection#status} + * and, in cases where multiple [sources]{@link module:enyo/Collection~Collection#source} were used, it waits + * until all have responded before clearing the [COMMITTING]{@link module:enyo/States.COMMITTING} + * flag. If a [success]{@link module:enyo/Collection~Collection~Success} callback was provided, it will be + * called once for each source. + * + * @param {module:enyo/Collection~Collection~ActionOptions} opts - The original options passed to + * [commit()]{@link module:enyo/Collection~Collection#commit}, merged with the defaults. + * @param {*} [res] - The result provided from the given + * [source]{@link module:enyo/Collection~Collection#source}, if any. This will vary depending + * on the source. + * @param {String} source - The name of the source that has completed successfully. + * @public + */ + committed: function (opts, res, source) { + var idx; + + if (this._waiting) { + idx = this._waiting.findIndex(function (ln) { + return (ln instanceof Source ? ln.name : ln) == source; + }); + if (idx > -1) this._waiting.splice(idx, 1); + if (!this._waiting.length) this._waiting = null; + } + + if (opts && opts.success) opts.success(this, opts, res, source); + + // clear the state + if (!this._waiting) { + this.set('status', (this.status | States.READY) & ~States.COMMITTING); + } + }, + + /** + * When a [fetch]{@link module:enyo/Collection~Collection#fetch} has completed successfully, it is returned + * to this method. This method handles special and important behavior; it should not be + * called directly and, when overloading, care must be taken to ensure that you call the + * super-method. This correctly sets the [status]{@link module:enyo/Collection~Collection#status} and, in + * cases where multiple [sources]{@link module:enyo/Collection~Collection#source} were used, it waits until + * all have responded before clearing the [FETCHING]{@link module:enyo/States.FETCHING} flag. If + * a [success]{@link module:enyo/Collection~Collection~Success} callback was provided, it will be called + * once for each source. + * + * @param {module:enyo/Collection~Collection~ActionOptions} opts - The original options passed to + * [fetch()]{@link module:enyo/Collection~Collection#fetch}, merged with the defaults. + * @param {*} [res] - The result provided from the given + * [source]{@link module:enyo/Collection~Collection#source}, if any. This will vary depending + * on the source. + * @param {String} source - The name of the source that has completed successfully. + * @public + */ + fetched: function (opts, res, source) { + var idx; + + if (this._waiting) { + idx = this._waiting.findIndex(function (ln) { + return (ln instanceof Source ? ln.name : ln) == source; + }); + if (idx > -1) this._waiting.splice(idx, 1); + if (!this._waiting.length) this._waiting = null; + } + + // if there is a result we add it to the collection passing it any per-fetch options + // that will override the defaults (e.g. parse) we don't do that here as it will + // be done in the add method -- also note we reassign the result to whatever was + // actually added and pass that to any other success callback if there is one + if (res) res = this.add(res, opts); + + // now look for an additional success callback + if (opts && opts.success) opts.success(this, opts, res, source); + + // clear the state + if (!this._waiting) { + this.set('status', (this.status | States.READY) & ~States.FETCHING); + } + }, + + /** + * If an error is encountered while [fetching]{@link module:enyo/Collection~Collection#fetch}, + * [committing]{@link module:enyo/Collection~Collection#commit}, or [destroying]{@link module:enyo/Collection~Collection#destroy} + * the [collection]{@link module:enyo/Collection~Collection}, this method will be called. By + * default, it updates the collection's [status]{@link module:enyo/Collection~Collection#status} + * property and then checks to see if there is a provided + * [error handler]{@link module:enyo/Collection~Collection~Error}. If the error handler + * exists, it will be called. + * + * @param {String} action - The name of the action that failed, + * one of `'FETCHING'` or `'COMMITTING'`. + * @param {module:enyo/Collection~Collection~ActionOptions} opts - The options hash originally + * passed along with the original action. + * @param {*} [res] - The result of the requested `action`; varies depending on the + * requested [source]{@link module:enyo/Collection~Collection#source}. + * @param {String} source - The name of the source that has returned an error. + * @public + */ + errored: function (action, opts, res, source) { + var stat; + + // if the error action is a status number then we don't need to update it otherwise + // we set it to the known state value + if (typeof action == 'string') { + + // all built-in errors will pass this as their values are > 0 but we go ahead and + // ensure that no developer used the 0x00 for an error code + stat = States['ERROR_' + action]; + } else stat = action; + + if (isNaN(stat) || !(stat & States.ERROR)) stat = States.ERROR_UNKNOWN; + + // if it has changed give observers the opportunity to respond + this.set('status', (this.status | stat) & ~States.READY); + + // we need to check to see if there is an options handler for this error + if (opts && opts.error) opts.error(this, action, opts, res, source); + }, + + /** + * Overloaded version of the method to call [set()]{@link module:enyo/Collection~Collection#set} + * instead of simply assigning the value. This allows it to + * [notify observers]{@link module:enyo/ObserverSupport} and thus update + * [bindings]{@link module:enyo/BindingSupport#binding} as well. + * + * @see {@link module:enyo/StateSupport~StateSupport#clearError} + * @public + */ + clearError: function () { + return this.set('status', States.READY); + }, + + /** + * @private + */ + _modelEvent: function (model, e) { + switch (e) { + case 'change': + this.emit('change', {model: model}); + break; + case 'destroy': + this.remove(model); + break; + } + }, + + /** + * Responds to changes to the [models]{@link module:enyo/Collection~Collection#models} property. + * + * @see module:enyo/Collection~Collection#models + * @fires module:enyo/Collection~Collection#reset + * @type {module:enyo/ObserverSupport~ObserverSupport~Observer} + * @public + */ + modelsChanged: function (was, is, prop) { + var models = this.models.copy(), + len = models.length; + + if (len != this.length) this.set('length', len); + + this.emit('reset', {models: models, collection: this}); + }, + + /** + * Initializes the [collection]{@link module:enyo/Collection~Collection}. + * + * @param {(Object|Object[]|module:enyo/Model~Model[])} [recs] May be an [array]{@glossary Array} + * of either [models]{@link module:enyo/Model~Model} or [hashes]{@glossary Object} used to + * initialize the [collection]{@link module:enyo/Collection~Collection}, or an [object]{@glossary Object} + * equivalent to the `props` parameter. + * @param {Object} [props] - A hash of properties to apply directly to the + * collection. + * @param {Object} [opts] - A hash. + * @method + * @public + */ + constructor: kind.inherit(function (sup) { + return function (recs, props, opts) { + // opts = opts? (this.options = enyo.mixin({}, [this.options, opts])): this.options; + + // if properties were passed in but not a records array + props = recs && !(recs instanceof Array)? recs: props; + if (props === recs) recs = null; + // initialize our core records + // this.models = this.models || new ModelList(); + !this.models && (this.set('models', new ModelList())); + + // this is backwards compatibility + if (props && props.records) { + recs = recs? recs.concat(props.records): props.records.slice(); + delete props.records; + } + + if (props && props.models) { + recs = recs? recs.concat(props.models): props.models.slice(); + delete props.models; + } + + if (props && props.options) { + this.options = utils.mixin({}, [this.options, props.options]); + delete props.options; + } + + opts = opts? utils.mixin({}, [this.options, opts]): this.options; + + // @TODO: For now, while there is only one property we manually check for it + // if more options arrise that should be configurable this way it may need to + // be modified + opts.fetch && (this.options.fetch = opts.fetch); + + this.length = this.models.length; + this.euid = utils.uid('c'); + + sup.call(this, props); + + typeof this.model == 'string' && (this.model = kind.constructorForKind(this.model)); + this.store = this.store || Store; + recs && recs.length && this.add(recs, opts); + }; + }), + + /** + * @method + * @private + */ + constructed: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + + // automatically attempt a fetch after initialization is complete + if (this.options.fetch) this.fetch(); + }; + }) + +}); + +/** +* @name module:enyo/Collection~Collection.concat +* @static +* @private +*/ +exports.concat = function (ctor, props) { + var proto = ctor.prototype || ctor; + + if (props.options) { + proto.options = utils.mixin({}, [proto.options, props.options]); + delete props.options; + } +}; + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./Component':'enyo/Component','./EventEmitter':'enyo/EventEmitter','./Model':'enyo/Model','./ModelList':'enyo/ModelList','./StateSupport':'enyo/StateSupport','./Source':'enyo/Source','./Store':'enyo/Store','./States':'enyo/States'}],'enyo/Signals':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Signals~Signals} kind. +* @module enyo/Signals +*/ + +var + kind = require('./kind'), + utils = require('./utils'); + +var + Component = require('./Component'); + +/** +* {@link module:enyo/Signals~Signals} is a [component]{@link module:enyo/Component~Component} used to listen +* to global messages. +* +* An object with a Signals component can listen to messages sent from anywhere +* by declaring handlers for them. +* +* DOM [events]{@glossary event} that have no node targets are broadcast as +* signals. These events include Window events, such as `onload` and +* `onbeforeunload`, as well as events that occur directly on `document`, such +* as `onkeypress` if `document` has the focus. +* +* For more information, see the documentation on [Event +* Handling]{@linkplain $dev-guide/key-concepts/event-handling.html} in the +* Enyo Developer Guide. +* +* @class Signals +* @extends module:enyo/Component~Component +* @public +*/ +var Signals = module.exports = kind( + /** @lends module:enyo/Signals~Signals.prototype */ { + + name: 'enyo.Signals', + + /** + * @private + */ + kind: Component, + + /** + * Needed because of early calls to bind DOM {@glossary event} listeners + * to the [enyo.Signals.send()]{@link module:enyo/Signals~Signals#send} call. + * + * @private + */ + + + /** + * @method + * @private + */ + create: kind.inherit(function (sup) { + return function() { + sup.apply(this, arguments); + Signals.addListener(this); + }; + }), + + /** + * @method + * @private + */ + destroy: kind.inherit(function (sup) { + return function() { + Signals.removeListener(this); + sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + notify: function (msg, load) { + this.dispatchEvent(msg, load); + }, + + /** + * @private + */ + protectedStatics: { + listeners: [], + addListener: function(listener) { + this.listeners.push(listener); + }, + removeListener: function(listener) { + utils.remove(listener, this.listeners); + } + }, + + /** + * @private + */ + statics: + /** @lends module:enyo/Signals~Signals.prototype */ { + + /** + * Broadcasts a global message to be consumed by subscribers. + * + * @param {String} msg - The message to send; usually the name of the + * {@glossary event}. + * @param {Object} load - An [object]{@glossary Object} containing any + * associated event properties to be accessed by subscribers. + * @public + */ + send: function (msg, load) { + utils.forEach(this.listeners, function(l) { + l.notify(msg, load); + }); + } + } +}); + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./Component':'enyo/Component'}],'enyo/MultipleDispatchComponent':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/MultipleDispatchComponent~MultipleDispatchComponent} kind. +* @module enyo/MultipleDispatchComponent +*/ + +var + kind = require('./kind'); + +var + Component = require('./Component'), + MultipleDispatchSupport = require('./MultipleDispatchSupport'); + +/** +* {@link module:enyo/MultipleDispatchComponent~MultipleDispatchComponent} is a purely abstract [kind] +* {@glossary kind} that simply provides a common ancestor for +* {@link module:enyo/Component~Component} [objects]{@glossary Object} that need +* the [MultipleDispatchSupport]{@link module:enyo/MultipleDispatchSupport~MultipleDispatchSupport} +* [mixin]{@glossary mixin}. +* +* @class MultipleDispatchComponent +* @extends module:enyo/Component~Component +* @mixes module:enyo/MultipleDispatchSupport~MultipleDispatchSupport +* @public +*/ +module.exports = kind( + /** @lends module:enyo/MultipleDispatchComponent~MultipleDispatchComponent */ { + + /** + * @private + */ + kind: Component, + + /** + * @private + */ + mixins: [MultipleDispatchSupport] +}); + +},{'./kind':'enyo/kind','./Component':'enyo/Component','./MultipleDispatchSupport':'enyo/MultipleDispatchSupport'}],'enyo/ScrollMath':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/ScrollMath~ScrollMath} kind. +* @module enyo/ScrollMath +*/ + +var + kind = require('./kind'), + utils = require('./utils'), + platform = require('./platform'), + animation = require('./animation'); + +var + Component = require('./Component'); + +/** +* Fires when a scrolling action starts. +* +* @event module:enyo/ScrollMath~ScrollMath#onScrollStart +* @type {Object} +* @property {Object} sender - The [component]{@link module:enyo/Component~Component} that most recently +* propagated the {@glossary event}. +* @property {module:enyo/Scroller~Scroller~ScrollEvent} event - An [object]{@glossary Object} containing +* event information. +* @private +*/ + +/** +* Fires while a scrolling action is in progress. +* +* @event module:enyo/ScrollMath~ScrollMath#onScroll +* @type {Object} +* @property {Object} sender - The [component]{@link module:enyo/Component~Component} that most recently +* propagated the {@glossary event}. +* @property {module:enyo/Scroller~Scroller~ScrollEvent} event - An [object]{@glossary Object} containing +* event information. +* @private +*/ + +/** +* Fires when a scrolling action stops. +* +* @event module:enyo/ScrollMath~ScrollMath#onScrollStop +* @type {Object} +* @property {Object} sender - The [component]{@link module:enyo/Component~Component} that most recently +* propagated the {@glossary event}. +* @property {module:enyo/Scroller~Scroller~ScrollEvent} event - An [object]{@glossary Object} containing +* event information. +* @private +*/ + +/** +* {@link module:enyo/ScrollMath~ScrollMath} implements a scrolling dynamics simulation. It is a +* helper [kind]{@glossary kind} used by other [scroller]{@link module:enyo/Scroller~Scroller} +* kinds, such as {@link module:enyo/TouchScrollStrategy~TouchScrollStrategy}. +* +* `enyo.ScrollMath` is not typically created in application code. +* +* @class ScrollMath +* @protected +*/ +module.exports = kind( + /** @lends module:enyo/ScrollMath~ScrollMath.prototype */ { + + name: 'enyo.ScrollMath', + + /** + * @private + */ + kind: Component, + + /** + * @private + */ + published: + /** @lends module:enyo/ScrollMath~ScrollMath.prototype */ { + + /** + * Set to `true` to enable vertical scrolling. + * + * @type {Boolean} + * @default true + * @private + */ + vertical: true, + + /** + * Set to `true` to enable horizontal scrolling. + * + * @type {Boolean} + * @default true + * @private + */ + horizontal: true + }, + + /** + * @private + */ + events: { + onScrollStart: '', + onScroll: '', + onScrollStop: '', + onStabilize: '' + }, + + /** + * "Spring" damping returns the scroll position to a value inside the boundaries. Lower + * values provide faster snapback. + * + * @private + */ + kSpringDamping: 0.93, + + /** + * "Drag" damping resists dragging the scroll position beyond the boundaries. Lower values + * provide more resistance. + * + * @private + */ + kDragDamping: 0.5, + + /** + * "Friction" damping reduces momentum over time. Lower values provide more friction. + * + * @private + */ + kFrictionDamping: 0.97, + + /** + * Additional "friction" damping applied when momentum carries the viewport into overscroll. + * Lower values provide more friction. + * + * @private + */ + kSnapFriction: 0.9, + + /** + * Scalar applied to `flick` event velocity. + * + * @private + */ + kFlickScalar: 15, + + /** + * Limits the maximum allowable flick. On Android > 2, we limit this to prevent compositing + * artifacts. + * + * @private + */ + kMaxFlick: platform.android > 2 ? 2 : 1e9, + + /** + * The value used in [friction()]{@link module:enyo/ScrollMath~ScrollMath#friction} to determine if the delta + * (e.g., y - y0) is close enough to zero to consider as zero. + * + * @private + */ + kFrictionEpsilon: platform.webos >= 4 ? 1e-1 : 1e-2, + + /** + * Top snap boundary, generally `0`. + * + * @private + */ + topBoundary: 0, + + /** + * Right snap boundary, generally `(viewport width - content width)`. + * + * @private + */ + rightBoundary: 0, + + /** + * Bottom snap boundary, generally `(viewport height - content height)`. + * + * @private + */ + bottomBoundary: 0, + + /** + * Left snap boundary, generally `0`. + * + * @private + */ + leftBoundary: 0, + + /** + * Animation time step. + * + * @private + */ + interval: 20, + + /** + * Flag to enable frame-based animation; if `false`, time-based animation is used. + * + * @private + */ + fixedTime: true, + + /** + * Simulation state. + * + * @private + */ + x0: 0, + + /** + * Simulation state. + * + * @private + */ + x: 0, + + /** + * Simulation state. + * + * @private + */ + y0: 0, + + /** + * Simulation state. + * + * @private + */ + y: 0, + + /** + * @method + * @private + */ + create: kind.inherit(function (sup) { + return function() { + sup.apply(this, arguments); + this.boundarySnapshot = {}; + }; + }), + + /** + * @method + * @private + */ + destroy: kind.inherit(function (sup) { + return function() { + this.stop(); + sup.apply(this, arguments); + }; + }), + + /** + * Simple Verlet integrator for simulating Newtonian motion. + * + * @private + */ + verlet: function () { + var x = this.x; + this.x += x - this.x0; + this.x0 = x; + // + var y = this.y; + this.y += y - this.y0; + this.y0 = y; + }, + + /** + * Boundary damping function. Returns damped `value` based on `coeff` on one side of + * `origin`. + * + * @private + */ + damping: function (val, origin, coeff, sign) { + var kEpsilon = 0.5; + // + // this is basically just value *= coeff (generally, coeff < 1) + // + // 'sign' and the conditional is to force the damping to only occur + // on one side of the origin. + // + var dv = val - origin; + // Force close-to-zero to zero + if (Math.abs(dv) < kEpsilon) { + return origin; + } + return val*sign > origin*sign ? coeff * dv + origin : val; + }, + + /** + * Dual-boundary damping function. Returns damped `value` based on `coeff` when exceeding + * either boundary. + * + * @private + */ + boundaryDamping: function (val, aBoundary, bBoundary, coeff) { + return this.damping(this.damping(val, aBoundary, coeff, 1), bBoundary, coeff, -1); + }, + + /** + * Simulation constraints (spring damping occurs here). + * + * @private + */ + constrain: function () { + var b = this.getDampingBoundaries(), + y = this.boundaryDamping(this.y, b.top, b.bottom, this.kSpringDamping), + x = this.boundaryDamping(this.x, b.left, b.right, this.kSpringDamping); + + if (y != this.y) { + // ensure snapping introduces no velocity, add additional friction + this.y0 = y - (this.y - this.y0) * this.kSnapFriction; + this.y = y; + } + + if (x != this.x) { + this.x0 = x - (this.x - this.x0) * this.kSnapFriction; + this.x = x; + } + }, + + /** + * The friction function. + * + * @private + */ + friction: function (ex, ex0, coeff) { + // implicit velocity + var dp = this[ex] - this[ex0]; + // let close-to-zero collapse to zero (i.e. smaller than epsilon is considered zero) + var c = Math.abs(dp) > this.kFrictionEpsilon ? coeff : 0; + // reposition using damped velocity + this[ex] = this[ex0] + c * dp; + }, + + /** + * One unit of time for simulation. + * + * @private + */ + frame: 10, + // piece-wise constraint simulation + simulate: function (t) { + while (t >= this.frame) { + t -= this.frame; + if (!this.dragging) { + this.constrain(); + } + this.verlet(); + this.friction('y', 'y0', this.kFrictionDamping); + this.friction('x', 'x0', this.kFrictionDamping); + } + return t; + }, + + /** + * @method + * @private + */ + getDampingBoundaries: function() { + return this.haveBoundarySnapshot ? + this.boundarySnapshot : + { + top : this.topBoundary, + bottom : this.bottomBoundary, + left : this.leftBoundary, + right : this.rightBoundary + }; + }, + + /** + * @method + * @private + */ + takeBoundarySnapshot: function () { + var s; + + if (!this.haveBoundarySnapshot) { + s = this.boundarySnapshot; + + s.top = this.topBoundary; + s.bottom = this.bottomBoundary; + s.left = this.leftBoundary; + s.right = this.rightBoundary; + + this.haveBoundarySnapshot = true; + } + }, + + /** + * @method + * @private + */ + discardBoundarySnapshot: function() { + this.haveBoundarySnapshot = false; + }, + + /** + * @fires module:enyo/ScrollMath~ScrollMath#onScrollStop + * @private + */ + animate: function () { + this.stop(); + // time tracking + var t0 = utils.perfNow(), t = 0; + // delta tracking + var x0, y0; + // animation handler + var fn = this.bindSafely(function() { + // wall-clock time + var t1 = utils.perfNow(); + // schedule next frame + this.job = animation.requestAnimationFrame(fn); + // delta from last wall clock time + var dt = t1 - t0; + // record the time for next delta + t0 = t1; + // user drags override animation + if (this.dragging) { + this.y0 = this.y = this.uy; + this.x0 = this.x = this.ux; + this.endX = this.endY = null; + } + // frame-time accumulator + // min acceptable time is 16ms (60fps) + t += Math.max(16, dt); + // prevent snapping to originally desired scroll position if we are in overscroll + if (this.isInOverScroll()) { + this.endX = null; + this.endY = null; + + this.takeBoundarySnapshot(); + } + else { + this.discardBoundarySnapshot(); + + // alternate fixed-time step strategy: + if (this.fixedTime) { + t = this.interval; + } + } + // consume some t in simulation + t = this.simulate(t); + // scroll if we have moved, otherwise the animation is stalled and we can stop + if (y0 != this.y || x0 != this.x) { + this.scroll(); + } else if (!this.dragging) { + // set final values + if (this.endX != null) { + this.x = this.x0 = this.endX; + } + if (this.endY != null) { + this.y = this.y0 = this.endY; + } + + this.stop(); + this.scroll(); + this.doScrollStop(); + + this.endX = null; + this.endY = null; + } + y0 = this.y; + x0 = this.x; + }); + this.job = animation.requestAnimationFrame(fn); + }, + + /** + * @private + */ + start: function () { + if (!this.job) { + this.doScrollStart(); + this.animate(); + } + }, + + /** + * @private + */ + stop: function (fire) { + var job = this.job; + if (job) { + this.job = animation.cancelRequestAnimationFrame(job); + } + if (fire) { + this.doScrollStop(); + + this.endX = undefined; + this.endY = undefined; + } + }, + + /** + * Adjusts the scroll position to be valid, if necessary (e.g., after the scroll contents + * have changed). + * + * @private + */ + stabilize: function (opts) { + var fire = !opts || opts.fire === undefined || opts.fire; + var y = Math.min(this.topBoundary, Math.max(this.bottomBoundary, this.y)); + var x = Math.min(this.leftBoundary, Math.max(this.rightBoundary, this.x)); + if (y != this.y || x != this.x) { + this.y = this.y0 = y; + this.x = this.x0 = x; + if (fire) { + this.doStabilize(); + } + } + }, + + /** + * @private + */ + startDrag: function (e) { + this.dragging = true; + // + this.my = e.pageY; + this.py = this.uy = this.y; + // + this.mx = e.pageX; + this.px = this.ux = this.x; + }, + + /** + * @private + */ + drag: function (e) { + var dy, dx, v, h; + if (this.dragging) { + v = this.canScrollY(); + h = this.canScrollX(); + + dy = v ? e.pageY - this.my : 0; + this.uy = dy + this.py; + // provides resistance against dragging into overscroll + this.uy = this.boundaryDamping(this.uy, this.topBoundary, this.bottomBoundary, this.kDragDamping); + // + dx = h ? e.pageX - this.mx : 0; + this.ux = dx + this.px; + // provides resistance against dragging into overscroll + this.ux = this.boundaryDamping(this.ux, this.leftBoundary, this.rightBoundary, this.kDragDamping); + // + this.start(); + return true; + } + }, + + /** + * @private + */ + dragDrop: function () { + if (this.dragging && !window.PalmSystem) { + var kSimulatedFlickScalar = 0.5; + this.y = this.uy; + this.y0 = this.y - (this.y - this.y0) * kSimulatedFlickScalar; + this.x = this.ux; + this.x0 = this.x - (this.x - this.x0) * kSimulatedFlickScalar; + } + this.dragFinish(); + }, + + /** + * @private + */ + dragFinish: function () { + this.dragging = false; + }, + + /** + * @private + */ + flick: function (e) { + var v; + if (this.canScrollY()) { + v = e.yVelocity > 0 ? Math.min(this.kMaxFlick, e.yVelocity) : Math.max(-this.kMaxFlick, e.yVelocity); + this.y = this.y0 + v * this.kFlickScalar; + } + if (this.canScrollX()) { + v = e.xVelocity > 0 ? Math.min(this.kMaxFlick, e.xVelocity) : Math.max(-this.kMaxFlick, e.xVelocity); + this.x = this.x0 + v * this.kFlickScalar; + } + this.start(); + }, + + /** + * TODO: Refine and test newMousewheel, remove this + * @private + */ + mousewheel: function (e) { + var dy = this.vertical ? e.wheelDeltaY || (!e.wheelDeltaX ? e.wheelDelta : 0) : 0, + dx = this.horizontal ? e.wheelDeltaX : 0, + shouldScroll = false; + if ((dy > 0 && this.y < this.topBoundary) || (dy < 0 && this.y > this.bottomBoundary)) { + this.y = this.y0 = this.y0 + dy; + shouldScroll = true; + } + if ((dx > 0 && this.x < this.leftBoundary) || (dx < 0 && this.x > this.rightBoundary)) { + this.x = this.x0 = this.x0 + dx; + shouldScroll = true; + } + this.stop(!shouldScroll); + if (shouldScroll) { + this.start(); + return true; + } + }, + + /** + * @private + */ + newMousewheel: function (e, opts) { + var rtl = opts && opts.rtl, + wdY = (e.wheelDeltaY === undefined) ? e.wheelDelta : e.wheelDeltaY, + dY = wdY, + dX = rtl ? -e.wheelDeltaX : e.wheelDeltaX, + canY = this.canScrollY(), + canX = this.canScrollX(), + shouldScroll = false, + m = 2, + // TODO: Figure out whether we need to port the configurable + // max / multiplier feature from Moonstone's implementation, + // and (if so) how + // max = 100, + scr = this.isScrolling(), + ovr = this.isInOverScroll(), + refY = (scr && this.endY !== null) ? this.endY : this.y, + refX = (scr && this.endX !== null) ? this.endX : this.x, + tY = refY, + tX = refX; + + if (ovr) { + return true; + } + + // If we're getting strictly vertical mousewheel events over a scroller that + // can only move horizontally, the user is probably using a one-dimensional + // mousewheel and would like us to scroll horizontally instead + if (dY && !dX && canX && !canY) { + dX = dY; + dY = 0; + } + + if (dY && canY) { + tY = -(refY + (dY * m)); + shouldScroll = true; + } + if (dX && canX) { + tX = -(refX + (dX * m)); + shouldScroll = true; + } + + if (shouldScroll) { + this.scrollTo(tX, tY, {allowOverScroll: true}); + return true; + } + }, + + /** + * @fires module:enyo/ScrollMath~ScrollMath#onScroll + * @private + */ + scroll: function () { + this.doScroll(); + }, + + // NOTE: Yip/Orvell method for determining scroller instantaneous velocity + // FIXME: incorrect if called when scroller is in overscroll region + // because does not account for additional overscroll damping. + + /** + * Scrolls to the specified position. + * + * @param {Number} x - The `x` position in pixels. + * @param {Number} y - The `y` position in pixels. + * @param {Object} opts - TODO: Document. When behavior == 'instant', we skip animation. + * @private + */ + scrollTo: function (x, y, opts) { + var animate = !opts || opts.behavior !== 'instant', + xSnap = this.xSnapIncrement, + ySnap = this.ySnapIncrement, + allowOverScroll = opts && opts.allowOverScroll, + maxX = Math.abs(Math.min(0, this.rightBoundary)), + maxY = Math.abs(Math.min(0, this.bottomBoundary)); + + if (typeof xSnap === 'number') { + x = xSnap * Math.round(x / xSnap); + } + + if (typeof ySnap === 'number') { + y = ySnap * Math.round(y / ySnap); + } + + if (!animate || !allowOverScroll) { + x = Math.max(0, Math.min(x, maxX)); + y = Math.max(0, Math.min(y, maxY)); + } + + if (-x == this.x && -y == this.y) return; + + if (!animate) { + this.doScrollStart(); + this.setScrollX(-x); + this.setScrollY(-y); + this.doScroll(); + this.doScrollStop(); + } + else { + if (y !== null) { + this.endY = -y; + this.y = this.y0 - (y + this.y0) * (1 - this.kFrictionDamping); + } + if (x !== null) { + this.endX = -x; + this.x = this.x0 - (x + this.x0) * (1 - this.kFrictionDamping); + } + this.start(); + } + }, + + /** + * Sets the scroll position along the x-axis. + * + * @param {Number} x - The x-axis scroll position in pixels. + * @method + * @private + */ + setScrollX: function (x) { + this.x = this.x0 = x; + }, + + /** + * Sets the scroll position along the y-axis. + * + * @param {Number} y - The y-axis scroll position in pixels. + * @method + * @private + */ + setScrollY: function (y) { + this.y = this.y0 = y; + }, + + /** + * Sets the scroll position; defaults to setting this position along the y-axis. + * + * @param {Number} pos - The scroll position in pixels. + * @method + * @private + */ + setScrollPosition: function (pos) { + this.setScrollY(pos); + }, + + canScrollX: function() { + return this.horizontal && this.rightBoundary < 0; + }, + + canScrollY: function() { + return this.vertical && this.bottomBoundary < 0; + }, + + + /** + * Determines whether or not the [scroller]{@link module:enyo/Scroller~Scroller} is actively moving. + * + * @return {Boolean} `true` if actively moving; otherwise, `false`. + * @private + */ + isScrolling: function () { + return Boolean(this.job); + }, + + /** + * Determines whether or not the [scroller]{@link module:enyo/Scroller~Scroller} is in overscroll. + * + * @return {Boolean} `true` if in overscroll; otherwise, `false`. + * @private + */ + isInOverScroll: function () { + return this.job && (this.x > this.leftBoundary || this.x < this.rightBoundary || + this.y > this.topBoundary || this.y < this.bottomBoundary); + } +}); + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./platform':'enyo/platform','./animation':'enyo/animation','./Component':'enyo/Component'}],'enyo/master':[function (module,exports,global,require,request){ +require('enyo'); + +var + utils = require('./utils'); +var + Component = require('./Component'), + Signals = require('./Signals'); + +/** +* Default owner assigned to ownerless [UiComponents]{@link module:enyo/UiComponent~UiComponent}, +* to allow such UiComponents to be notified of important system events like window resize. +* +* NOTE: Ownerless [UiComponents]{@link module:enyo/UiComponent~UiComponent} will not be garbage collected unless +* explicitly destroyed, as they will be referenced by `master`. +* +* @module enyo/master +* @private +*/ +var master = module.exports = new Component({ + name: 'master', + notInstanceOwner: true, + eventFlags: {showingOnly: true}, // don't waterfall these events into hidden controls + getId: function () { + return ''; + }, + isDescendantOf: utils.nop, + bubble: function (nom, event) { + //enyo.log('master event: ' + nom); + if (nom == 'onresize') { + // Resize is special; waterfall this message. + // This works because master is a Component, so it waterfalls + // to its owned Components (i.e., master has no children). + master.waterfallDown('onresize', this.eventFlags); + master.waterfallDown('onpostresize', this.eventFlags); + } else { + // All other top-level events are sent only to interested Signal + // receivers. + Signals.send(nom, event); + } + } +}); + +},{'./utils':'enyo/utils','./Component':'enyo/Component','./Signals':'enyo/Signals'}],'enyo/Controller':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Controller~Controller} kind. +* @module enyo/Controller +*/ + +var + kind = require('./kind'), + utils = require('./utils'); + +var + MultipleDispatchComponent = require('./MultipleDispatchComponent'); + +/** +* {@link module:enyo/Controller~Controller} is the base [kind]{@glossary kind} for all +* controllers in Enyo. An abstract kind, `enyo.Controller` is a +* [delegate]{@glossary delegate}/[component]{@link module:enyo/Component~Component} that +* is designed to be a proxy for information. +* +* @class Controller +* @extends module:enyo/MultipleDispatchComponent~MultipleDispatchComponent +* @public +*/ +module.exports = kind( + /** @lends module:enyo/Controller~Controller.prototype */ { + + name: 'enyo.Controller', + + /** + * @private + */ + kind: MultipleDispatchComponent, + + /** + * Set this flag to `true` to make this [controller]{@link module:enyo/Controller~Controller} + * available globally, when instanced. When set to `true`, even the + * [owner]{@link module:enyo/Component~Component#owner} (if any) cannot + * [destroy]{@link module:enyo/Component~Component#destroy} it. + * + * @type {Boolean} + * @default false + * @public + */ + global: false, + + /** + * The default source of information for all instances of {@link module:enyo/Controller~Controller} + * and its [subkinds]{@glossary subkind}. In some cases, this will be a + * [computed property]{@link module:enyo/ComputedSupport} to facilitate overloading. + * It may contain any type of data. + * + * @type {*} + * @default null + * @public + */ + data: null, + + /** + * @method + * @private + */ + constructor: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + // don't attempt to set a controller globally without a name + if (this.global && this.name) { + utils.setPath.call(global, this.name, this); + } + }; + }), + _isController: true +}); + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./MultipleDispatchComponent':'enyo/MultipleDispatchComponent'}],'enyo/dispatcher':[function (module,exports,global,require,request){ +/** +* Contains dispatcher methods +* @module enyo/dispatcher +* @private +*/ +require('enyo'); + +var + logger = require('./logger'), + master = require('./master'), + utils = require('./utils'), + platform = require('./platform'); + +var + Dom = require('./dom'); + +/** + * An [object]{@glossary Object} describing the the last known coordinates of the cursor or + * user-interaction point in touch environments. + * + * @typedef {Object} module:enyo/dispatcher~CursorCoordinates + * @property {Number} clientX - The horizontal coordinate within the application's client area. + * @property {Number} clientY - The vertical coordinate within the application's client area. + * @property {Number} pageX - The X coordinate of the cursor relative to the viewport, including any + * scroll offset. + * @property {Number} pageY - The Y coordinate of the cursor relative to the viewport, including any + * scroll offset. + * @property {Number} screenX - The X coordinate of the cursor relative to the screen, not including + * any scroll offset. + * @property {Number} screenY - The Y coordinate of the cursor relative to the screen, not including + * any scroll offset. + */ + +/** +* @private +*/ + +/** +* @private +*/ +var dispatcher = module.exports = dispatcher = { + + $: {}, + + /** + * These events come from document + * + * @private + */ + events: ["mousedown", "mouseup", "mouseover", "mouseout", "mousemove", "mousewheel", + "click", "dblclick", "change", "keydown", "keyup", "keypress", "input", + "paste", "copy", "cut", "webkitTransitionEnd", "transitionend", "webkitAnimationEnd", "animationend", + "webkitAnimationStart", "animationstart", "webkitAnimationIteration", "animationiteration"], + + /** + * These events come from window + * + * @private + */ + windowEvents: ["resize", "load", "unload", "message", "hashchange", "popstate", "focus", "blur"], + + /** + * Feature plugins (aka filters) + * + * @private + */ + features: [], + + /** + * @private + */ + connect: function() { + var d = dispatcher, i, n; + for (i=0; (n=d.events[i]); i++) { + d.listen(document, n); + } + for (i=0; (n=d.windowEvents[i]); i++) { + // Chrome Packaged Apps don't like "unload" + if(n === "unload" && + (typeof global.chrome === "object") && + global.chrome.app) { + continue; + } + + d.listen(window, n); + } + }, + + /** + * @private + */ + listen: function(inListener, inEventName, inHandler) { + if (inListener.addEventListener) { + this.listen = function(inListener, inEventName, inHandler) { + inListener.addEventListener(inEventName, inHandler || dispatch, false); + }; + } else { + //enyo.log("IE8 COMPAT: using 'attachEvent'"); + this.listen = function(inListener, inEvent, inHandler) { + inListener.attachEvent("on" + inEvent, function(e) { + e.target = e.srcElement; + if (!e.preventDefault) { + e.preventDefault = this.iePreventDefault; + } + return (inHandler || dispatch)(e); + }); + }; + } + this.listen(inListener, inEventName, inHandler); + }, + + /** + * @private + */ + stopListening: function(inListener, inEventName, inHandler) { + if (inListener.addEventListener) { + this.stopListening = function(inListener, inEventName, inHandler) { + inListener.removeEventListener(inEventName, inHandler || dispatch, false); + }; + } else { + //enyo.log("IE8 COMPAT: using 'detachEvent'"); + this.stopListening = function(inListener, inEvent, inHandler) { + inListener.detachEvent("on" + inEvent, inHandler || dispatch); + }; + } + this.stopListening(inListener, inEventName, inHandler); + }, + + /** + * Fires an event for Enyo to listen for. + * + * @private + */ + dispatch: function(e) { + // Find the control who maps to e.target, or the first control that maps to an ancestor of e.target. + var c = this.findDispatchTarget(e.target) || this.findDefaultTarget(); + // Cache the original target + e.dispatchTarget = c; + // support pluggable features return true to abort immediately or set e.preventDispatch to avoid processing. + for (var i=0, fn; (fn=this.features[i]); i++) { + if (fn.call(this, e) === true) { + return; + } + } + if (c && !e.preventDispatch) { + return this.dispatchBubble(e, c); + } + }, + + /** + * Takes an event target and finds the corresponding Enyo control. + * + * @private + */ + findDispatchTarget: function(inNode) { + var t, n = inNode; + // FIXME: Mozilla: try/catch is here to squelch "Permission denied to access property xxx from a non-chrome context" + // which appears to happen for scrollbar nodes in particular. It's unclear why those nodes are valid targets if + // it is illegal to interrogate them. Would like to trap the bad nodes explicitly rather than using an exception block. + try { + while (n) { + if ((t = this.$[n.id])) { + // there could be multiple nodes with this id, the relevant node for this event is n + // we don't push this directly to t.node because sometimes we are just asking what + // the target 'would be' (aka, calling findDispatchTarget from handleMouseOverOut) + t.eventNode = n; + break; + } + n = n.parentNode; + } + } catch(x) { + logger.log(x, n); + } + return t; + }, + + /** + * Returns the default Enyo control for events. + * + * @private + */ + findDefaultTarget: function() { + return master; + }, + + /** + * @private + */ + dispatchBubble: function(e, c) { + var type = e.type; + type = e.customEvent ? type : "on" + type; + return c.bubble(type, e, c); + } +}; + +/** +* Called in the context of an event. +* +* @private +*/ +dispatcher.iePreventDefault = function() { + try { + this.returnValue = false; + } + catch(e) { + // do nothing + } +}; + +/** +* @private +*/ +function dispatch (inEvent) { + return dispatcher.dispatch(inEvent); +} + +/** +* @private +*/ +dispatcher.bubble = function(inEvent) { + // '|| window.event' clause needed for IE8 + var e = inEvent || global.event; + if (e) { + // We depend on e.target existing for event tracking and dispatching. + if (!e.target) { + e.target = e.srcElement; + } + dispatcher.dispatch(e); + } +}; + +// This string is set on event handlers attributes for DOM elements that +// don't normally bubble (like onscroll) so that they can participate in the +// Enyo event system. +dispatcher.bubbler = "enyo.bubble(arguments[0])"; + +// The code below helps make Enyo compatible with Google Packaged Apps +// Content Security Policy(http://developer.chrome.com/extensions/contentSecurityPolicy.html), +// which, among other things, forbids the use of inline scripts. +// We replace online scripting with equivalent means, leaving enyo.bubbler +// for backward compatibility. +(function() { + var bubbleUp = function() { + dispatcher.bubble(arguments[0]); + }; + + /** + * Makes given events bubble on a specified Enyo control. + * + * @private + */ + dispatcher.makeBubble = function() { + var args = Array.prototype.slice.call(arguments, 0), + control = args.shift(); + + if((typeof control === "object") && (typeof control.hasNode === "function")) { + utils.forEach(args, function(event) { + if(this.hasNode()) { + dispatcher.listen(this.node, event, bubbleUp); + } + }, control); + } + }; + + /** + * Removes the event listening and bubbling initiated by + * [enyo.makeBubble()]{@link enyo.makeBubble} on a specific control. + * + * @private + */ + dispatcher.unmakeBubble = function() { + var args = Array.prototype.slice.call(arguments, 0), + control = args.shift(); + + if((typeof control === "object") && (typeof control.hasNode === "function")) { + utils.forEach(args, function(event) { + if(this.hasNode()) { + dispatcher.stopListening(this.node, event, bubbleUp); + } + }, control); + } + }; +})(); + +/** +* @private +*/ +// FIXME: we need to create and initialize dispatcher someplace else to allow overrides +Dom.requiresWindow(dispatcher.connect); + +/** +* Generates a tapped event for a raw-click event. +* +* @private +*/ +dispatcher.features.push( + function (e) { + if ("click" === e.type) { + if (e.clientX === 0 && e.clientY === 0 && !e.detail) { + // this allows the click to dispatch as well + // but note the tap event will fire first + var cp = utils.clone(e); + cp.type = "tap"; + cp.preventDefault = utils.nop; + dispatcher.dispatch(cp); + } + } + } +); + +/** +* Instead of having multiple `features` pushed and handled in separate methods +* for these events, we handle them uniformly here to expose the last known +* interaction coordinates as accurately as possible. +* +* @private +*/ +var _xy = {}; +dispatcher.features.push( + function (e) { + if ( + (e.type == "mousemove") || + (e.type == "tap") || + (e.type == "click") || + (e.type == "touchmove") + ) { + _xy.clientX = e.clientX; + _xy.clientY = e.clientY; + // note only ie8 does not support pageX/pageY + _xy.pageX = e.pageX; + _xy.pageY = e.pageY; + // note ie8 and opera report these values incorrectly + _xy.screenX = e.screenX; + _xy.screenY = e.screenY; + } + } +); + +/** +* Retrieves the last known coordinates of the cursor or user-interaction point +* in touch environments. Returns an immutable object with the `clientX`, +* `clientY`, `pageX`, `pageY`, `screenX`, and `screenY` properties. It is +* important to note that IE8 and Opera have improper reporting for the +* `screenX` and `screenY` properties (they both use CSS pixels as opposed to +* device pixels) and IE8 has no support for the `pageX` and `pageY` properties, +* so they are facaded. +* +* @returns {module:enyo/dispatcher~CursorCoordinates} An [object]{@glossary Object} describing the +* the last known coordinates of the cursor or user-interaction point in touch environments. +* @public +*/ +dispatcher.getPosition = function () { + var p = utils.clone(_xy); + // if we are in ie8 we facade the _pageX, pageY_ properties + if (platform.ie < 9) { + var d = (document.documentElement || document.body.parentNode || document.body); + p.pageX = (p.clientX + d.scrollLeft); + p.pageY = (p.clientY + d.scrollTop); + } + return p; +}; + + +/** +* Key mapping feature: Adds a `keySymbol` property to key [events]{@glossary event}, +* based on a global key mapping. Use +* [enyo.dispatcher.registerKeyMap()]{@link enyo.dispatcher.registerKeyMap} to add +* keyCode-to-keySymbol mappings via a simple hash. This method may be called +* multiple times from different libraries to mix different maps into the global +* mapping table; if conflicts arise, the last-in wins. +* +* ``` +* enyo.dispatcher.registerKeyMap({ +* 415 : 'play', +* 413 : 'stop', +* 19 : 'pause', +* 412 : 'rewind', +* 417 : 'fastforward' +* }); +* ``` +* +* @private +*/ +dispatcher.features.push(function(e) { + if ((e.type === 'keydown') || (e.type === 'keyup') || (e.type === 'keypress')) { + e.keySymbol = this.keyMap[e.keyCode]; + // Dispatch key events to be sent via Signals + var c = this.findDefaultTarget(); + if (e.dispatchTarget !== c) { + this.dispatchBubble(e, c); + } + } +}); + +utils.mixin(dispatcher, { + keyMap: {}, + registerKeyMap: function(map) { + utils.mixin(this.keyMap, map); + } +}); + + +/** +* Event modal capture feature. Capture events to a specific control via +* [enyo.dispatcher.capture(inControl, inShouldForward)]{@linkcode enyo.dispatcher.capture}; +* release events via [enyo.dispatcher.release()]{@link enyo.dispatcher.release}. +* +* @private +*/ +dispatcher.features.push(function(e) { + if (this.captureTarget) { + var c = e.dispatchTarget; + var eventName = (e.customEvent ? '' : 'on') + e.type; + var handlerName = this.captureEvents[eventName]; + var handlerScope = this.captureHandlerScope || this.captureTarget; + var handler = handlerName && handlerScope[handlerName]; + var shouldCapture = handler && !(c && c.isDescendantOf && c.isDescendantOf(this.captureTarget)); + if (shouldCapture) { + var c1 = e.captureTarget = this.captureTarget; + // NOTE: We do not want releasing capture while an event is being processed to alter + // the way the event propagates. Therefore decide if the event should forward + // before the capture target receives the event (since it may release capture). + e.preventDispatch = handler && handler.apply(handlerScope, [c1, e]) && !this.autoForwardEvents[e.type]; + } + } +}); + +// +// NOTE: This object is a plug-in; these methods should +// be called on `enyo.dispatcher`, and not on the plug-in itself. +// +utils.mixin(dispatcher, { + + /** + * @private + */ + autoForwardEvents: {leave: 1, resize: 1}, + + /** + * @private + */ + captures: [], + + /** + * Captures [events]{@glossary event} for `inTarget`, where `inEvents` is specified as a + * hash of event names mapped to callback handler names to be called on `inTarget` (or, + * optionally, `inScope`). The callback is called when any of the captured events are + * dispatched outside of the capturing control. Returning `true` from the callback stops + * dispatch of the event to the original `dispatchTarget`. + * + * @private + */ + capture: function(inTarget, inEvents, inScope) { + var info = {target: inTarget, events: inEvents, scope: inScope}; + this.captures.push(info); + this.setCaptureInfo(info); + }, + + /** + * Removes the specified target from the capture list. + * + * @private + */ + release: function(inTarget) { + for (var i = this.captures.length - 1; i >= 0; i--) { + if (this.captures[i].target === inTarget) { + this.captures.splice(i,1); + this.setCaptureInfo(this.captures[this.captures.length-1]); + break; + } + } + }, + + /** + * Sets the information for a captured {@glossary event}. + * + * @private + */ + setCaptureInfo: function(inInfo) { + this.captureTarget = inInfo && inInfo.target; + this.captureEvents = inInfo && inInfo.events; + this.captureHandlerScope = inInfo && inInfo.scope; + } +}); + + +(function () { + /** + * Dispatcher preview feature + * + * Allows {@link module:enyo/Control~Control} ancestors of the {@glossary event} target + * a chance (eldest first) to react by implementing `previewDomEvent`. + * + * @private + */ + var fn = 'previewDomEvent'; + var preview = + /** @lends enyo.dispatcher.features */ { + + /** + * @private + */ + feature: function(e) { + preview.dispatch(e, e.dispatchTarget); + }, + + /** + * @returns {(Boolean|undefined)} Handlers return `true` to abort preview and prevent default + * event processing. + * + * @private + */ + dispatch: function(evt, control) { + var i, l, + lineage = this.buildLineage(control); + for (i=0; (l=lineage[i]); i++) { + if (l[fn] && l[fn](evt) === true) { + evt.preventDispatch = true; + return; + } + } + }, + + /** + * We ascend, making a list of Enyo [controls]{@link module:enyo/Control~Control}. + * + * Note that a control is considered to be its own ancestor. + * + * @private + */ + buildLineage: function(control) { + var lineage = [], + c = control; + while (c) { + lineage.unshift(c); + c = c.parent; + } + return lineage; + } + }; + + dispatcher.features.push(preview.feature); +})(); + +},{'./logger':'enyo/logger','./master':'enyo/master','./utils':'enyo/utils','./platform':'enyo/platform','./dom':'enyo/dom'}],'enyo/AccessibilitySupport':[function (module,exports,global,require,request){ +/** +* Mixin for adding WAI-ARIA attributes to controls +* +* @module enyo/AccessibilitySupport +*/ + +var + dispatcher = require('../dispatcher'), + kind = require('../kind'), + platform = require('../platform'), + utils = require('../utils'); + +var defaultObservers = [ + {from: 'accessibilityDisabled', method: function () { + this.setAriaAttribute('aria-hidden', this.accessibilityDisabled ? 'true' : null); + }}, + {from: 'accessibilityLive', method: function () { + var live = this.accessibilityLive === true && 'assertive' || this.accessibilityLive || null; + this.setAriaAttribute('aria-live', live); + }}, + {path: ['accessibilityAlert', 'accessibilityRole'], method: function () { + var role = this.accessibilityAlert && 'alert' || this.accessibilityRole || null; + this.setAriaAttribute('role', role); + }}, + {path: ['content', 'accessibilityHint', 'accessibilityLabel', 'tabIndex'], method: function () { + var focusable = this.accessibilityLabel || this.content || this.accessibilityHint || false, + prefix = this.accessibilityLabel || this.content || null, + label = this.accessibilityHint && prefix && (prefix + ' ' + this.accessibilityHint) || + this.accessibilityHint || + this.accessibilityLabel || + null; + + this.setAriaAttribute('aria-label', label); + + // A truthy or zero tabindex will be set directly + if (this.tabIndex || this.tabIndex === 0) { + this.setAriaAttribute('tabindex', this.tabIndex); + } + // The webOS browser will only read nodes with a non-null tabindex so if the node has + // readable content, make it programmably focusable. + else if (focusable && this.tabIndex === undefined && platform.webos) { + this.setAriaAttribute('tabindex', -1); + } + // Otherwise, remove it + else { + this.setAriaAttribute('tabindex', null); + } + }} +]; + +/** +* Prevents browser-initiated scrolling contained controls into view when those controls are +* explicitly focus()'ed. +* +* @private +*/ +function preventScroll (node) { + if (node) { + dispatcher.listen(node, 'scroll', function () { + node.scrollTop = 0; + node.scrollLeft = 0; + }); + } +} + +function updateAriaAttributes (all) { + var i, l, obs; + + for (i = 0, l = this._ariaObservers.length; i < l; i++) { + obs = this._ariaObservers[i]; + if ((all || obs.pending) && obs.method) { + obs.method(); + obs.pending = false; + } + } +} + +function registerAriaUpdate (obj) { + var fn; + if (!obj.pending) { + obj.pending = true; + fn = this.bindSafely(updateAriaAttributes); + if (!this.accessibilityDefer) { + fn(); + } else { + this.startJob('updateAriaAttributes', fn, 16); + } + } +} + +function toAriaAttribute (from, to) { + var value = this[from]; + this.setAriaAttribute(to, value === undefined ? null : value); +} + +function staticToAriaAttribute (to, value) { + this.setAriaAttribute(to, value); +} + +function initAriaObservers (control) { + var conf = control._ariaObservers, + i, l, fn; + + control._ariaObservers = []; + for (i = 0, l = defaultObservers.length; i < l; i++) { + initAriaObserver(control, defaultObservers[i]); + } + if (conf) { + for (i = 0, l = conf.length; i < l; i++) { + initAriaObserver(control, conf[i]); + } + } + + // setup disabled observer and kickoff first run of observers + fn = updateAriaAttributes.bind(control, true); + control.addObserver('accessibilityDisabled', fn); + fn(); +} + +function initAriaObserver (control, c) { + var + // path can either source from 'path' or 'from' (for binding-style configs) + path = c.path || c.from, + + // method is either: + // 'method', if it exists, or + // staticToAriaAttribute if 'to' and 'value' exist - static binding-style config, or + // toAriaAttribute if a 'to' path exists - binding-style config + method = c.method && control.bindSafely(c.method) || + !path && c.to && c.value !== undefined && control.bindSafely(staticToAriaAttribute, c.to, c.value) || + c.to && control.bindSafely(toAriaAttribute, path, c.to) || + null, + + // import the relevant and pre-validated parts into the instance-level config + config = { + path: path, + method: method, + pending: false + }, + + // pre-bind the register method as it's used multiple times when 'path' is an array + fn = registerAriaUpdate.bind(control, config), + + // iterator + l; + + control._ariaObservers.push(config); + if (utils.isArray(path)) { + for (l = path.length - 1; l >= 0; --l) { + control.addObserver(path[l], fn); + } + } + else if (path) { + control.addObserver(path, fn); + } +} + +/** +* @mixin +*/ +var AccessibilitySupport = { + + /** + * @private + */ + name: 'enyo.AccessibilitySupport', + + /** + * AccessibilityLabel is used for accessibility voice readout. + * If accessibilityLabel is set, screen reader reads the label when control is focused. + * + * @type {String} + * @default '' + * @public + */ + accessibilityLabel: '', + + /** + * AccessibilityHint is used for additional information of control. + * If accessibilityHint is set and content exists, screen reader + * reads accessibilityHint with content when control is focused. + * + * @type {String} + * @default '' + * @public + */ + accessibilityHint: '', + + /** + * The `role` of the control. May be superceded by a truthy `accessibilityAlert` value. + * + * @type {String} + * @default '' + * @public + */ + accessibilityRole: '', + + /** + * AccessibilityAlert is for alert message or page description. + * If accessibilityAlert is true, aria role will be set to "alert" and + * screen reader will automatically reads content or accessibilityLabel + * regardless focus. + * Note that if you use accessibilityAlert, previous role will be + * replaced with "alert" role. + * + * Range: [`true`, `false`] + * - true: screen reader automatically reads label regardless focus. + * - false: screen reader reads label with focus. + * + * @type {Boolean} + * @default false + * @public + */ + accessibilityAlert: false, + + /** + * AccessibilityLive is for dynamic content which updates without a page reload. + * If AccessibilityLive is true, screen reader will read content or accessibilityLabel + * when it changed. + * + * Range: [`true`, `false`] + * - true: screen reader reads content when it changed. + * - false: screen reader reads content with focus. + * + * @type {Boolean} + * @default false + * @public + */ + accessibilityLive: false, + + /** + * AccessibilityDisabled prevents VoiceReadout. + * If accessibilityDisabled is true, screen reader doesn't read any label for the control. + * Note that this is not working on HTML form elements which can get focus without tabindex. + * + * Range: [`true`, `false`] + * + * @type {Boolean} + * @default false + * @public + */ + accessibilityDisabled: false, + + /** + * When true, `onscroll` events will be observed and scrolling prevented by resetting the + * `scrollTop` and `scrollLeft` of the node. This prevents inadvertent layout issues introduced + * by the browser scrolling contained controls into view when `focus()`'ed. + * + * @type {Boolean} + * @default false + * @public + */ + accessibilityPreventScroll: false, + + /** + * Sets the `tabindex` of the control. When `undefined` on webOS, it will be set to -1 to enable + * screen reading. A value of `null` (or `undefined` on non-webOS) ensures that no `tabindex` is + * set. + * + * @type {Number} + * @default undefined + * @public + */ + + /** + * @method + * @private + */ + create: kind.inherit(function (sup) { + return function (props) { + sup.apply(this, arguments); + initAriaObservers(this); + }; + }), + + /** + * If accessibilityDisabled is `false`, sets the node attribute. Otherwise, removes it. + * + * @param {String} name Attribute name + * @param {String} value Attribute value + * @public + */ + setAriaAttribute: function (name, value) { + // if the control is disabled, don't set any aria properties except aria-hidden + if (this.accessibilityDisabled && name != 'aria-hidden') { + value = null; + } + // if the value is defined and non-null, cast it to a String + else if (value !== undefined && value !== null) { + value = String(value); + } + this.setAttribute(name, value); + }, + + /** + * @private + */ + rendered: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + if (this.accessibilityPreventScroll) { + preventScroll(this.hasNode()); + } + }; + }) +}; + +var sup = kind.concatHandler; +kind.concatHandler = function (ctor, props, instance) { + sup.call(this, ctor, props, instance); + + var proto = ctor.prototype || ctor, + ariaObservers = proto._ariaObservers && proto._ariaObservers.slice(), + incoming = props.ariaObservers; + + if (incoming && incoming instanceof Array) { + if (ariaObservers) { + ariaObservers.push.apply(ariaObservers, incoming); + } else { + ariaObservers = incoming.slice(); + } + } + + proto._ariaObservers = ariaObservers; +}; + +module.exports = AccessibilitySupport; +},{'../dispatcher':'enyo/dispatcher','../kind':'enyo/kind','../platform':'enyo/platform','../utils':'enyo/utils'}],'enyo/Control/fullscreen':[function (module,exports,global,require,request){ +var + dispatcher = require('../dispatcher'), + utils = require('../utils'), + ready = require('../ready'), + Signals = require('../Signals'); + +/** +* Normalizes and provides fullscreen support for [controls]{@link module:enyo/Control~Control}, +* based on the [fullscreen]{@glossary fullscreen} API. +* +* @module enyo/Control/fullscreen +* @public +*/ +module.exports = function (Control) { + var floatingLayer = Control.floatingLayer; + var fullscreen = { + + /** + * Reference to the current fullscreen [control]{@link module:enyo/Control~Control}. + * + * @private + */ + fullscreenControl: null, + + /** + * Reference to the current fullscreen element (fallback for platforms + * without native support). + * + * @private + */ + fullscreenElement: null, + + /** + * Reference to that [control]{@link module:enyo/Control~Control} that requested fullscreen. + * + * @private + */ + requestor: null, + + /** + * Native accessor used to get reference to the current fullscreen element. + * + * @private + */ + elementAccessor: + ('fullscreenElement' in document) ? 'fullscreenElement' : + ('mozFullScreenElement' in document) ? 'mozFullScreenElement' : + ('webkitFullscreenElement' in document) ? 'webkitFullscreenElement' : + null, + + /** + * Native accessor used to request fullscreen. + * + * @private + */ + requestAccessor: + ('requestFullscreen' in document.documentElement) ? 'requestFullscreen' : + ('mozRequestFullScreen' in document.documentElement) ? 'mozRequestFullScreen' : + ('webkitRequestFullscreen' in document.documentElement) ? 'webkitRequestFullscreen' : + null, + + /** + * Native accessor used to cancel fullscreen. + * + * @private + */ + cancelAccessor: + ('cancelFullScreen' in document) ? 'cancelFullScreen' : + ('mozCancelFullScreen' in document) ? 'mozCancelFullScreen' : + ('webkitCancelFullScreen' in document) ? 'webkitCancelFullScreen' : + null, + + /** + * Determines whether the platform supports the [fullscreen]{@glossary fullscreen} API. + * + * @returns {Boolean} Returns `true` if platform supports all of the + * [fullscreen]{@glossary fullscreen} API, `false` otherwise. + * @public + */ + nativeSupport: function() { + return (this.elementAccessor !== null && this.requestAccessor !== null && this.cancelAccessor !== null); + }, + + /** + * Normalizes `getFullscreenElement()`. + * + * @public + */ + getFullscreenElement: function() { + return (this.nativeSupport()) ? document[this.elementAccessor] : this.fullscreenElement; + }, + + /** + * Returns current fullscreen [control]{@link module:enyo/Control~Control}. + * + * @public + */ + getFullscreenControl: function() { + return this.fullscreenControl; + }, + + /** + * Normalizes `requestFullscreen()`. + * + * @public + */ + requestFullscreen: function(ctl) { + if (this.getFullscreenControl() || !(ctl.hasNode())) { + return false; + } + + this.requestor = ctl; + + // Only use native request if platform supports all of the API + if (this.nativeSupport()) { + ctl.hasNode()[this.requestAccessor](); + } else { + this.fallbackRequestFullscreen(); + } + + return true; + }, + + /** + * Normalizes `cancelFullscreen()`. + * + * @public + */ + cancelFullscreen: function() { + if (this.nativeSupport()) { + document[this.cancelAccessor](); + } else { + this.fallbackCancelFullscreen(); + } + }, + + /** + * Fallback support for setting fullscreen element (done by browser on platforms with + * native support). + * + * @private + */ + setFullscreenElement: function(node) { + this.fullscreenElement = node; + }, + + /** + * Sets current fullscreen [control]{@link module:enyo/Control~Control}. + * + * @private + */ + setFullscreenControl: function(ctl) { + this.fullscreenControl = ctl; + }, + + /** + * Fallback fullscreen request for platforms without fullscreen support. + * + * @private + */ + fallbackRequestFullscreen: function() { + var control = this.requestor; + + if (!control) { + return; + } + + // Get before node to allow us to exit floating layer to the proper position + control.prevAddBefore = control.parent.controlAtIndex(control.indexInContainer() + 1); + + // Render floating layer if we need to + if (!floatingLayer.hasNode()) { + floatingLayer.render(); + } + + control.addClass('enyo-fullscreen'); + control.appendNodeToParent(floatingLayer.hasNode()); + control.resize(); + + this.setFullscreenControl(control); + this.setFullscreenElement(control.hasNode()); + }, + + /** + * Fallback cancel fullscreen for platforms without fullscreen support. + * + * @private + */ + fallbackCancelFullscreen: function() { + var control = this.fullscreenControl, + beforeNode, + parentNode + ; + + if (!control) { + return; + } + + // Find beforeNode based on _this.addBefore_ and _this.prevAddBefore_ + beforeNode = (control.prevAddBefore) ? control.prevAddBefore.hasNode() : null; + parentNode = control.parent.hasNode(); + control.prevAddBefore = null; + + control.removeClass('enyo-fullscreen'); + + if (!beforeNode) { + control.appendNodeToParent(parentNode); + } else { + control.insertNodeInParent(parentNode, beforeNode); + } + + control.resize(); + + this.setFullscreenControl(null); + this.setFullscreenElement(null); + }, + + /** + * Listens for fullscreen change {@glossary event} and broadcasts it as a + * normalized event. + * + * @private + */ + detectFullscreenChangeEvent: function() { + this.setFullscreenControl(this.requestor); + this.requestor = null; + + // Broadcast change + Signals.send('onFullscreenChange'); + } + }; + + /** + * Normalizes platform-specific fullscreen change [events]{@glossary event}. + * + * @private + */ + ready(function() { + // no need for IE8 fallback, since it won't ever send this event + if (document.addEventListener) { + document.addEventListener('webkitfullscreenchange', utils.bind(fullscreen, 'detectFullscreenChangeEvent'), false); + document.addEventListener('mozfullscreenchange', utils.bind(fullscreen, 'detectFullscreenChangeEvent'), false); + document.addEventListener('fullscreenchange', utils.bind(fullscreen, 'detectFullscreenChangeEvent'), false); + } + }); + + /** + * If this platform doesn't have native support for fullscreen, add an escape handler to mimic + * native behavior. + */ + if(!fullscreen.nativeSupport()) { + dispatcher.features.push( + function(e) { + if (e.type === 'keydown' && e.keyCode === 27) { + fullscreen.cancelFullscreen(); + } + } + ); + } + + return fullscreen; +}; +},{'../dispatcher':'enyo/dispatcher','../utils':'enyo/utils','../ready':'enyo/ready','../Signals':'enyo/Signals'}],'enyo/Scrollable':[function (module,exports,global,require,request){ +var + kind = require('../kind'), + utils = require('../utils'), + dom = require('../dom'), + dispatcher = require('../dispatcher'); + +var + EventEmitter = require('../EventEmitter'), + ScrollMath = require('../ScrollMath'); + +var blockOptions = { + start: true, + end: true, + nearest: true, + farthest: true +}; + +function calcNodeVisibility (nodePos, nodeSize, scrollPos, scrollSize) { + return (nodePos >= scrollPos && nodePos + nodeSize <= scrollPos + scrollSize) + ? 0 + : nodePos - scrollPos > 0 + ? 1 + : nodePos - scrollPos < 0 + ? -1 + : 0; +} + +/** +* Doc +* +* @module enyo/Scrollable +* @public +*/ +module.exports = { + + /** + * @private + */ + name: 'Scrollable', + + /** + * Specifies how to horizontally scroll. Acceptable values are `'scroll'`, `'auto'`, + * `'hidden'`, and `'default'`. The precise effect of the setting is determined by the + * scroll strategy. + * + * @type {String} + * @default 'default' + * @public + */ + horizontal: 'default', + + /** + * Specifies how to vertically scroll. Acceptable values are `'scroll'`, `'auto'`, + * `'hidden'`, and `'default'`. The precise effect of the setting is determined by the + * scroll strategy. + * + * @type {String} + * @default 'default' + * @public + */ + vertical: 'default', + + /** + * The vertical scroll position. + * + * @type {Number} + * @default 0 + * @public + */ + scrollTop: 0, + + /** + * The horizontal scroll position. + * + * @type {Number} + * @default 0 + * @public + */ + scrollLeft: 0, + + /** + * Maximum height of the scroll content. + * + * @type {Number} + * @default null + * @memberof module:enyo/Scroller~Scroller.prototype + * @public + */ + maxHeight: null, + + /** + * Set to `true` to make this [scroller]{@link module:enyo/Scroller~Scroller} select a + * platform-appropriate touch-based scrolling strategy. Note that if you specify a value + * for [strategyKind]{@link module:enyo/Scroller~Scroller#strategyKind}, that will take precedence over + * this setting. + * + * @type {Boolean} + * @default false + * @public + */ + touch: true, + + /** + * TODO: Document. Based on CSSOM View spec (). + * Options: 'smooth', 'instant', maybe 'auto' + * @see {@linkplain http://dev.w3.org/csswg/cssom-view/} + * @see {@linkplain https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView} + * + * @type {String} + * @default 'smooth' + * @public + */ + behavior: 'smooth', + + /** + * TODO: Document. Based on CSSOM View spec (), but modified to add 'nearest' and + * 'farthest' to the possible values. + * Options: 'start', 'end', 'nearest', 'farthest' + * @see {@linkplain http://dev.w3.org/csswg/cssom-view/} + * @see {@linkplain https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView} + * + * @type {String} + * @default 'nearest' + * @public + */ + block: 'farthest', + + /** + * Set to `true` to display a scroll thumb in touch [scrollers]{@link module:enyo/Scroller~Scroller}. + * + * @type {Boolean} + * @default true + * @public + */ + thumb: true, + + /** + * If `true`, mouse wheel may be used to move the [scroller]{@link module:enyo/Scroller~Scroller}. + * + * @type {Boolean} + * @default true + * @public + */ + useMouseWheel: true, + + /** + * TODO: Document + * Experimental + * + * @public + */ + horizontalSnapIncrement: null, + + /** + * TODO: Document + * Experimental + * + * @public + */ + verticalSnapIncrement: null, + + /** + * TODO: Document + * Experimental + * + * @public + */ + suppressMouseEvents: false, + + scrollMath: {kind: ScrollMath}, + + pageMultiplier: 1, + + canScrollX: false, + canScrollY: false, + couldScrollX: false, + couldScrollY: false, + canScrollUp: false, + canScrollDown: false, + canScrollLeft: false, + canScrollRight: false, + + velocity: 0, + + topOffset: 0, + rightOffset: 0, + bottomOffset: 0, + leftOffset: 0, + + mixins: [EventEmitter], + + handlers: { + ondragstart: 'dragstart', + ondragfinish: 'dragfinish', + ondrag: 'drag', + onflick: 'flick', + ondown: 'down', + onmove: 'move', + onmousewheel: 'mousewheel', + onscroll: 'domScroll', + onScroll: 'scroll', + onScrollStart: 'scrollStart', + onScrollStop: 'scrollStop', + onShouldDrag: 'shouldDrag', + onStabilize: 'scroll' + }, + + events: { + onScrollStart: '', + onScroll: '', + onScrollStop: '', + onShouldDrag: '' + }, + + classes: 'enyo-scrollable enyo-fill', + + create: kind.inherit(function (sup) { + return function() { + sup.apply(this, arguments); + + var smc = [ utils.mixin(utils.clone(this.scrollMath), {name: 'scrollMath'}) ], + defProps = this.defaultProps; + + this.defaultProps = {}; + this.accessibilityPreventScroll = true; + + this.createComponents(smc, {isChrome: true, owner: this}); + if (this.scrollControls) { + this.createComponents(this.scrollControls, {isChrome: true, scroller: this}); + } + + this.defaultProps = defProps; + }; + }), + + destroy: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + if (this._suppressing) { + this._resumeMouseEvents(); + } + }; + }), + + showingChangedHandler: kind.inherit(function (sup) { + return function (sender, event) { + sup.apply(this, arguments); + if (!event.showing && this._suppressing) { + this._resumeMouseEvents(); + } + }; + }), + + rendered: kind.inherit(function (sup) { + return function() { + sup.apply(this, arguments); + this.calcScrollNode(); + this.calcBoundaries(); + }; + }), + + /** + * @private + */ + horizontalSnapIncrementChanged: function() { + this.$.scrollMath.xSnapIncrement = this.horizontalSnapIncrement; + }, + + /** + * @private + */ + verticalSnapIncrementChanged: function() { + this.$.scrollMath.ySnapIncrement = this.verticalSnapIncrement; + }, + + /** + * @private + */ + horizontalChanged: function () { + var hEnabled = (this.hEnabled = (this.horizontal !== 'hidden')); + this.$.scrollMath.horizontal = hEnabled; + this.addRemoveClass('h-enabled', hEnabled); + this.emit('scrollabilityChanged'); + }, + + /** + * @private + */ + verticalChanged: function () { + var vEnabled = (this.vEnabled = (this.vertical !== 'hidden')); + this.$.scrollMath.vertical = vEnabled; + this.addRemoveClass('v-enabled', vEnabled); + this.emit('scrollabilityChanged'); + }, + + /** + * @private + */ + scrollTopChanged: function() { + this.$.scrollMath.setScrollY(-this.scrollTop); + }, + + /** + * @private + */ + scrollLeftChanged: function() { + this.$.scrollMath.setScrollX(-this.scrollLeft); + }, + + /** + * TODO: Document. Based on CSSOM View spec () + * @see {@linkplain http://dev.w3.org/csswg/cssom-view/} + * @see {@linkplain https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView} + * + * @public + */ + scrollTo: function(x, y, opts) { + opts = opts || {}; + opts.behavior = opts.behavior || this.behavior; + this.$.scrollMath.scrollTo(x, y, opts); + }, + + /** + * TODO: Document. Based on CSSOM View spec () + * @see {@linkplain http://dev.w3.org/csswg/cssom-view/} + * @see {@linkplain https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView} + * + * @public + */ + scrollToControl: function (control, opts) { + var n = control.hasNode(); + + if (n) { + this.scrollToNode(n, opts); + } + }, + + /** + * TODO: Document. Based on CSSOM View spec () + * @see {@linkplain http://dev.w3.org/csswg/cssom-view/} + * @see {@linkplain https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView} + * + * @public + */ + scrollToNode: function(node, opts) { + var nodeBounds = dom.getAbsoluteBounds(node), + absoluteBounds = dom.getAbsoluteBounds(this.scrollNode), + scrollBounds = this.getScrollBounds(), + docWidth = document.body.offsetWidth, + block, + offsetTop, + offsetLeft, + offsetHeight, + offsetWidth, + xDir, + yDir, + x, + y; + + if (typeof opts === 'boolean') { + block = opts ? 'start' : 'end'; + } + else if (typeof opts === 'object' && blockOptions[opts.block]) { + block = opts.block; + } + else { + block = this.block; + } + + + // For the calculations below, we want the `right` bound + // to be relative to the document's right edge, not its + // left edge, so we adjust it now + nodeBounds.right = docWidth - nodeBounds.right; + absoluteBounds.right = docWidth - absoluteBounds.right; + + // Make absolute controlBounds relative to scroll position + nodeBounds.top += scrollBounds.top; + if (this.rtl) { + nodeBounds.right += scrollBounds.left; + } else { + nodeBounds.left += scrollBounds.left; + } + + offsetTop = nodeBounds.top - absoluteBounds.top; + offsetLeft = (this.rtl ? nodeBounds.right : nodeBounds.left) - (this.rtl ? absoluteBounds.right : absoluteBounds.left); + offsetHeight = nodeBounds.height; + offsetWidth = nodeBounds.width; + + // 0: currently visible, 1: right of viewport, -1: left of viewport + xDir = calcNodeVisibility(offsetLeft, offsetWidth, scrollBounds.left, scrollBounds.clientWidth); + // 0: currently visible, 1: below viewport, -1: above viewport + yDir = calcNodeVisibility(offsetTop, offsetHeight, scrollBounds.top, scrollBounds.clientHeight); + // If we're already scrolling and the direction the node is in is not the same as the direction we're scrolling, + // we need to recalculate based on where the scroller will end up, not where it is now. This is to handle the + // case where the node is currently visible but won't be once the scroller settles. + // + // NOTE: Currently setting block = 'nearest' whenever we make this correction to avoid some nasty jumpiness + // when 5-way moving horizontally in a vertically scrolling grid layout in Moonstone. Not sure this is the + // right fix. + if (this.isScrolling) { + if (this.xDir !== xDir) { + scrollBounds.left = this.destX; + xDir = calcNodeVisibility(offsetLeft, offsetWidth, scrollBounds.left, scrollBounds.clientWidth); + block = 'nearest'; + } + if (this.yDir !== yDir) { + scrollBounds.top = this.destY; + yDir = calcNodeVisibility(offsetTop, offsetHeight, scrollBounds.top, scrollBounds.clientHeight); + block = 'nearest'; + } + } + + switch (xDir) { + case 0: + x = this.scrollLeft; + break; + case 1: + // If control requested to be scrolled all the way to the viewport's left, or if the control + // is larger than the viewport, scroll to the control's left edge. Otherwise, scroll just + // far enough to get the control into view. + if (block === 'farthest' || block === 'start' || offsetWidth > scrollBounds.clientWidth) { + x = offsetLeft; + } else { + x = offsetLeft - scrollBounds.clientWidth + offsetWidth; + // If nodeStyle exists, add the _marginRight_ to the scroll value. + x += dom.getComputedBoxValue(node, 'margin', 'right'); + } + break; + case -1: + // If control requested to be scrolled all the way to the viewport's right, or if the control + // is larger than the viewport, scroll to the control's right edge. Otherwise, scroll just + // far enough to get the control into view. + if (block === 'farthest' || block === 'end' || offsetWidth > scrollBounds.clientWidth) { + x = offsetLeft - scrollBounds.clientWidth + offsetWidth; + } else { + x = offsetLeft; + // If nodeStyle exists, subtract the _marginLeft_ from the scroll value. + x -= dom.getComputedBoxValue(node, 'margin', 'left'); + } + break; + } + + switch (yDir) { + case 0: + y = this.scrollTop; + break; + case 1: + // If control requested to be scrolled all the way to the viewport's top, or if the control + // is larger than the viewport, scroll to the control's top edge. Otherwise, scroll just + // far enough to get the control into view. + if (block === 'farthest' || block === 'start' || offsetHeight > scrollBounds.clientHeight) { + y = offsetTop; + // If nodeStyle exists, subtract the _marginTop_ from the scroll value. + y -= dom.getComputedBoxValue(node, 'margin', 'top'); + } else { + y = offsetTop - scrollBounds.clientHeight + offsetHeight; + // If nodeStyle exists, add the _marginBottom_ to the scroll value. + y += dom.getComputedBoxValue(node, 'margin', 'bottom'); + } + break; + case -1: + // If control requested to be scrolled all the way to the viewport's bottom, or if the control + // is larger than the viewport, scroll to the control's bottom edge. Otherwise, scroll just + // far enough to get the control into view. + if (block === 'farthest' || block === 'end' || offsetHeight > scrollBounds.clientHeight) { + y = offsetTop - scrollBounds.clientHeight + offsetHeight; + } else { + y = offsetTop; + // If nodeStyle exists, subtract the _marginBottom_ from the scroll value. + y -= dom.getComputedBoxValue(node, 'margin', 'bottom'); + } + break; + } + + // If x or y changed, scroll to new position + if (x !== this.scrollLeft || y !== this.scrollTop) { + this.scrollTo(x, y, opts); + } + }, + + /** + * @public + */ + pageUp: function() { + this.paginate('up'); + }, + + /** + * @public + */ + pageDown: function() { + this.paginate('down'); + }, + + /** + * @public + */ + pageLeft: function() { + this.paginate('left'); + }, + + /** + * @public + */ + pageRight: function() { + this.paginate('right'); + }, + + /** + * Stops any active scroll movement. + * + * @param {Boolean} emit - Whether or not to fire the `onScrollStop` event. + * @public + */ + stop: function () { + var m = this.$.scrollMath; + + if (m.isScrolling()) { + m.stop(true); + } + }, + + /** + * @private + */ + paginate: function(direction) { + var b = this.calcBoundaries(), + scrollYDelta = b.clientHeight * this.pageMultiplier, + scrollXDelta = b.clientWidth * this.pageMultiplier, + x = this.scrollLeft, + y = this.scrollTop; + + switch (direction) { + case 'left': + x -= scrollXDelta; + break; + case 'up': + y -= scrollYDelta; + break; + case 'right': + x += scrollXDelta; + break; + case 'down': + y += scrollYDelta; + break; + } + + x = Math.max(0, Math.min(x, b.maxLeft)); + y = Math.max(0, Math.min(y, b.maxTop)); + + this.scrollTo(x, y); + }, + + /** + /* @private + */ + mousewheel: function (sender, e) { + if (this.useMouseWheel) { + if (!this.$.scrollMath.isScrolling()) { + this.calcBoundaries(); + } + + // TODO: Change this after newMousewheel becomes mousewheel + if (this.$.scrollMath.newMousewheel(e, {rtl: this.rtl})) { + e.preventDefault(); + return true; + } + } + }, + + /** + * @private + */ + down: function (sender, e) { + var m = this.$.scrollMath; + + if (m.isScrolling() && !m.isInOverScroll() && !this.isScrollControl(e.originator)) { + this.stop(true); + e.preventTap(); + } + this.calcStartInfo(); + }, + + /** + * @private + */ + dragstart: function (sender, e) { + if (this.touch) { + this.calcBoundaries(); + // Ignore drags sent from multi-touch events + if(!this.dragDuringGesture && e.srcEvent.touches && e.srcEvent.touches.length > 1) { + return true; + } + // note: allow drags to propagate to parent scrollers via data returned in the shouldDrag event. + this.doShouldDrag(e); + this.dragging = (e.dragger == this || (!e.dragger && e.boundaryDragger == this)); + if (this.dragging) { + if(this.preventDefault){ + e.preventDefault(); + } + // note: needed because show/hide changes + // the position so sync'ing is required when + // dragging begins (needed because show/hide does not trigger onscroll) + //this.syncScrollMath(); + this.$.scrollMath.startDrag(e); + if (this.preventDragPropagation) { + return true; + } + } + } + }, + + /** + * @private + */ + drag: function (sender, e) { + if (this.touch) { + if (this.dragging) { + if(this.preventDefault){ + e.preventDefault(); + } + this.$.scrollMath.drag(e); + } + } + }, + + /** + * @private + */ + dragfinish: function (sender, e) { + if (this.touch) { + if (this.dragging) { + e.preventTap(); + this.$.scrollMath.dragFinish(); + this.dragging = false; + } + } + }, + + /** + * @private + */ + flick: function (sender, e) { + if (this.touch) { + var onAxis = Math.abs(e.xVelocity) > Math.abs(e.yVelocity) ? this.$.scrollMath.horizontal : this.$.scrollMath.vertical; + if (onAxis && this.dragging) { + this.$.scrollMath.flick(e); + return this.preventDragPropagation; + } + } + }, + + /** + * @private + */ + shouldDrag: function (sender, e) { + //this.calcAutoScrolling(); + var requestV = e.vertical, + // canH = this.$.scrollMath.horizontal && !requestV, + // canV = this.$.scrollMath.vertical && requestV, + canH = this.$.scrollMath.canScrollX() && !requestV, + canV = this.$.scrollMath.canScrollY() && requestV, + down = e.dy < 0, + right = e.dx < 0, + oobV = (!down && this.startEdges.top || down && this.startEdges.bottom), + oobH = (!right && this.startEdges.left || right && this.startEdges.right); + // we would scroll if not at a boundary + if (!e.boundaryDragger && (canH || canV)) { + e.boundaryDragger = this; + } + // include boundary exclusion + if ((!oobV && canV) || (!oobH && canH)) { + e.dragger = this; + return true; + } + }, + + stabilize: function() { + this.$.scrollMath.stabilize(); + }, + + /** + * @private + */ + scroll: kind.inherit(function (sup) { + return function(sender, event) { + var px = this.scrollLeft, + py = this.scrollTop, + pv = this.velocity, + x = this.scrollLeft = -sender.x, + y = this.scrollTop = -sender.y, + dx = px - x, + dy = py - y, + v = (dx * dx) + (dy * dy); + this.xDir = (dx < 0? 1: dx > 0? -1: 0); + this.yDir = (dy < 0? 1: dy > 0? -1: 0); + this.velocity = v; + this.acc = v > pv; + this.dec = v < pv; + this.destX = -sender.endX; + this.destY = -sender.endY; + this.updateScrollability(x, y); + // Experimental: suppress and resume mouse events + // based on veclocity and acceleration + this._manageMouseEvents(); + sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + updateScrollability: function(x, y) { + var m = this.$.scrollMath, + b = -m.bottomBoundary, + r = -m.rightBoundary, + c = (this.canScrollX !== this.couldScrollX) || + (this.canScrollY !== this.couldScrollY); + + if (this.canScrollY || this.couldScrollY) { + this.set('yPosRatio', y / b); + + if (this.canScrollUp) { + if (y <= 0) { + this.canScrollUp = false; + c = true; + } + } + else { + if (y > 0) { + this.canScrollUp = true; + c = true; + } + } + if (this.canScrollDown) { + if (y >= b) { + this.canScrollDown = false; + c = true; + } + } + else { + if (y < b) { + this.canScrollDown = true; + c = true; + } + } + } + + if (this.canScrollX || this.couldScrollX) { + this.set('xPosRatio', x / r); + + if (this.canScrollLeft) { + if (x <= 0) { + this.canScrollLeft = false; + c = true; + } + } + else { + if (x > 0) { + this.canScrollLeft = true; + c = true; + } + } + if (this.canScrollRight) { + if (x >= r) { + this.canScrollRight = false; + c = true; + } + } + else { + if (x < r) { + this.canScrollRight = true; + c = true; + } + } + } + + this.couldScrollX = this.canScrollX; + this.couldScrollY = this.canScrollY; + + if (c) { + this.emit('scrollabilityChanged'); + } + }, + + /** + * @private + */ + scrollStart: function () { + this.calcBoundaries(); + this.isScrolling = true; + this.emit('stateChanged'); + }, + /** + * @private + */ + scrollStop: function () { + // TODO: Leaving this in to be safe... + // But we should already have resumed, due to + // velocity-sensitive logic in scroll(). + // This whole scheme probably needs bullet-proofing. + if (!this.touch) { + this._resumeMouseEvents(); + } + this.isScrolling = false; + this.emit('stateChanged'); + }, + /** + * @private + */ + _manageMouseEvents: function () { + // TODO: Experiment, make configurable + var t = 5, + v = this.velocity; + + if (this._suppressing) { + if (this.dec && v < t) { + this._resumeMouseEvents(); + } + } + // TODO: Can probably allow suppressing events when this.touch === true + // if we resume events on down so we can capture flicks and drags. Need + // to experiment. + else if (this.suppressMouseEvents && !this.touch) { + if (this.isScrolling && this.acc && v > t) { + this._suppressMouseEvents(); + } + } + }, + + /** + * @private + */ + _suppressMouseEvents: function () { + // TODO: Create a dispatcher API for this + dispatcher.stopListening(document, 'mouseover'); + dispatcher.stopListening(document, 'mouseout'); + dispatcher.stopListening(document, 'mousemove'); + this._suppressing = true; + }, + + /** + * @private + */ + _resumeMouseEvents: function () { + // TODO: Create a dispatcher API for this + dispatcher.listen(document, 'mouseover'); + dispatcher.listen(document, 'mouseout'); + dispatcher.listen(document, 'mousemove'); + this._suppressing = false; + }, + + /** + * @private + */ + getScrollBounds: function () { + var s = this.getScrollSize(), cn = this.calcScrollNode(); + var b = { + left: this.get('scrollLeft'), + top: this.get('scrollTop'), + clientHeight: cn ? cn.clientHeight : 0, + clientWidth: cn ? cn.clientWidth : 0, + height: s.height, + width: s.width + }; + b.maxLeft = Math.max(0, b.width - b.clientWidth); + b.maxTop = Math.max(0, b.height - b.clientHeight); + this.cachedBounds = b; + return b; + }, + + /** + * @private + */ + getScrollSize: function () { + var n = this.calcScrollNode(), + w = this.getScrollWidth && this.getScrollWidth() || (n ? n.scrollWidth : 0), + h = this.getScrollHeight && this.getScrollHeight() || (n ? n.scrollHeight : 0); + return {width: w, height: h}; + }, + + /** + * @private + */ + calcScrollNode: kind.inherit(function (sup) { + return function() { + return (this.scrollNode = sup.apply(this, arguments) || this.hasNode()); + }; + }), + + /** + * @private + */ + calcStartInfo: function (bounds) { + var sb = bounds || this.getScrollBounds(), + y = this.scrollTop, + x = this.scrollLeft; + + // this.canVertical = sb.maxTop > 0 && this.vertical !== 'hidden'; + // this.canHorizontal = sb.maxLeft > 0 && this.horizontal !== 'hidden'; + this.startEdges = { + top: y === 0, + bottom: y === sb.maxTop, + left: x === 0, + right: x === sb.maxLeft + }; + }, + + /** + * @private + */ + calcBoundaries: function (bounds) { + var m = this.$.scrollMath, + b = bounds || this.getScrollBounds(), + width = b.width, + height = b.height, + clWidth = b.clientWidth, + clHeight = b.clientHeight, + rBound = clWidth - width, + bBound = clHeight - height, + xRatio = Math.min(1, clWidth / width), + yRatio = Math.min(1, clHeight / height), + cTop = Math.min(b.top, Math.max(0, -bBound)), + cLeft = Math.min(b.left, Math.max(0, -rBound)); + + m.rightBoundary = rBound; + m.bottomBoundary = bBound; + + this.set('canScrollX', m.canScrollX()); + this.set('canScrollY', m.canScrollY()); + + if (b.top !== cTop || b.left !== cLeft) { + b.left = cLeft; + b.top = cTop; + m.setScrollX(-cLeft); + m.setScrollY(-cTop); + this.scroll(m); + this.stop(); + } + + this.set('xSizeRatio', xRatio); + this.set('ySizeRatio', yRatio); + + this.updateScrollability(cLeft, cTop); + + return b; + }, + + // xSizeRatioChanged: function() { + // this.emit('metricsChanged', this.xSizeRatio); + // }, + + // ySizeRatioChanged: function() { + // this.emit('metricsChanged', this.ySizeRatio); + // }, + + xPosRatioChanged: function() { + this.emit('metricsChanged', this.xPosRatio); + }, + + yPosRatioChanged: function() { + this.emit('metricsChanged', this.yPosRatio); + }, + + // canScrollXChanged: function() { + // this.emit('scrollabilityChanged'); + // }, + + // canScrollYChanged: function() { + // this.emit('scrollabilityChanged'); + // } + + /** + * Returns `true` if `control` is a scrolling child of this + * scrollable (an element within the Scrollable's scrolling + * region). + * + * (Currently, we assume that any child of the Scrollable that + * is not a scroll control is a scrolling child, but this + * is an implementation detail and could change if we + * determine that there's a more appropriate way to test.) + * + * Returns `false` if `control` is one of the Scrollable's + * scroll controls, or if `control` is not a child of the + * Scrollable at all. + * + * @param {module:enyo/Control~Control} control - The control to be tested + * @protected + */ + isScrollingChild: function (control) { + var c = control; + + while (c && c !== this) { + if (c.scroller === this) { + return false; + } + c = c.parent; + } + + return c === this; + }, + + /** + * Returns `true` if `control` is one of the Scrollable's + * scroll controls. + * + * @param {module:enyo/Control~Control} control - The control to be tested + * @protected + */ + isScrollControl: function (control) { + var c = control; + + while (c && c !== this) { + if (c.scroller === this) { + return true; + } + c = c.parent; + } + + return false; + } +}; + +},{'../kind':'enyo/kind','../utils':'enyo/utils','../dom':'enyo/dom','../dispatcher':'enyo/dispatcher','../EventEmitter':'enyo/EventEmitter','../ScrollMath':'enyo/ScrollMath'}],'enyo/AnimationSupport/AnimationInterfaceSupport':[function (module,exports,global,require,request){ +require('enyo'); + +var + kind = require('../kind'), + animator = require('./Core'), + frame = require('./Frame'), + utils = require('../utils'), + dispatcher = require('../dispatcher'); + +var extend = kind.statics.extend; + +kind.concatenated.push('animation'); + +var AnimationInterfaceSupport = { + + /** + * @private + */ + patterns: [], + + /** + * @private + */ + checkX: 0, + + /** + * @private + */ + checkY: 0, + + /** + * @private + */ + deltaX: 0, + + /** + * @private + */ + deltaY: 0, + + /** + * @private + */ + translateX: 0, + + /** + * @private + */ + translateY: 0, + + /** + * @private + */ + scrollValue: 0, + + /** + * @private + */ + deltaValueX: 0, + + /** + * @private + */ + deltaValueY: 0, + + /** + * @private + */ + checkDragStartX: 0, + + /** + * @private + */ + checkDragStartY: 0, + + /** + * @private + */ + deltaDragValueX: 0, + + /** + * @private + */ + deltaDragValueY: 0, + + /** + * @private + */ + setAnimateOne: 0, + + /** + * @private + */ + setAnimateTwo: 0, + + /** + * @private + */ + setAnimateThree: 0, + + /** + * @private + */ + eventArray: [ + "dragstart", + "dragend", + "drag", + "flick", + "down", + "move", + "scroll", + "mousewheel", + "touchstart", + "touchmove", + "touchend" + ], + + /** + * @public + */ + initialize: function() { + var i, eventArrayLength = this.eventArray.length; + for (i = 0; i < eventArrayLength; i++) { + dispatcher.listen(this.node, this.eventArray[i], this.bindSafely(this.detectTheEvent)); + } + }, + + /** + * @public + */ + detectTheEvent: function(inSender, inEvent) { + var eventType = inSender.type; + switch (eventType) { + case "dragstart": + this.touchDragStart(inSender, inEvent, inSender.pageX, inSender.pageY); + break; + case "drag": + this.touchDragMove(inSender, inEvent, inSender.pageX, inSender.pageY); + break; + case "dragend": + this.touchDragEnd(inSender, inEvent); + break; + case "flick": + this.handleMyEvent(inSender, inEvent); + break; + case "down": + this.handleMyEvent(inSender, inEvent); + break; + case "move": + this.handleMyEvent(inSender, inEvent); + break; + case "scroll": + this.scrollEvent(inSender, inEvent); + break; + case "mousewheel": + this.mousewheelEvent(inSender, inEvent); + break; + case "touchstart": + this.touchDragStart(inSender, inEvent, inSender.targetTouches[0].pageX, inSender.targetTouches[0].pageY); + break; + case "touchmove": + this.touchDragMove(inSender, inEvent, inSender.targetTouches[0].pageX, inSender.targetTouches[0].pageY); + break; + case "touchend": + this.touchDragEnd(inSender, inEvent); + break; + default: + this.handleMyEvent(inSender, inEvent); + } + }, + + /** + * @public + */ + touchDragStart: function(inSender, inEvent, x, y) { + this.checkX = x; + this.checkY = y; + }, + + /** + * @public + */ + touchDragMove: function(inSender, inEvent, x, y) { + var currentX = x, + currentY = y; + + if (currentX != 0 || currentY != 0) { + this.deltaValueX = this.checkX - currentX; + + this.checkX = currentX; // set the initial position to the current position while moving + + this.deltaValueY = this.checkY - currentY; + + this.checkY = currentY; // set the initial position to the current position while moving + + //call commonTasks function with delta values + this.translateX = this.translateX + this.deltaValueX; + this.translateY = this.translateY + this.deltaValueY; + + this.setAnimateOne = this.translateX; + this.setAnimateTwo = this.translateX; + this.setAnimateThree = this.translateY; + + } + + }, + + /** + * @public + */ + touchDragEnd: function(inSender, inEvent, x, y) { + this.checkX = 0; + this.checkY = 0; + this.deltaValueX = 0; + this.deltaValueY = 0; + }, + + /** + * @public + */ + scrollEvent: function(inSender, inEvent) { + var delta = inSender.deltaY, + scrollTop = inSender.target.scrollTop, + scrollLeft = inSender.target.scrollLeft; + + if (this.scrollValue === 0) { + this.scrollValue = inSender.target.scrollTop; + } + + delta = inSender.target.scrollTop - this.scrollValue; + + this.deltaX = scrollLeft - this.deltaX; + this.deltaY = scrollTop - this.deltaY; + this.scrollValue = scrollTop; + + this.translateX = this.translateX + this.deltaX; + this.translateY = this.translateY + this.deltaY; + + //call commonTasks function with delta values + this.setAnimateOne = delta; + this.setAnimateTwo = this.translateX; + this.setAnimateThree = this.translateY; + + + this.deltaX = scrollLeft; + this.deltaY = scrollTop; + }, + + /** + * @public + */ + mousewheelEvent: function(inSender, inEvent) { + var delta = inSender.deltaY, + deltaX = inSender.wheelDeltaX, + deltaY = inSender.wheelDeltaY; + + this.translateX = this.translateX + deltaX; + this.translateY = this.translateY + deltaY; + + //call commonTasks function with delta values + this.setAnimateOne = delta; + this.setAnimateTwo = (-1 * (this.translateX)); + this.setAnimateThree = (-1 * (this.translateY)); + if (patterns[0].name === "Slideable") { + this.setAnimateTwo = this.setAnimateThree; + } + + + }, + + /** + * @public + */ + commonTasks: function(delta, deltax, deltay) { + var patternsLength = patterns.length; + if (delta !== 0) { + delta = delta / Math.abs(delta); + } + //Call specific interface + for (var i = 0; i < patternsLength; i++) { + if (patterns[i].name === "Fadeable") { + patterns[i].fadeByDelta.call(this, delta); + } else if (patterns[i].name === "Flippable") { + patterns[i].doFlip.call(this, delta); + } else if (patterns[i].name === "Slideable") { + if (this.parallax === true) { + for (var j = 0; j < this.children.length; j++) { + var current = this.children[j]; + animator.trigger(current); + patterns[i].slide.call(current, (-1 * deltax / current.speed), (-1 * deltay / current.speed), 0); + current.start(true); + } + } else { + patterns[i].slide.call(this, (-1 * deltax), (-1 * deltay), 0); + } + } + if (patterns[i].name !== "Slideable") { + this.setAnimateOne = 0; + this.setAnimateTwo = 0; + this.setAnimateThree = 0; + } + } + this.start(true); + }, + + /** + * @public + */ + handleMyEvent: function(inSender, inEvent) { + /*TODO:*/ + }, + + /** + * @public + */ + commitAnimation: function(x, y, z) { + var i, len; + + if (patterns && Object.prototype.toString.call(patterns) === "[object Array]") { + len = patterns.length; + for (i = 0; i < len; i++) { + if (typeof patterns[i].triggerEvent === 'function') { + //patterns[i].triggerEvent(); + + } + this.commonTasks(this.setAnimateOne, this.setAnimateTwo, this.setAnimateThree); + + } + } + }, + + /** + * @private + */ + rendered: kind.inherit(function(sup) { + return function() { + sup.apply(this, arguments); + this.initialize(); + }; + }) +}; + +module.exports = AnimationInterfaceSupport; + +/** + Hijacking original behaviour as in other Enyo supports. +*/ +var sup = kind.concatHandler; + +/** + * @private + */ +kind.concatHandler = function(ctor, props, instance) { + sup.call(this, ctor, props, instance); + var aPattern = props.pattern; + if (aPattern && Object.prototype.toString.call(aPattern) === "[object Array]") { + var proto = ctor.prototype || ctor; + extend(AnimationInterfaceSupport, proto); + + patterns = aPattern; + var len = patterns.length; + for (var i = 0; i < len; i++) { + extend(patterns[i], proto); + } + animator.register(proto); + } +}; + +},{'../kind':'enyo/kind','./Core':'enyo/AnimationSupport/Core','./Frame':'enyo/AnimationSupport/Frame','../utils':'enyo/utils','../dispatcher':'enyo/dispatcher'}],'enyo/gesture/drag':[function (module,exports,global,require,request){ +var + dispatcher = require('../dispatcher'), + platform = require('../platform'), + utils = require('../utils'); + +var + gestureUtil = require('./util'); + +/** +* Enyo supports a cross-platform set of drag [events]{@glossary event}. These +* events allow users to write a single set of event handlers for applications +* that run on both mobile and desktop platforms. +* +* The following events are provided: +* +* * 'dragstart' +* * 'dragfinish' +* * 'drag' +* * 'drop' +* * 'dragover' +* * 'dragout' +* * 'hold' +* * 'release' +* * 'holdpulse' +* * 'flick' +* +* For more information on these events, see the documentation on +* [Event Handling]{@linkplain $dev-guide/key-concepts/event-handling.html} in +* the Enyo Developer Guide. +* +* Used internally by {@link module:enyo/gesture} +* +* @module enyo/gesture/drag +* @public +*/ +module.exports = { + + /** + * @private + */ + holdPulseDefaultConfig: { + frequency: 200, + events: [{name: 'hold', time: 200}], + resume: false, + preventTap: false, + moveTolerance: 16, + endHold: 'onMove' + }, + + /** + * Call this method to specify the framework's 'holdPulse' behavior, which + * determines the nature of the events generated when a user presses and holds + * on a user interface element. + * + * By default, an `onhold` event fires after 200 ms. After that, an `onholdpulse` + * event fires every 200 ms until the user stops holding, at which point a + * `onrelease` event fires. + * + * To change the default behavior, call this method and pass it a holdPulse + * configuration object. The holdPulse configuration object has a number of + * properties. + * + * You can specify a set of custom hold events by setting the `events` property + * to an array containing one or more objects. Each object specifies a custom + * hold event, in the form of a `name` / `time` pair. Notes: + * + * * Your custom event names should not include the 'on' prefix; that will be + * added automatically by the framework. + * + * * Times should be specified in milliseconds. + * + * * Your `events` array overrides the framework defaults entirely, so if you + * want the standard `hold` event to fire at 200 ms (in addition to whatever + * custom events you define), you'll need to redefine it yourself as part of + * your `events` array. + * + * Regardless of how many custom hold events you define, `onholdpulse` events + * will start firing after the first custom hold event fires, and continue until + * the user stops holding. Likewise, only one `onrelease` event will fire, + * regardless of how many custom hold events you define. + * + * The`frequency` parameter determines not only how often `holdpulse` events are + * sent, but the frequency with which the hold duration is measured. This means + * that the value you set for `frequency` should always be a common factor of the + * times you set for your custom hold events, to ensure accurate event timing. + * + * You can use the `endHold` property to specify the circumstances under which a + * hold is considered to end. Set `endHold` to `onMove` (the default) if you want + * the hold to end as soon as the user's finger or pointer moves. Set `endHold` + * to `onLeave` if you want the hold to end only when the finger or pointer + * leaves the element altogether. When specifying `onMove`, you can also provide + * a `moveTolerance` value (default: `16`) that determines how tolerant you want + * to be of small movements when deciding whether a hold has ended. The higher + * the value, the further a user's finger or pointer may move without causing + * the hold to end. + * + * The `resume` parameter (default: `false`) specifies whether a hold + * that has ended due to finger / pointer movement should be resumed if the + * user's finger or pointer moves back inside the tolerance threshold (in the + * case of `endHold: onMove`) or back over the element (in the case of + * `endHold: onLeave`). + * + * Finally, the `preventTap` paramenter (default: `false`) allows you to prevent + * an `ontap` event from firing when the hold is released. + * + * Here is an example: + * + * ``` + * gesture.drag.configureHoldPulse({ + * frequency: 100, + * events: [ + * {name: 'hold', time: 200}, + * {name: 'longpress', time: 500} + * ], + * endHold: 'onLeave', + * resume: true, + * preventTap: true + * }); + * ``` + * For comparison, here are the out-of-the-box defaults: + * + * ``` + * gesture.drag.configureHoldPulse({ + * frequency: 200, + * events: [ + * {name: 'hold', time: 200} + * ], + * endHold: 'onMove', + * moveTolerance: 16, + * resume: false, + * preventTap: false + * }); + * ``` + * + * The settings you provide via this method will be applied globally, affecting + * every Control. Note that you can also override the defaults on a case-by-case + * basis by handling the `down` event for any Control and calling the + * `configureHoldPulse` method exposed by the event itself. That method works + * exactly like this one, except that the settings you provide will apply only to + * holds on that particular Control. + * + * @public + */ + configureHoldPulse: function (config) { + // TODO: Might be nice to do some validation, error handling + + // _holdPulseConfig represents the current, global `holdpulse` settings, if the default + // settings have been overridden in some way. + this._holdPulseConfig = this._holdPulseConfig || utils.clone(this.holdPulseDefaultConfig, true); + utils.mixin(this._holdPulseConfig, config); + }, + + /** + * Resets the `holdPulse` behavior to the default settings. + * + * @public + */ + resetHoldPulseConfig: function () { + this._holdPulseConfig = null; + }, + + /** + * @private + */ + holdPulseConfig: {}, + + /** + * @private + */ + trackCount: 5, + + /** + * @private + */ + minFlick: 0.1, + + /** + * @private + */ + minTrack: 8, + + /** + * @private + */ + down: function(e) { + // tracking if the mouse is down + //enyo.log('tracking ON'); + // Note: 'tracking' flag indicates interest in mousemove, it's turned off + // on mouseup + // make sure to stop dragging in case the up event was not received. + this.stopDragging(e); + this.target = e.target; + this.startTracking(e); + }, + + /** + * @private + */ + move: function(e) { + if (this.tracking) { + this.track(e); + // If the mouse is not down and we're tracking a drag, abort. + // this error condition can occur on IE/Webkit after interaction with a scrollbar. + if (!e.which) { + this.stopDragging(e); + this.endHold(); + this.tracking = false; + //enyo.log('gesture.drag: mouse must be down to drag.'); + return; + } + if (this.dragEvent) { + this.sendDrag(e); + } else if (this.holdPulseConfig.endHold === 'onMove') { + if (this.dy*this.dy + this.dx*this.dx >= this.holdPulseConfig.moveTolerance) { // outside of target + if (this.holdJob) { // only stop/cancel hold job if it currently exists + if (this.holdPulseConfig.resume) { // pause hold to potentially resume later + this.suspendHold(); + } else { // completely cancel hold + this.endHold(); + this.sendDragStart(e); + } + } + } else if (this.holdPulseConfig.resume && !this.holdJob) { // when moving inside target, only resume hold job if it was previously paused + this.resumeHold(); + } + } + } + }, + + /** + * @private + */ + up: function(e) { + this.endTracking(e); + this.stopDragging(e); + this.endHold(); + this.target = null; + }, + + /** + * @private + */ + enter: function(e) { + // resume hold when re-entering original target when using 'onLeave' endHold value + if (this.holdPulseConfig.resume && this.holdPulseConfig.endHold === 'onLeave' && this.target && e.target === this.target) { + this.resumeHold(); + } + }, + + /** + * @private + */ + leave: function(e) { + if (this.dragEvent) { + this.sendDragOut(e); + } else if (this.holdPulseConfig.endHold === 'onLeave') { + if (this.holdPulseConfig.resume) { // pause hold to potentially resume later + this.suspendHold(); + } else { // completely cancel hold + this.endHold(); + this.sendDragStart(e); + } + } + }, + + /** + * @private + */ + stopDragging: function(e) { + if (this.dragEvent) { + this.sendDrop(e); + var handled = this.sendDragFinish(e); + this.dragEvent = null; + return handled; + } + }, + + /** + * @private + */ + makeDragEvent: function(inType, inTarget, inEvent, inInfo) { + var adx = Math.abs(this.dx), ady = Math.abs(this.dy); + var h = adx > ady; + // suggest locking if off-axis < 22.5 degrees + var l = (h ? ady/adx : adx/ady) < 0.414; + var e = {}; + // var e = { + e.type = inType; + e.dx = this.dx; + e.dy = this.dy; + e.ddx = this.dx - this.lastDx; + e.ddy = this.dy - this.lastDy; + e.xDirection = this.xDirection; + e.yDirection = this.yDirection; + e.pageX = inEvent.pageX; + e.pageY = inEvent.pageY; + e.clientX = inEvent.clientX; + e.clientY = inEvent.clientY; + e.horizontal = h; + e.vertical = !h; + e.lockable = l; + e.target = inTarget; + e.dragInfo = inInfo; + e.ctrlKey = inEvent.ctrlKey; + e.altKey = inEvent.altKey; + e.metaKey = inEvent.metaKey; + e.shiftKey = inEvent.shiftKey; + e.srcEvent = inEvent.srcEvent; + // }; + //Fix for IE8, which doesn't include pageX and pageY properties + if(platform.ie==8 && e.target) { + e.pageX = e.clientX + e.target.scrollLeft; + e.pageY = e.clientY + e.target.scrollTop; + } + e.preventDefault = gestureUtil.preventDefault; + e.disablePrevention = gestureUtil.disablePrevention; + return e; + }, + + /** + * @private + */ + sendDragStart: function(e) { + //enyo.log('dragstart'); + this.dragEvent = this.makeDragEvent('dragstart', this.target, e); + dispatcher.dispatch(this.dragEvent); + }, + + /** + * @private + */ + sendDrag: function(e) { + //enyo.log('sendDrag to ' + this.dragEvent.target.id + ', over to ' + e.target.id); + // send dragOver event to the standard event target + var synth = this.makeDragEvent('dragover', e.target, e, this.dragEvent.dragInfo); + dispatcher.dispatch(synth); + // send drag event to the drag source + synth.type = 'drag'; + synth.target = this.dragEvent.target; + dispatcher.dispatch(synth); + }, + + /** + * @private + */ + sendDragFinish: function(e) { + //enyo.log('dragfinish'); + var synth = this.makeDragEvent('dragfinish', this.dragEvent.target, e, this.dragEvent.dragInfo); + synth.preventTap = function() { + if (e.preventTap) { + e.preventTap(); + } + }; + dispatcher.dispatch(synth); + }, + + /** + * @private + */ + sendDragOut: function(e) { + var synth = this.makeDragEvent('dragout', e.target, e, this.dragEvent.dragInfo); + dispatcher.dispatch(synth); + }, + + /** + * @private + */ + sendDrop: function(e) { + var synth = this.makeDragEvent('drop', e.target, e, this.dragEvent.dragInfo); + synth.preventTap = function() { + if (e.preventTap) { + e.preventTap(); + } + }; + dispatcher.dispatch(synth); + }, + + /** + * @private + */ + startTracking: function(e) { + this.tracking = true; + // note: use clientX/Y to be compatible with ie8 + this.px0 = e.clientX; + this.py0 = e.clientY; + // this.flickInfo = {startEvent: e, moves: []}; + this.flickInfo = {}; + this.flickInfo.startEvent = e; + // FIXME: so we're trying to reuse objects where possible, should + // do the same in scenarios like this for arrays + this.flickInfo.moves = []; + this.track(e); + }, + + /** + * @private + */ + track: function(e) { + this.lastDx = this.dx; + this.lastDy = this.dy; + this.dx = e.clientX - this.px0; + this.dy = e.clientY - this.py0; + this.xDirection = this.calcDirection(this.dx - this.lastDx, 0); + this.yDirection = this.calcDirection(this.dy - this.lastDy, 0); + // + var ti = this.flickInfo; + ti.moves.push({ + x: e.clientX, + y: e.clientY, + t: utils.perfNow() + }); + // track specified # of points + if (ti.moves.length > this.trackCount) { + ti.moves.shift(); + } + }, + + /** + * @private + */ + endTracking: function() { + this.tracking = false; + var ti = this.flickInfo; + var moves = ti && ti.moves; + if (moves && moves.length > 1) { + // note: important to use up time to reduce flick + // velocity based on time between move and up. + var l = moves[moves.length-1]; + var n = utils.perfNow(); + // take the greatest of flick between each tracked move and last move + for (var i=moves.length-2, dt=0, x1=0, y1=0, x=0, y=0, sx=0, sy=0, m; (m=moves[i]); i--) { + // this flick (this move - last move) / (this time - last time) + dt = n - m.t; + x1 = (l.x - m.x) / dt; + y1 = (l.y - m.y) / dt; + // establish flick direction + sx = sx || (x1 < 0 ? -1 : (x1 > 0 ? 1 : 0)); + sy = sy || (y1 < 0 ? -1 : (y1 > 0 ? 1 : 0)); + // if either axis is a greater flick than previously recorded use this one + if ((x1 * sx > x * sx) || (y1 * sy > y * sy)) { + x = x1; + y = y1; + } + } + var v = Math.sqrt(x*x + y*y); + if (v > this.minFlick) { + // generate the flick using the start event so it has those coordinates + this.sendFlick(ti.startEvent, x, y, v); + } + } + this.flickInfo = null; + }, + + /** + * @private + */ + calcDirection: function(inNum, inDefault) { + return inNum > 0 ? 1 : (inNum < 0 ? -1 : inDefault); + }, + + /** + * Translate the old format for holdPulseConfig to the new one, to + * preserve backward compatibility. + * + * @private + */ + normalizeHoldPulseConfig: function (oldOpts) { + var nOpts = utils.clone(oldOpts); + nOpts.frequency = nOpts.delay; + nOpts.events = [{name: 'hold', time: nOpts.delay}]; + return nOpts; + }, + + /** + * Method to override holdPulseConfig for a given gesture. This method isn't + * accessed directly from gesture.drag, but exposed by the `down` event. + * See `prepareHold()`. + * + * @private + */ + _configureHoldPulse: function(opts) { + var nOpts = (opts.delay === undefined) ? + opts : + this.normalizeHoldPulseConfig(opts); + utils.mixin(this.holdPulseConfig, nOpts); + }, + + /** + * @private + */ + prepareHold: function(e) { + // quick copy as the prototype of the new overridable config + this.holdPulseConfig = utils.clone(this._holdPulseConfig || this.holdPulseDefaultConfig, true); + + // expose method for configuring holdpulse options + e.configureHoldPulse = this._configureHoldPulse.bind(this); + }, + + /** + * @private + */ + beginHold: function(e) { + var ce; + // cancel any existing hold since it's possible in corner cases to get a down without an up + this.endHold(); + this.holdStart = utils.perfNow(); + this._holdJobFunction = utils.bind(this, 'handleHoldPulse'); + // clone the event to ensure it stays alive on IE upon returning to event loop + ce = this._holdJobEvent = utils.clone(e); + ce.srcEvent = utils.clone(e.srcEvent); + ce.downEvent = e; + this._pulsing = false; + this._unsent = utils.clone(this.holdPulseConfig.events); + this._unsent.sort(this.sortEvents); + this._next = this._unsent.shift(); + if (this._next) { + this.holdJob = setInterval(this._holdJobFunction, this.holdPulseConfig.frequency); + } + }, + + /** + * @private + */ + resumeHold: function() { + this.handleHoldPulse(); + this.holdJob = setInterval(this._holdJobFunction, this.holdPulseConfig.frequency); + }, + + /** + * @private + */ + sortEvents: function(a, b) { + if (a.time < b.time) return -1; + if (a.time > b.time) return 1; + return 0; + }, + + /** + * @private + */ + endHold: function() { + var e = this._holdJobEvent; + this.suspendHold(); + if (e && this._pulsing) { + this.sendRelease(e); + } + this._pulsing = false; + this._unsent = null; + this._holdJobFunction = null; + this._holdJobEvent = null; + this._next = null; + }, + + /** + * @private + */ + suspendHold: function() { + clearInterval(this.holdJob); + this.holdJob = null; + }, + + /** + * @private + */ + handleHoldPulse: function() { + var holdTime = utils.perfNow() - this.holdStart, + hje = this._holdJobEvent, + e; + this.maybeSendHold(hje, holdTime); + if (this._pulsing) { + e = gestureUtil.makeEvent('holdpulse', hje); + e.holdTime = holdTime; + dispatcher.dispatch(e); + } + }, + + /** + * @private + */ + maybeSendHold: function(inEvent, inHoldTime) { + var n = this._next; + while (n && n.time <= inHoldTime) { + var e = gestureUtil.makeEvent(n.name, inEvent); + if (!this._pulsing && this.holdPulseConfig.preventTap) { + inEvent.downEvent.preventTap(); + } + this._pulsing = true; + dispatcher.dispatch(e); + n = this._next = this._unsent.shift(); + } + }, + + /** + * @private + */ + sendRelease: function(inEvent) { + var e = gestureUtil.makeEvent('release', inEvent); + dispatcher.dispatch(e); + }, + + /** + * @private + */ + sendFlick: function(inEvent, inX, inY, inV) { + var e = gestureUtil.makeEvent('flick', inEvent); + e.xVelocity = inX; + e.yVelocity = inY; + e.velocity = inV; + dispatcher.dispatch(e); + } +}; + +},{'../dispatcher':'enyo/dispatcher','../platform':'enyo/platform','../utils':'enyo/utils','./util':'enyo/gesture/util'}],'enyo/gesture/touchGestures':[function (module,exports,global,require,request){ +var + dispatcher = require('../dispatcher'), + utils = require('../utils'); + +/** +* The extended {@glossary event} [object]{@glossary Object} that is provided when we +* emulate iOS gesture events on non-iOS devices. +* +* @typedef {Object} module:enyo/gesture/touchGestures~EmulatedGestureEvent +* @property {Number} pageX - The x-coordinate of the center point between fingers. +* @property {Number} pageY - The y-coordinate of the center point between fingers. +* @property {Number} rotation - The degrees of rotation from the beginning of the gesture. +* @property {Number} scale - The percent change of distance between fingers. +*/ + +/** +* @module enyo/gesture/touchGestures +* @private +*/ +module.exports = { + + /** + * @private + */ + orderedTouches: [], + + /** + * @private + */ + gesture: null, + + /** + * @private + */ + touchstart: function (e) { + // some devices can send multiple changed touches on start and end + var i, + changedTouches = e.changedTouches, + length = changedTouches.length; + + for (i = 0; i < length; i++) { + var id = changedTouches[i].identifier; + + // some devices can send multiple touchstarts + if (utils.indexOf(id, this.orderedTouches) < 0) { + this.orderedTouches.push(id); + } + } + + if (e.touches.length >= 2 && !this.gesture) { + var p = this.gesturePositions(e); + + this.gesture = this.gestureVector(p); + this.gesture.angle = this.gestureAngle(p); + this.gesture.scale = 1; + this.gesture.rotation = 0; + var g = this.makeGesture('gesturestart', e, {vector: this.gesture, scale: 1, rotation: 0}); + dispatcher.dispatch(g); + } + }, + + /** + * @private + */ + touchend: function (e) { + // some devices can send multiple changed touches on start and end + var i, + changedTouches = e.changedTouches, + length = changedTouches.length; + + for (i = 0; i < length; i++) { + utils.remove(changedTouches[i].identifier, this.orderedTouches); + } + + if (e.touches.length <= 1 && this.gesture) { + var t = e.touches[0] || e.changedTouches[e.changedTouches.length - 1]; + + // gesture end sends last rotation and scale, with the x/y of the last finger + dispatcher.dispatch(this.makeGesture('gestureend', e, {vector: {xcenter: t.pageX, ycenter: t.pageY}, scale: this.gesture.scale, rotation: this.gesture.rotation})); + this.gesture = null; + } + }, + + /** + * @private + */ + touchmove: function (e) { + if (this.gesture) { + var g = this.makeGesture('gesturechange', e); + this.gesture.scale = g.scale; + this.gesture.rotation = g.rotation; + dispatcher.dispatch(g); + } + }, + + /** + * @private + */ + findIdentifiedTouch: function (touches, id) { + for (var i = 0, t; (t = touches[i]); i++) { + if (t.identifier === id) { + return t; + } + } + }, + + /** + * @private + */ + gesturePositions: function (e) { + var first = this.findIdentifiedTouch(e.touches, this.orderedTouches[0]); + var last = this.findIdentifiedTouch(e.touches, this.orderedTouches[this.orderedTouches.length - 1]); + var fx = first.pageX, lx = last.pageX, fy = first.pageY, ly = last.pageY; + // center the first touch as 0,0 + var x = lx - fx, y = ly - fy; + var h = Math.sqrt(x*x + y*y); + return {x: x, y: y, h: h, fx: fx, lx: lx, fy: fy, ly: ly}; + }, + + /** + * Finds rotation angle. + * + * @private + */ + gestureAngle: function (positions) { + var p = positions; + // yay math!, rad -> deg + var a = Math.asin(p.y / p.h) * (180 / Math.PI); + // fix for range limits of asin (-90 to 90) + // Quadrants II and III + if (p.x < 0) { + a = 180 - a; + } + // Quadrant IV + if (p.x > 0 && p.y < 0) { + a += 360; + } + return a; + }, + + /** + * Finds bounding box. + * + * @private + */ + gestureVector: function (positions) { + // the least recent touch and the most recent touch determine the bounding box of the gesture event + var p = positions; + // center the first touch as 0,0 + return { + magnitude: p.h, + xcenter: Math.abs(Math.round(p.fx + (p.x / 2))), + ycenter: Math.abs(Math.round(p.fy + (p.y / 2))) + }; + }, + + /** + * @private + */ + makeGesture: function (type, e, cache) { + var vector, scale, rotation; + if (cache) { + vector = cache.vector; + scale = cache.scale; + rotation = cache.rotation; + } else { + var p = this.gesturePositions(e); + vector = this.gestureVector(p); + scale = vector.magnitude / this.gesture.magnitude; + // gestureEvent.rotation is difference from the starting angle, clockwise + rotation = (360 + this.gestureAngle(p) - this.gesture.angle) % 360; + } + var event = utils.clone(e); + return utils.mixin(event, { + type: type, + scale: scale, + pageX: vector.xcenter, + pageY: vector.ycenter, + rotation: rotation + }); + } +}; + +},{'../dispatcher':'enyo/dispatcher','../utils':'enyo/utils'}],'enyo/UiComponent':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/UiComponent~UiComponent} kind. +* @module enyo/UiComponent +*/ + +var + kind = require('./kind'), + utils = require('./utils'), + master = require('./master'), + AnimationSupport = require('./AnimationSupport/AnimationSupport'), + AnimationInterfaceSupport = require('./AnimationSupport/AnimationInterfaceSupport'); + +var + Component = require('./Component'); + +/** +* {@link module:enyo/UiComponent~UiComponent} implements a container strategy suitable for presentation layers. +* +* `UiComponent` itself is abstract. Concrete [subkinds]{@glossary subkind} include +* {@link module:enyo/Control~Control} (for HTML/DOM) and +* {@link module:canvas/Control~Control} (for Canvas contexts). +* +* @class UiComponent +* @extends module:enyo/Component~Component +* @public +*/ +var UiComponent = module.exports = kind( + /** @lends module:enyo/UiComponent~UiComponent.prototype */ { + + name: 'enyo.UiComponent', + + /** + * @private + */ + kind: Component, + + statics: { + + /** + * The default set of keys which are effectively "ignored" when determining whether or not the + * this control has changed in such a way that warrants a complete re-render. When + * {@link enyo.UIComponent#updateComponents} is invoked on a parent component, this set of + * stateful keys is utilized by default, if no stateful keys are provided by us. + * + * @type {String[]} + * @default ['content', active', 'disabled'] + * @private + */ + statefulKeys: [ + 'content', + 'active', + 'disabled' + ], + + /** + * Finds static properties by walking up the inheritance chain, until the property is found. + * By default this will return the property from {@link module:enyo/UiComponent} if the + * property is not found anywhere along the chain. + * + * @param {module:enyo/kind} kind - The kind which we are attempting to retrieve the property + * from; if the property is not found on this kind, its parent kind will be examined. + * @param {String} prop - The property we are trying to retrieve. + * @returns {String[]} The array of stateful key strings. + * @public + */ + findStatic: function (kind, prop) { + if (kind) { + if (kind[prop]) return kind[prop]; + return UiComponent.findStatic(kind.kind, prop); + } else { + return UiComponent[prop]; + } + } + }, + + /** + * @private + */ + published: + /** @lends module:enyo/UiComponent~UiComponent.prototype */ { + + /** + * The [UiComponent]{@link module:enyo/UiComponent~UiComponent} that physically contains this + * [component]{@link module:enyo/Component~Component} in the DOM. + * + * @type {module:enyo/UiComponent~UiComponent} + * @default null + * @public + */ + container: null, + + /** + * The [UiComponent]{@link module:enyo/UiComponent~UiComponent} that owns this + * [component]{@link module:enyo/Component~Component} for purposes of {@glossary event} + * propagation. + * + * @type {module:enyo/UiComponent~UiComponent} + * @default null + * @public + */ + parent: null, + + /** + * The [UiComponent]{@link module:enyo/UiComponent~UiComponent} that will physically contain new items added + * by calls to [createComponent()]{@link module:enyo/UiComponent~UiComponent#createComponent}. + * + * @type {String} + * @default 'client' + * @public + */ + controlParentName: 'client', + + /** + * A {@glossary kind} used to manage the size and placement of child + * [components]{@link module:enyo/Component~Component}. + * + * @type {String} + * @default '' + * @public + */ + layoutKind: '' + }, + + /** + * @private + */ + handlers: { + onresize: 'handleResize' + }, + + /** + * Adding animation support for controls + * @private + */ + mixins: [AnimationSupport, AnimationInterfaceSupport], + + /** + * When set, provides a [control]{@link module:enyo/Control~Control} reference used to indicate where a + * newly-created [component]{@link module:enyo/Component~Component} should be added in the + * [UiComponent's]{@link module:enyo/UiComponent~UiComponent} [array]{@glossary Array} of children. This is + * typically used when creating children dynamically (rather than at design time). If set + * to `null`, the new control will be added at the beginning of the array; if set to a + * specific existing control, the new control will be added before the specified + * control. If left as `undefined`, the default behavior is to add the new control + * at the end of the array. + * + * @type {module:enyo/Control~Control} + * @default undefined + * @public + */ + addBefore: undefined, + + /** + * @method + * @private + */ + create: kind.inherit(function (sup) { + return function() { + this.controls = this.controls || []; + this.children = this.children || []; + this.containerChanged(); + sup.apply(this, arguments); + this.layoutKindChanged(); + }; + }), + + /** + * @method + * @private + */ + destroy: kind.inherit(function (sup) { + return function() { + // Destroys all non-chrome controls (regardless of owner). + this.destroyClientControls(); + // Removes us from our container. + this.setContainer(null); + // Destroys chrome controls owned by this. + sup.apply(this, arguments); + }; + }), + + /** + * @method + * @private + */ + importProps: kind.inherit(function (sup) { + return function(inProps) { + sup.apply(this, arguments); + if (!this.owner) { + this.owner = master; + } + }; + }), + + /** + * Creates [components]{@link module:enyo/Component~Component} as defined by the [arrays]{@glossary Array} + * of base and additional property [hashes]{@glossary Object}. The standard and + * additional property hashes are combined as described in + * {@link module:enyo/Component~Component#createComponent}. + * + * ``` + * // ask foo to create components 'bar' and 'zot', but set the owner of + * // both components to 'this'. + * this.$.foo.createComponents([ + * {name: 'bar'}, + * {name: 'zot'} + * ], {owner: this}); + * ``` + * + * As implemented, [controlParentName]{@link module:enyo/UiComponent~UiComponent#controlParentName} only works + * to identify an owned control created via `createComponents()` + * (i.e., usually in our `components` block). To attach a `controlParent` via other means, + * one must call [discoverControlParent()]{@link module:enyo/UiComponent~UiComponent#discoverControlParent} or + * set `controlParent` directly. + * + * We could call `discoverControlParent()` in + * [addComponent()]{@link module:enyo/Component~Component#addComponent}, but that would + * cause a lot of useless checking. + * + * @param {Object[]} props The array of {@link module:enyo/Component~Component} definitions to be created. + * @param {Object} ext - Additional properties to be supplied as defaults for each. + * @returns {module:enyo/Component~Component[]} The array of components that were created. + * @method + * @public + */ + // + createComponents: kind.inherit(function (sup) { + return function() { + var results = sup.apply(this, arguments); + this.discoverControlParent(); + return results; + }; + }), + + /** + * An alternative component update path that attempts to intelligently update only the + * relevant portions of the component which have changed. + * + * @param {Array} comps - An array of kind definitions to be set as the child components of + * this component. + * @returns {Boolean} - Whether or not the component should be re-rendered. + * @public + */ + updateComponents: function (comps) { + var allStatefulKeys = {}, + isChanged = this.computeComponentsDiff(comps, allStatefulKeys), + comp, controls, control, keys, key, idxKey, idxComp, kind; + + if (isChanged) { + this.destroyClientControls(); + this.createComponents(comps); + return true; + } else { + controls = this.getClientControls(); + for (idxComp = 0; idxComp < comps.length; idxComp++) { + comp = comps[idxComp]; + control = controls[idxComp]; + kind = comp.kind || this.defaultKind; + keys = allStatefulKeys[idxComp]; + + for (idxKey = 0; idxKey < keys.length; idxKey++) { // for each key, determine if there is a change + key = keys[idxKey]; + if (comp[key] != control[key]) { + control.set(key, comp[key]); + } + } + } + } + + return false; + }, + + /** + * @private + */ + computeComponentsDiff: function (comps, allStatefulKeys) { + var hash = this.computeComponentsHash(comps, allStatefulKeys), + isChanged = false; + + if (this._compHash) isChanged = this._compHash != hash; + else isChanged = true; + + this._compHash = hash; + + return isChanged; + }, + + /** + * @private + */ + computeComponentsHash: function (comps, allStatefulKeys) { + var keyCount = 0, + hash, str, filtered, chr, len, idx; + + // http://jsperf.com/json-parse-and-iteration-vs-array-map + filtered = comps.map(this.bindSafely(function (comp, itemIdx) { + var kind = comp.kind || this.defaultKind, + keys = UiComponent.findStatic(kind, 'statefulKeys'), + objKeys = Object.keys(comp), + obj = {}, + idx, key, value; + + allStatefulKeys[itemIdx] = keys; // cache statefulKeys + + for (idx = 0; idx < objKeys.length; idx++) { + key = objKeys[idx]; + + if (keys.indexOf(key) == -1) { // ignore stateful keys + value = comp[key]; + if (typeof value == 'function') value = (value.prototype && value.prototype.kindName) || value.toString(); + obj[key] = value; + keyCount++; + } + + } + + return obj; + })); + + // Adapted from http://stackoverflow.com/a/7616484 + str = JSON.stringify(filtered) + keyCount; + hash = 0; + + for (idx = 0, len = str.length; idx < len; idx++) { + chr = str.charCodeAt(idx); + hash = ((hash << 5) - hash) + chr; + hash |= 0; // Convert to 32bit integer + } + + return hash; + }, + + /** + * Determines and sets the current [control's]{@link module:enyo/Control~Control} parent. + * + * @protected + */ + discoverControlParent: function () { + this.controlParent = this.$[this.controlParentName] || this.controlParent; + }, + + /** + * @method + * @private + */ + adjustComponentProps: kind.inherit(function (sup) { + return function(inProps) { + // Components we create have us as a container by default. + inProps.container = inProps.container || this; + sup.apply(this, arguments); + }; + }), + + /** + * Containment + * + * @method + * @private + */ + containerChanged: function (container) { + if (container) { + container.removeControl(this); + } + if (this.container) { + this.container.addControl(this, this.addBefore); + } + }, + + /** + * Parentage + * + * @method + * @private + */ + parentChanged: function (oldParent) { + if (oldParent && oldParent != this.parent) { + oldParent.removeChild(this); + } + }, + + /** + * Determines whether the [control]{@link module:enyo/Control~Control} is a descendant of + * another control. + * + * Note: Oddly, a control is considered to be a descendant of itself. + * + * @method + * @param {module:enyo/Control~Control} ancestor - The [control]{@link module:enyo/Control~Control} whose lineage + * will be checked to determine whether the current control is a descendant. + * @public + */ + isDescendantOf: function (ancestor) { + var p = this; + while (p && p!=ancestor) { + p = p.parent; + } + return ancestor && (p === ancestor); + }, + + /** + * Returns all controls. + * + * @method + * @returns {module:enyo/Control~Control[]} An [array]{@glossary Array} of [controls]{@link module:enyo/Control~Control}. + * @public + */ + getControls: function () { + return this.controls; + }, + + /** + * Returns all non-chrome controls. + * + * @method + * @returns {module:enyo/Control~Control[]} An [array]{@glossary Array} of [controls]{@link module:enyo/Control~Control}. + * @public + */ + getClientControls: function () { + var results = []; + for (var i=0, cs=this.controls, c; (c=cs[i]); i++) { + if (!c.isChrome) { + results.push(c); + } + } + return results; + }, + + /** + * Destroys "client controls", the same set of [controls]{@link module:enyo/Control~Control} returned by + * [getClientControls()]{@link module:enyo/UiComponent~UiComponent#getClientControls}. + * + * @method + * @public + */ + destroyClientControls: function () { + var c$ = this.getClientControls(); + for (var i=0, c; (c=c$[i]); i++) { + c.destroy(); + } + }, + + /** + * @method + * @private + */ + addControl: function (ctl, before) { + // Called to add an already created control to the object's control list. It is + // not used to create controls and should likely not be called directly. + // It can be overridden to detect when controls are added. + if (before !== undefined) { + var idx = (before === null) ? 0 : this.indexOfControl(before); + this.controls.splice(idx, 0, ctl); + } else { + this.controls.push(ctl); + } + // When we add a Control, we also establish a parent. + this.addChild(ctl, before); + }, + + /** + * @method + * @private + */ + removeControl: function (ctl) { + // Called to remove a control from the object's control list. As with addControl it + // can be overridden to detect when controls are removed. + // When we remove a Control, we also remove it from its parent. + ctl.setParent(null); + return utils.remove(ctl, this.controls); + }, + + /** + * @method + * @private + */ + indexOfControl: function (ctl) { + return utils.indexOf(ctl, this.controls); + }, + + /** + * @method + * @private + */ + indexOfClientControl: function (ctl) { + return utils.indexOf(ctl, this.getClientControls()); + }, + + /** + * @method + * @private + */ + indexInContainer: function () { + return this.container.indexOfControl(this); + }, + + /** + * @method + * @private + */ + clientIndexInContainer: function () { + return this.container.indexOfClientControl(this); + }, + + /** + * @method + * @private + */ + controlAtIndex: function (idx) { + return this.controls[idx]; + }, + + /** + * Determines what the following sibling [control]{@link module:enyo/Control~Control} is for the current + * [control]{@link module:enyo/Control~Control}. + * + * @method + * @returns {module:enyo/Control~Control | null} The [control]{@link module:enyo/Control~Control} that is the] following + * sibling. If no following sibling exists, we return `null`. + * @public + */ + getNextControl: function () { + var comps = this.getParent().children, + comp, + sibling, + i; + + for (i = comps.length - 1; i >= 0; i--) { + comp = comps[i]; + if (comp === this) return sibling ? sibling : null; + if (comp.generated) sibling = comp; + } + + return null; + }, + + /** + * Children + * + * @method + * @private + */ + addChild: function (child, before) { + // if before is undefined, add to the end of the child list. + // If it's null, add to front of list, otherwise add before the + // specified control. + // + // allow delegating the child to a different container + if (this.controlParent /*&& !child.isChrome*/) { + // this.controlParent might have a controlParent, and so on; seek the ultimate parent + this.controlParent.addChild(child, before); + } else { + // NOTE: addChild drives setParent. + // It's the opposite for setContainer, where containerChanged (in Containable) + // drives addControl. + // Because of the way 'parent' is derived from 'container', this difference is + // helpful for implementing controlParent. + // By the same token, since 'parent' is derived from 'container', setParent is + // not intended to be called by client code. Therefore, the lack of parallelism + // should be private to this implementation. + // Set the child's parent property to this + child.setParent(this); + // track in children array + if (before !== undefined) { + var idx = (before === null) ? 0 : this.indexOfChild(before); + this.children.splice(idx, 0, child); + } else { + this.children.push(child); + } + } + }, + + /** + * @method + * @private + */ + removeChild: function (child) { + return utils.remove(child, this.children); + }, + + /** + * @method + * @private + */ + indexOfChild: function (child) { + return utils.indexOf(child, this.children); + }, + + /** + * @method + * @private + */ + layoutKindChanged: function () { + if (this.layout) { + this.layout.destroy(); + } + this.layout = kind.createFromKind(this.layoutKind, this); + if (this.generated) { + this.render(); + } + }, + + /** + * @method + * @private + */ + flow: function () { + if (this.layout) { + this.layout.flow(); + } + }, + + /** + * CAVEAT: currently we use the entry point for both post-render layout work *and* + * post-resize layout work. + * @method + * @private + */ + reflow: function () { + if (this.layout) { + this.layout.reflow(); + } + }, + + /** + * Call after this [control]{@link module:enyo/Control~Control} has been resized to allow it to process the + * size change. To respond to a resize, override `handleResize()` instead. Acts as syntactic + * sugar for `waterfall('onresize')`. + * + * @method + * @public + */ + resize: function () { + this.waterfall('onresize'); + this.waterfall('onpostresize'); + }, + + /** + * @method + * @private + */ + handleResize: function () { + // FIXME: once we are in the business of reflowing layouts on resize, then we have an + // inside/outside problem: some scenarios will need to reflow before child + // controls reflow, and some will need to reflow after. Even more complex scenarios + // have circular dependencies, and can require multiple passes or other resolution. + // When we can rely on CSS to manage reflows we do not have these problems. + this.reflow(); + }, + + /** + * Sends a message to all of my descendants, but not myself. You can stop a + * [waterfall]{@link module:enyo/Component~Component#waterfall} into [components]{@link module:enyo/Component~Component} + * owned by a receiving [object]{@glossary Object} by returning a truthy value from the + * {@glossary event} [handler]{@link module:enyo/Component~Component~EventHandler}. + * + * @method + * @param {String} nom - The name of the {@glossary event}. + * @param {Object} [event] - The event object to pass along. + * @param {module:enyo/Component~Component} [sender=this] - The event's originator. + * @returns {this} The callee for chaining. + * @public + */ + waterfallDown: function (nom, event, sender) { + event = event || {}; + // Note: Controls will generally be both in a $ hash and a child list somewhere. + // Attempt to avoid duplicated messages by sending only to components that are not + // UiComponent, as those components are guaranteed not to be in a child list. + // May cause a problem if there is a scenario where a UiComponent owns a pure + // Component that in turn owns Controls. + // + // waterfall to all pure components + for (var n in this.$) { + if (!(this.$[n] instanceof UiComponent)) { + this.$[n].waterfall(nom, event, sender); + } + } + // waterfall to my children + for (var i=0, cs=this.children, c; (c=cs[i]); i++) { + c.waterfall(nom, event, sender); + } + }, + + /** + * @method + * @private + */ + getBubbleTarget: function (nom, event) { + if (event.delegate) return this.owner; + else { + return ( + this.bubbleTarget + || (this.cachedBubble && this.cachedBubbleTarget[nom]) + || this.parent + || this.owner + ); + } + }, + + /** + * @method + * @private + */ + bubbleTargetChanged: function (was) { + if (was && this.cachedBubble && this.cachedBubbleTarget) { + for (var n in this.cachedBubbleTarget) { + if (this.cachedBubbleTarget[n] === was) delete this.cachedBubbleTarget[n]; + } + } + } +}); + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./master':'enyo/master','./AnimationSupport/AnimationSupport':'enyo/AnimationSupport/AnimationSupport','./AnimationSupport/AnimationInterfaceSupport':'enyo/AnimationSupport/AnimationInterfaceSupport','./Component':'enyo/Component'}],'enyo/gesture':[function (module,exports,global,require,request){ +require('enyo'); +/** + * @module enyo/gesture + */ + + +var + dispatcher = require('../dispatcher'), + dom = require('../dom'), + platform = require('../platform'), + utils = require('../utils'); + +var + drag = require('./drag'), + touchGestures = require('./touchGestures'), + gestureUtil = require('./util'); + + +/** +* Enyo supports a set of normalized events that work similarly across all supported platforms. +* These events are provided so that users can write a single set of event handlers for +* applications that run on both mobile and desktop platforms. They are needed because desktop +* and mobile platforms handle basic input differently. +* +* For more information on normalized input events and their associated properties, see the +* documentation on [Event Handling]{@linkplain $dev-guide/key-concepts/event-handling.html} +* in the Enyo Developer Guide. +* +* @module enyo/gesture +* @public +*/ +var gesture = module.exports = { + /** + * Handles "down" [events]{@glossary event}, including `mousedown` and `keydown`. This is + * responsible for the press-and-hold key repeater. + * + * @param {Event} evt - The standard {@glossary event} [object]{glossary Object}. + * @public + */ + down: function(evt) { + var e = gestureUtil.makeEvent('down', evt); + + // prepare for hold + drag.prepareHold(e); + + // enable prevention of tap event + e.preventTap = function() { + e._tapPrevented = true; + }; + + dispatcher.dispatch(e); + this.downEvent = e; + + // start hold, now that control has had a chance + // to override the holdPulse configuration + drag.beginHold(e); + }, + + /** + * Handles `mousemove` [events]{@glossary event}. + * + * @param {Event} evt - The standard {@glossary event} [object]{glossary Object}. + * @public + */ + move: function(evt) { + var e = gestureUtil.makeEvent('move', evt); + // include delta and direction v. down info in move event + e.dx = e.dy = e.horizontal = e.vertical = 0; + if (e.which && this.downEvent) { + e.dx = evt.clientX - this.downEvent.clientX; + e.dy = evt.clientY - this.downEvent.clientY; + e.horizontal = Math.abs(e.dx) > Math.abs(e.dy); + e.vertical = !e.horizontal; + } + dispatcher.dispatch(e); + }, + + /** + * Handles "up" [events]{@glossary event}, including `mouseup` and `keyup`. + * + * @param {Event} evt - The standard {@glossary event} [object]{glossary Object}. + * @public + */ + up: function(evt) { + var e = gestureUtil.makeEvent('up', evt); + + // We have added some logic to synchronize up and down events in certain scenarios (i.e. + // clicking multiple buttons with a mouse) and to generally guard against any potential + // asymmetry, but a full solution would be to maintain a map of up/down events as an + // ideal solution, for future work. + e._tapPrevented = this.downEvent && this.downEvent._tapPrevented && this.downEvent.which == e.which; + e.preventTap = function() { + e._tapPrevented = true; + }; + + dispatcher.dispatch(e); + if (!e._tapPrevented && this.downEvent && this.downEvent.which == 1) { + var target = this.findCommonAncestor(this.downEvent.target, evt.target); + + // the common ancestor of the down/up events is the target of the tap + if(target) { + if(this.supportsDoubleTap(target)) { + this.doubleTap(e, target); + } else { + this.sendTap(e, target); + } + } + } + if (this.downEvent && this.downEvent.which == e.which) { + this.downEvent = null; + } + }, + + /** + * Handles `mouseover` [events]{@glossary event}. + * + * @param {Event} evt - The standard {@glossary event} [object]{glossary Object}. + * @public + */ + over: function(evt) { + var e = gestureUtil.makeEvent('enter', evt); + dispatcher.dispatch(e); + }, + + /** + * Handles `mouseout` [events]{@glossary event}. + * + * @param {Event} evt - The standard {@glossary event} [object]{glossary Object}. + * @public + */ + out: function(evt) { + var e = gestureUtil.makeEvent('leave', evt); + dispatcher.dispatch(e); + }, + + /** + * Generates `tap` [events]{@glossary event}. + * + * @param {Event} evt - The standard {@glossary event} [object]{glossary Object}. + * @public + */ + sendTap: function(evt, target) { + var e = gestureUtil.makeEvent('tap', evt); + e.target = target; + dispatcher.dispatch(e); + }, + + /** + * @private + */ + tapData: { + id: null, + timer: null, + start: 0 + }, + + /** + * Global configuration for double tap support. If this is true, all tap events for Controls + * that do not have {@link module:enyo/Control~Control#doubleTapEnabled} explicitly set to false will be + * delayed by the {@link module:enyo/Control~Control#doubleTapInterval}. + * + * @type {Boolean} + * @default false + * @public + */ + doubleTapEnabled: false, + + /** + * Determines if the provided target node supports double tap events + * + * @param {Node} target + * @return {Boolean} + * @private + */ + supportsDoubleTap: function(target) { + var obj = dispatcher.findDispatchTarget(target); + + if(obj) { + // Control.doubleTapEnabled is a tri-value property. The default is 'inherit' + // which takes its cue from gesture's doubleTapEnabled. Values of true or false + // override the default. So, if the global is true, any truthy value on Control + // results in true. If the global is false, only an explicit true on Control + // results in true. + return this.doubleTapEnabled? !!obj.doubleTapEnabled : obj.doubleTapEnabled === true; + } else { + return false; + } + }, + + /** + * @private + */ + doubleTap: function(evt, t) { + var obj = dispatcher.findDispatchTarget(t); + + if(this.tapData.id !== obj.id) { // this is the first tap + this.resetTapData(true); + + this.tapData.id = obj.id; + this.tapData.event = evt; + this.tapData.target = t; + this.tapData.timer = setTimeout(utils.bind(this, "resetTapData", true), obj.doubleTapInterval); + this.tapData.start = utils.perfNow(); + } else { // this is the double tap + var e2 = gestureUtil.makeEvent('doubletap', evt); + e2.target = t; + e2.tapInterval = utils.perfNow() - this.tapData.start; + this.resetTapData(false); + dispatcher.dispatch(e2); + } + }, + + resetTapData: function(sendTap) { + var data = this.tapData; + + if(sendTap && data.id) { + this.sendTap(data.event, data.target); + } + + clearTimeout(data.timer); + data.id = data.start = data.event = data.target = data.timer = null; + }, + + /** + * Given two [DOM nodes]{@glossary Node}, searches for a shared ancestor (looks up + * the hierarchic [DOM]{@glossary DOM} tree of [nodes]{@glossary Node}). The shared + * ancestor node is returned. + * + * @param {Node} controlA - Control one. + * @param {Node} controlB - Control two. + * @returns {(Node|undefined)} The shared ancestor. + * @public + */ + findCommonAncestor: function(controlA, controlB) { + var p = controlB; + while (p) { + if (this.isTargetDescendantOf(controlA, p)) { + return p; + } + p = p.parentNode; + } + }, + + /** + * Given two controls, returns `true` if the `child` is inside the `parent`. + * + * @param {Node} child - The child to search for. + * @param {Node} parent - The expected parent. + * @returns {(Boolean|undefined)} `true` if the `child` is actually a child of `parent`. + */ + isTargetDescendantOf: function(child, parent) { + var c = child; + while(c) { + if (c == parent) { + return true; + } + c = c.parentNode; + } + }, + + /** + * @todo I'd rather refine the public API of gesture rather than simply forwarding the internal + * drag module but this will work in the interim. - ryanjduffy + * + * Known Consumers: + * - Spotlight.onAcceleratedKey - (prepare|begin|end)Hold() + * - Moonstone - configureHoldPulse() + */ + drag: drag +}; + +/** +* Contains various methods for gesture events. +* +* @type {object} +* @public +*/ +module.exports.events = { + /** + * Shortcut to [gesture.down()]{@link module:enyo/gesture#down}. + * + * @memberof! module:enyo/gesture# + * @method events.mousedown + * @public + */ + mousedown: function(e) { + gesture.down(e); + }, + + /** + * Shortcut to [gesture.up()]{@link module:enyo/gesture#up}. + * + * @memberof! module:enyo/gesture# + * @method events.mouseup + * @public + */ + mouseup: function(e) { + gesture.up(e); + }, + + /** + * Shortcut to [gesture.move()]{@link module:enyo/gesture#move}. + * + * @memberof! module:enyo/gesture# + * @method events.mousemove + * @public + */ + mousemove: function(e) { + gesture.move(e); + }, + + /** + * Shortcut to [gesture.over()]{@link module:enyo/gesture#over}. + * + * @memberof! module:enyo/gesture# + * @method events.mouseover + * @public + */ + mouseover: function(e) { + gesture.over(e); + }, + + /** + * Shortcut to [gesture.out()]{@link module:enyo/gesture#out}. + * + * @memberof! module:enyo/gesture# + * @method events.mouseout + * @public + */ + mouseout: function(e) { + gesture.out(e); + } +}; + +// Firefox mousewheel handling +dom.requiresWindow(function() { + if (document.addEventListener) { + document.addEventListener('DOMMouseScroll', function(inEvent) { + var e = utils.clone(inEvent); + e.preventDefault = function() { + inEvent.preventDefault(); + }; + e.type = 'mousewheel'; + var p = e.VERTICAL_AXIS == e.axis ? 'wheelDeltaY' : 'wheelDeltaX'; + e[p] = e.detail * -40; + dispatcher.dispatch(e); + }, false); + } +}); + +/** +* @private +*/ +var handlers = { + touchstart: true, + touchmove: true, + touchend: true +}; + +/** +* @private +*/ +dispatcher.features.push(function (e) { + var type = e.type; + + // NOTE: beware of properties in gesture.events and drag inadvertently mapped to event types + if (gesture.events[type]) { + gesture.events[type](e); + } + if (!platform.gesture && platform.touch && handlers[type]) { + touchGestures[type](e); + } + if (drag[type]) { + drag[type](e); + } +}); + +},{'../dispatcher':'enyo/dispatcher','../dom':'enyo/dom','../platform':'enyo/platform','../utils':'enyo/utils','./drag':'enyo/gesture/drag','./touchGestures':'enyo/gesture/touchGestures','./util':'enyo/gesture/util'}],'enyo/ViewController':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/ViewController~ViewController} kind. +* @module enyo/ViewController +*/ + +var + kind = require('./kind'), + utils = require('./utils'); + +var + Controller = require('./Controller'), + UiComponent = require('./UiComponent'); + +var + Dom = require('./dom'); + +/** +* {@link module:enyo/ViewController~ViewController} is designed to manage the lifecycle of a particular view +* ({@link module:enyo/Control~Control}) that it owns. It is capable of controlling when a view is inserted into +* the DOM and where, managing [events]{@glossary event} bubbled from the view, and isolating (or +* encapsulating) the entire view hierarchy below it. Alternatively, it may be implemented as a +* [component]{@link module:enyo/Component~Component} in a larger hierarchy, in which case it will inject its view +* into its parent rather than directly into the DOM. And, of course, a ViewController may be +* used as the `controller` property of another view, although this usage will (by default) +* result in the removal of its own view from the {@link module:enyo/Component~Component} bubbling hierarchy. +* +* Note that `enyo.ViewController` may have components defined in its +* `components` [array]{@glossary Array}, but these components should +* not be `enyo.Controls`. +* +* @class ViewController +* @extends module:enyo/Controller~Controller +* @public +*/ +module.exports = kind( + /** @lends module:enyo/ViewController~ViewController.prototype */ { + + name: 'enyo.ViewController', + + /** + * @private + */ + kind: Controller, + + /** + * The `view` property may either be a [constructor]{@glossary constructor}, an + * instance of {@link module:enyo/Control~Control}, an [object]{@glossary Object} + * description of the view ([object literal/hash]), or `null` if it will be + * set later. Setting this property to a constructor or string naming a kind + * will automatically create an instance of that kind according to this + * controller's settings. If the `view` is set to an instance, it will be + * rendered according to the properties of the controller. If this property + * is a constructor, it will be preserved in the + * [viewKind]{@link module:enyo/ViewController~ViewController#viewKind} property. Once + * initialization is complete, the instance of this controller's view will be + * available via this property. + * + * @type {Control|Function|Object} + * @default null + * @public + */ + view: null, + + /** + * The preserved [kind]{@glossary kind} for this controller's view. You may + * set this to a [constructor]{@glossary constructor} (or the + * [view]{@link module:enyo/ViewController~ViewController#view} property). In either case, if a + * view is set explicitly or this property is used, the constructor will be + * available via this property. + * + * @type {Function} + * @default null + * @public + */ + viewKind: null, + + /** + * Designates where the controller's view will render. This should be a + * string consisting of either `'document.body'` (the default) or the DOM id + * of a node (either inserted by an {@link module:enyo/Control~Control} or static HTML + * already in the `document.body`). If the controller has a parent (because + * it was instantiated as a component in an `enyo.Control`, this property + * will be ignored and the view will instead be rendered in the parent. This + * will not happen if the controller is a component of {@link module:enyo/Component~Component} + * or is set as the `controller` property of an `enyo.Control`. + * + * @type {String} + * @default 'document.body' + * @public + */ + renderTarget: 'document.body', + + /** + * When the view of the controller has its [destroy()]{@link module:enyo/Control~Control#destroy} + * method called, it automatically triggers its own removal from the controller's + * [view]{@link module:enyo/ViewController~ViewController#view} property. By default, the controller + * will not create a new view (from [viewKind]{@link module:enyo/ViewController~ViewController#viewKind}) + * automatically unless this flag is set to `true`. + * + * @type {Boolean} + * @default false + * @public + */ + resetView: false, + + /** + * Renders the controller's view, if possible. If the controller is a + * component of a [UiComponent]{@link module:enyo/UiComponent~UiComponent}, the view will be + * rendered into its container; otherwise, the view will be rendered into the + * controller's [renderTarget]{@link module:enyo/ViewController~ViewController#renderTarget}. If + * the view is already rendered, this method will do nothing. + * + * @param {String} [target] - When specified, this value will be used instead of + * [renderTarget]{@link module:enyo/ViewController~ViewController#renderTarget}. + * @public + */ + render: function (target) { + var v = this.view, + t = target || this.renderTarget; + if (v) { + if (v.hasNode() && v.generated) { return; } + // here we test to see if we need to render it into our target node or the container + if (this.container) { + v.render(); + } else { + v.renderInto(Dom.byId(t) || utils.getPath.call(window, t)); + } + } + }, + + /** + * Renders the view into the specified `target` and sets the + * [renderTarget]{@link module:enyo/ViewController~ViewController#renderTarget} property to + * `target`. + * + * @param {String} target - Where the view will be rendered into. + * @public + */ + renderInto: function (target) { + this.render((this.renderTarget=target)); + }, + + /** + * Responds to changes in the controller's [view]{@link module:enyo/ViewController~ViewController#view} + * property during initialization or whenever `set('view', ...)` is called. + * If a [constructor]{@glossary constructor} is found, it will be instanced + * or resolved from a [string]{@glossary String}. If a previous view exists + * and the controller is its [owner]{@link module:enyo/Component~Component#owner}, it will be + * destroyed; otherwise, it will simply be removed. + * + * @private + */ + viewChanged: function (previous) { + if (previous) { + previous.set('bubbleTarget', null); + if (previous.owner === this && !previous.destroyed) { + previous.destroy(); + } + if (previous.destroyed && !this.resetView) { + return; + } + } + var v = this.view; + + // if it is a function we need to instance it + if (typeof v == 'function') { + // save the constructor for later + this.viewKind = v; + v = null; + } + + if ((!v && this.viewKind) || (v && typeof v == 'object' && !(v instanceof UiComponent))) { + var d = (typeof v == 'object' && v !== null && !v.destroyed && v) || {kind: this.viewKind}, + s = this; + // in case it isn't set... + d.kind = d.kind || this.viewKind || kind.getDefaultCtor(); + v = this.createComponent(d, { + owner: this, + // if this controller is a component of a UiComponent kind then it + // will have assigned a container that we can add to the child + // so it will register as a child and control to be rendered in the + // correct location + container: this.container || null, + bubbleTarget: this + }); + v.extend({ + destroy: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + // if the bubble target is the view contorller then we need to + // let it know we've been destroyed + if (this.bubbleTarget === s) { + this.bubbleTarget.set('view', null); + } + }; + }) + }); + } else if (v && v instanceof UiComponent) { + // make sure we grab the constructor from an instance so we know what kind + // it was to recreate later if necessary + if (!this.viewKind) { + this.viewKind = v.ctor; + } + v.set('bubbleTarget', this); + } + this.view = v; + }, + + /** + * @method + * @private + */ + create: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + this.viewChanged(); + }; + }), + + /** + * @method + * @private + */ + destroy: kind.inherit(function (sup) { + return function () { + this.view = null; + this.viewKind = null; + sup.apply(this, arguments); + }; + }), + /** + The `controller` can't be the instance owner of its child view for event + propagation reasons. When this flag is `true`, it ensures that events will + not be handled multiple times (by the `controller` and its `view` + separately). + */ + notInstanceOwner: true +}); + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./Controller':'enyo/Controller','./UiComponent':'enyo/UiComponent','./dom':'enyo/dom'}],'enyo/Control':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Control~Control} kind. +* @module enyo/Control +*/ + +var + kind = require('../kind'), + utils = require('../utils'), + platform = require('../platform'), + dispatcher = require('../dispatcher'), + options = require('../options'), + roots = require('../roots'); + +var + AccessibilitySupport = require('../AccessibilitySupport'), + UiComponent = require('../UiComponent'), + HTMLStringDelegate = require('../HTMLStringDelegate'), + Dom = require('../dom'); + +var + fullscreen = require('./fullscreen'), + FloatingLayer = require('./floatingLayer'); + +// While the namespace isn't needed here, gesture is required for ontap events for which Control +// has a handler. Bringing them all in now for the time being. +require('../gesture'); + +var nodePurgatory; + +/** +* Called by `Control.teardownRender()`. In certain circumstances, +* we need to temporarily keep a DOM node around after tearing down +* because we're still acting on a stream of touch events emanating +* from the node. See `Control.retainNode()` for more information. +* +* @private +*/ +function storeRetainedNode (control) { + var p = getNodePurgatory(), + n = control._retainedNode; + if (n) { + p.appendChild(n); + } + control._retainedNode = null; +} + +/** +* Called (via a callback) when it's time to release a DOM node +* that we've retained. +* +* @private +*/ +function releaseRetainedNode (retainedNode) { + var p = getNodePurgatory(); + if (retainedNode) { + p.removeChild(retainedNode); + } +} + +/** +* Lazily add a hidden `
` to `document.body` to serve as a +* container for retained DOM nodes. +* +* @private +*/ +function getNodePurgatory () { + var p = nodePurgatory; + if (!p) { + p = nodePurgatory = document.createElement("div"); + p.id = "node_purgatory"; + p.style.display = "none"; + document.body.appendChild(p); + } + return p; +} + +/** +* {@link module:enyo/Control~Control} is a [component]{@link module:enyo/UiComponent~UiComponent} that controls +* a [DOM]{@glossary DOM} [node]{@glossary Node} (i.e., an element in the user +* interface). Controls are generally visible and the user often interacts with +* them directly. While things like buttons and input boxes are obviously +* controls, in Enyo, a control may be as simple as a text item or as complex +* as an entire application. Both inherit the same basic core capabilities from +* this kind. +* +* For more information, see the documentation on +* [Controls]{@linkplain $dev-guide/key-concepts/controls.html} in the +* Enyo Developer Guide. +* +* **If you make changes to `enyo.Control`, be sure to add or update the +* appropriate unit tests.** +* +* @class Control +* @extends module:enyo/UiComponent~UiComponent +* @ui +* @public +*/ +var Control = module.exports = kind( + /** @lends module:enyo/Control~Control.prototype */ { + + name: 'enyo.Control', + + /** + * @private + */ + kind: UiComponent, + + /** + * @private + */ + mixins: options.accessibility ? [AccessibilitySupport] : null, + + /** + * @type {String} + * @default 'module:enyo/Control~Control' + * @public + */ + defaultKind: null, // set after the fact + + /** + * The [DOM node]{@glossary DOM} tag name that should be created. + * + * @type {String} + * @default 'div' + * @public + */ + tag: 'div', + + /** + * A [hash]{@glossary Object} of attributes to be applied to the created + * [DOM]{@glossary DOM} node. + * + * @type {Object} + * @default null + * @public + */ + attributes: null, + + /** + * [Boolean]{@glossary Boolean} flag indicating whether this element should + * "fit", or fill its container's size. + * + * @type {Boolean} + * @default null + * @public + */ + fit: null, + + /** + * [Boolean]{@glossary Boolean} flag indicating whether HTML is allowed in + * this control's [content]{@link module:enyo/Control~Control#content} property. If `false` + * (the default), HTML will be encoded into [HTML entities]{@glossary entity} + * (e.g., `<` and `>`) for literal visual representation. + * + * @type {Boolean} + * @default null + * @public + */ + allowHtml: false, + + /** + * Mimics the HTML `style` attribute. + * + * @type {String} + * @default '' + * @public + */ + style: '', + + /** + * @private + */ + kindStyle: '', + + /** + * Mimics the HTML `class` attribute. + * + * @type {String} + * @default '' + * @public + */ + classes: '', + + /** + * @private + */ + kindClasses: '', + + /** + * [Classes]{@link module:enyo/Control~Control#classes} that are applied to all controls. + * + * @type {String} + * @default '' + * @public + */ + controlClasses: '', + + /** + * The text-based content of the Control. If the [allowHtml]{@link module:enyo/Control~Control#allowHtml} + * flag is set to `true`, you may set this property to an HTML string. + * @public + */ + content: '', + + /** + * If true or 'inherit' and enyo/gesture#doubleTabEnabled == true, will fire a doubletap + * event, and will temporarily suppress a single tap while waiting for a double tap. + * + * @type {String|Boolean} + * @default 'inherit' + * @public + */ + doubleTapEnabled: 'inherit', + + /** + * Time in milliseconds to wait to detect a double tap + * + * @type {Number} + * @default 300 + * @public + */ + doubleTapInterval: 300, + + /** + * If set to `true`, the [control]{@link module:enyo/Control~Control} will not be rendered until its + * [showing]{@link module:enyo/Control~Control#showing} property has been set to `true`. This can be used + * directly or is used by some widgets to control when children are rendered. + * + * It is important to note that setting this to `true` will _force_ + * [canGenerate]{@link module:enyo/Control~Control#canGenerate} and [showing]{@link module:enyo/Control~Control#showing} + * to be `false`. Arbitrarily modifying the values of these properties prior to its initial + * render may have unexpected results. + * + * Once a control has been shown/rendered with `renderOnShow` `true` the behavior will not + * be used again. + * + * @type {Boolean} + * @default false + * @public + */ + renderOnShow: false, + + /** + * @todo Find out how to document "handlers". + * @public + */ + handlers: { + ontap: 'tap', + onShowingChanged: 'showingChangedHandler' + }, + + /** + * @private + */ + strictlyInternalEvents: {onenter: 1, onleave: 1}, + + /** + * @private + */ + isInternalEvent: function (event) { + var rdt = dispatcher.findDispatchTarget(event.relatedTarget); + return rdt && rdt.isDescendantOf(this); + }, + + // ................................. + // DOM NODE MANIPULATION API + + /** + * Gets the bounds for this control. The `top` and `left` properties returned + * by this method represent the control's positional distance in pixels from + * either A) the first parent of this control that is absolutely or relatively + * positioned, or B) the `document.body`. + * + * This is a shortcut convenience method for {@link module:enyo/dom#getBounds}. + * + * @returns {Object} An [object]{@glossary Object} containing `top`, `left`, + * `width`, and `height` properties. + * @public + */ + getBounds: function () { + var node = this.hasNode(), + bounds = node && Dom.getBounds(node); + + return bounds || {left: undefined, top: undefined, width: undefined, height: undefined}; + }, + + /** + * Sets the absolute/relative position and/or size for this control. Values + * of `null` or `undefined` for the `bounds` properties will be ignored. You + * may optionally specify a `unit` (i.e., a valid CSS measurement unit) as a + * [string]{@glossary String} to be applied to each of the position/size + * assignments. + * + * @param {Object} bounds - An [object]{@glossary Object}, optionally + * containing one or more of the following properties: `width`, `height`, + * `top`, `right`, `bottom`, and `left`. + * @param {String} [unit='px'] + * @public + */ + setBounds: function (bounds, unit) { + var newStyle = '', + extents = ['width', 'height', 'left', 'top', 'right', 'bottom'], + i = 0, + val, + ext; + + // if no unit is supplied, we default to pixels + unit = unit || 'px'; + + for (; (ext = extents[i]); ++i) { + val = bounds[ext]; + if (val || val === 0) { + newStyle += (ext + ':' + val + (typeof val == 'string' ? '' : unit) + ';'); + } + } + + this.set('style', this.style + newStyle); + }, + + /** + * Gets the bounds for this control. The `top` and `left` properties returned + * by this method represent the control's positional distance in pixels from + * `document.body`. To get the bounds relative to this control's parent(s), + * use [getBounds()]{@link module:enyo/Control~Control#getBounds}. + * + * This is a shortcut convenience method for {@link module:enyo/dom#getAbsoluteBounds}. + * + * @returns {Object} An [object]{@glossary Object} containing `top`, `left`, + * `width`, and `height` properties. + * @public + */ + getAbsoluteBounds: function () { + var node = this.hasNode(), + bounds = node && Dom.getAbsoluteBounds(node); + + return bounds || { + left: undefined, + top: undefined, + width: undefined, + height: undefined, + bottom: undefined, + right: undefined + }; + }, + + /** + * Shortcut method to set [showing]{@link module:enyo/Control~Control#showing} to `true`. + * + * @public + */ + show: function () { + this.set('showing', true); + }, + + /** + * Shortcut method to set [showing]{@link module:enyo/Control~Control#showing} to `false`. + * + * @public + */ + hide: function () { + this.set('showing', false); + }, + + /** + * Sets this control to be [focused]{@glossary focus}. + * + * @public + */ + focus: function () { + if (this.hasNode()) this.node.focus(); + }, + + /** + * [Blurs]{@glossary blur} this control. (The opposite of + * [focus()]{@link module:enyo/Control~Control#focus}.) + * + * @public + */ + blur: function () { + if (this.hasNode()) this.node.blur(); + }, + + /** + * Determines whether this control currently has the [focus]{@glossary focus}. + * + * @returns {Boolean} Whether this control has focus. `true` if the control + * has focus; otherwise, `false`. + * @public + */ + hasFocus: function () { + if (this.hasNode()) return document.activeElement === this.node; + }, + + /** + * Determines whether this control's [DOM node]{@glossary Node} has been created. + * + * @returns {Boolean} Whether this control's [DOM node]{@glossary Node} has + * been created. `true` if it has been created; otherwise, `false`. + * @public + */ + hasNode: function () { + return this.generated && (this.node || this.findNodeById()); + }, + + /** + * Gets the requested property (`name`) from the control's attributes + * [hash]{@glossary Object}, from its cache of node attributes, or, if it has + * yet to be cached, from the [node]{@glossary Node} itself. + * + * @param {String} name - The attribute name to get. + * @returns {(String|Null)} The value of the requested attribute, or `null` + * if there isn't a [DOM node]{@glossary Node} yet. + * @public + */ + getAttribute: function (name) { + var node; + + // TODO: This is a fixed API assuming that no changes will happen to the DOM that + // do not use it...original implementation of this method used the node's own + // getAttribute method every time it could but we really only need to do that if we + // weren't the ones that set the value to begin with -- in slow DOM situations this + // could still be faster but it needs to be verified + if (this.attributes.hasOwnProperty(name)) return this.attributes[name]; + else { + node = this.hasNode(); + + // we store the value so that next time we'll know what it is + /*jshint -W093 */ + return (this.attributes[name] = (node ? node.getAttribute(name) : null)); + /*jshint +W093 */ + } + }, + + /** + * Assigns an attribute to a control's [node]{@glossary Node}. Assigning + * `name` a value of `null`, `false`, or the empty string `("")` will remove + * the attribute from the node altogether. + * + * @param {String} name - Attribute name to assign/remove. + * @param {(String|Number|null)} value - The value to assign to `name` + * @returns {this} Callee for chaining. + * @public + */ + setAttribute: function (name, value) { + var attrs = this.attributes, + node = this.hasNode(), + delegate = this.renderDelegate || Control.renderDelegate; + + if (name) { + attrs[name] = value; + + if (node) { + if (value == null || value === false || value === '') { + node.removeAttribute(name); + } else node.setAttribute(name, value); + } + + delegate.invalidate(this, 'attributes'); + } + + return this; + }, + + /** + * Reads the `name` property directly from the [node]{@glossary Node}. You + * may provide a default (`def`) to use if there is no node yet. + * + * @param {String} name - The [node]{@glossary Node} property name to get. + * @param {*} def - The default value to apply if there is no node. + * @returns {String} The value of the `name` property, or `def` if the node + * was not available. + * @public + */ + getNodeProperty: function (name, def) { + return this.hasNode() ? this.node[name] : def; + }, + + /** + * Sets the value of a property (`name`) directly on the [node]{@glossary Node}. + * + * @param {String} name - The [node]{@glossary Node} property name to set. + * @param {*} value - The value to assign to the property. + * @returns {this} The callee for chaining. + * @public + */ + setNodeProperty: function (name, value) { + if (this.hasNode()) this.node[name] = value; + return this; + }, + + /** + * Appends additional content to this control. + * + * @param {String} content - The new string to add to the end of the `content` + * property. + * @returns {this} The callee for chaining. + * @public + */ + addContent: function (content) { + return this.set('content', this.get('content') + content); + }, + + // ................................. + + // ................................. + // STYLE/CLASS API + + /** + * Determines whether this control has the class `name`. + * + * @param {String} name - The name of the class (or classes) to check for. + * @returns {Boolean} Whether the control has the class `name`. + * @public + */ + hasClass: function (name) { + return name && (' ' + this.classes + ' ').indexOf(' ' + name + ' ') > -1; + }, + + /** + * Adds the specified class to this control's list of classes. + * + * @param {String} name - The name of the class to add. + * @returns {this} The callee for chaining. + * @public + */ + addClass: function (name) { + var classes = this.classes || ''; + + // NOTE: Because this method accepts a string and for efficiency does not wish to + // parse it to determine if it is actually multiple classes we later pull a trick + // to keep it normalized and synchronized with our attributes hash and the node's + if (!this.hasClass(name)) { + + // this is hooked + this.set('classes', classes + (classes ? (' ' + name) : name)); + } + + return this; + }, + + /** + * Removes the specified class from this control's list of classes. + * + * **Note: It is not advisable to pass a string of multiple, space-delimited + * class names into this method. Instead, call the method once for each class + * name that you want to remove.** + * + * @param {String} name - The name of the class to remove. + * @returns {this} The callee for chaining. + * @public + */ + removeClass: function (name) { + var classes = this.classes; + + if (name) { + this.set('classes', (' ' + classes + ' ').replace(' ' + name + ' ', ' ').trim()); + } + + return this; + }, + + /** + * Adds or removes the specified class conditionally, based on the state + * of the `add` argument. + * + * @param {String} name - The name of the class to add or remove. + * @param {Boolean} add - If `true`, `name` will be added as a class; if + * `false`, it will be removed. + * @returns {this} The callee for chaining. + * @public + */ + addRemoveClass: function (name, add) { + return name ? this[add ? 'addClass' : 'removeClass'](name) : this; + }, + + /** + * @private + */ + classesChanged: function () { + var classes = this.classes, + node = this.hasNode(), + attrs = this.attributes, + delegate = this.renderDelegate || Control.renderDelegate; + + if (node) { + if (classes || this.kindClasses) { + node.setAttribute('class', classes || this.kindClasses); + } else node.removeAttribute('class'); + + this.classes = classes = node.getAttribute('class'); + } + + // we need to update our attributes.class value and flag ourselves to be + // updated + attrs['class'] = classes; + + // we want to notify the delegate that the attributes have changed in case it wants + // to handle this is some special way + delegate.invalidate(this, 'attributes'); + }, + + /** + * Applies a CSS style directly to the control. Use the `prop` argument to + * specify the CSS property name you'd like to set, and `value` to specify + * the desired value. Setting `value` to `null` will remove the CSS property + * `prop` altogether. + * + * @param {String} prop - The CSS property to assign. + * @param {(String|Number|null|undefined)} value - The value to assign to + * `prop`. Setting a value of `null`, `undefined`, or the empty string `("")` + * will remove the property `prop` from the control. + * @returns {this} Callee for chaining. + * @public + */ + applyStyle: function (prop, value) { + + // NOTE: This method deliberately avoids calling set('style', ...) for performance + // as it will have already been parsed by the browser so we pass it on via the + // notification system which is the same + + // TODO: Wish we could delay this potentially... + // if we have a node we render the value immediately and update our style string + // in the process to keep them synchronized + var node = this.hasNode(), + style = this.style, + delegate = this.renderDelegate || Control.renderDelegate; + + // FIXME: This is put in place for a Firefox bug where setting a style value of a node + // via its CSSStyleDeclaration object (by accessing its node.style property) does + // not work when using a CSS property name that contains one or more dash, and requires + // setting the property via the JavaScript-style property name. This fix should be + // removed once this issue has been resolved in the Firefox mainline and its variants + // (it is currently resolved in the 36.0a1 nightly): + // https://bugzilla.mozilla.org/show_bug.cgi?id=1083457 + if (node && (platform.firefox < 35 || platform.firefoxOS || platform.androidFirefox)) { + prop = prop.replace(/-([a-z])/gi, function(match, submatch) { + return submatch.toUpperCase(); + }); + } + + if (value !== null && value !== '' && value !== undefined) { + // update our current cached value + if (node) { + node.style[prop] = value; + + // cssText is an internal property used to help know when to sync and not + // sync with the node in styleChanged + this.style = this.cssText = node.style.cssText; + + // otherwise we have to try and prepare it for the next time it is rendered we + // will need to update it because it will not be synchronized + } else this.set('style', style + (' ' + prop + ':' + value + ';')); + } else { + + // in this case we are trying to clear the style property so if we have the node + // we let the browser handle whatever the value should be now and otherwise + // we have to parse it out of the style string and wait to be rendered + + if (node) { + node.style[prop] = ''; + this.style = this.cssText = node.style.cssText; + + // we need to invalidate the style for the delegate + delegate.invalidate(this, 'style'); + } else { + + // this is a rare case to nullify the style of a control that is not + // rendered or does not have a node + style = style.replace(new RegExp( + // This looks a lot worse than it is. The complexity stems from needing to + // match a url container that can have other characters including semi- + // colon and also that the last property may/may-not end with one + '\\s*' + prop + '\\s*:\\s*[a-zA-Z0-9\\ ()_\\-\'"%,]*(?:url\\(.*\\)\\s*[a-zA-Z0-9\\ ()_\\-\'"%,]*)?\\s*(?:;|;?$)', + 'gi' + ),''); + this.set('style', style); + } + } + // we need to invalidate the style for the delegate -- regardless of whether or + // not the node exists to ensure that the tag is updated properly the next time + // it is rendered + delegate.invalidate(this, 'style'); + + return this; + }, + + /** + * Allows the addition of several CSS properties and values at once, via a + * single string, similar to how the HTML `style` attribute works. + * + * @param {String} css - A string containing one or more valid CSS styles. + * @returns {this} The callee for chaining. + * @public + */ + addStyles: function (css) { + var key, + newStyle = ''; + + if (typeof css == 'object') { + for (key in css) newStyle += (key + ':' + css[key] + ';'); + } else newStyle = css || ''; + + this.set('style', this.style + newStyle); + }, + + /** + * @private + */ + styleChanged: function () { + var delegate = this.renderDelegate || Control.renderDelegate; + + // if the cssText internal string doesn't match then we know style was set directly + if (this.cssText !== this.style) { + + // we need to render the changes and synchronize - this means that the style + // property was set directly so we will reset it prepending it with the original + // style (if any) for the kind and keeping whatever the browser is keeping + if (this.hasNode()) { + this.node.style.cssText = this.kindStyle + (this.style || ''); + // now we store the parsed version + this.cssText = this.style = this.node.style.cssText; + } + + // we need to ensure that the delegate has an opportunity to handle this change + // separately if it needs to + delegate.invalidate(this, 'style'); + } + }, + + /** + * Retrieves a control's CSS property value. This doesn't just pull the + * assigned value of `prop`; it returns the browser's understanding of `prop`, + * the "computed" value. If the control isn't been rendered yet, and you need + * a default value (such as `0`), include it in the arguments as `def`. + * + * @param {String} prop - The property name to get. + * @param {*} [def] - An optional default value, in case the control isn't + * rendered yet. + * @returns {(String|Number)} The computed value of `prop`, as the browser + * sees it. + * @public + */ + getComputedStyleValue: function (prop, def) { + return this.hasNode() ? Dom.getComputedStyleValue(this.node, prop) : def; + }, + + /** + * @private + */ + findNodeById: function () { + return this.id && (this.node = Dom.byId(this.id)); + }, + + /** + * @private + */ + idChanged: function (was) { + if (was) Control.unregisterDomEvents(was); + if (this.id) { + Control.registerDomEvents(this.id, this); + this.setAttribute('id', this.id); + } + }, + + /** + * @private + */ + contentChanged: function () { + var delegate = this.renderDelegate || Control.renderDelegate; + delegate.invalidate(this, 'content'); + }, + + /** + * If the control has been generated, re-flows the control. + * + * @public + */ + beforeChildRender: function () { + // if we are generated, we should flow before rendering a child; + // if not, the render context isn't ready anyway + if (this.generated) this.flow(); + }, + + /** + * @private + */ + showingChanged: function (was) { + var nextControl; + // if we are changing from not showing to showing we attempt to find whatever + // our last known value for display was or use the default + if (!was && this.showing) { + this.applyStyle('display', this._display || ''); + + // note the check for generated and canGenerate as changes to canGenerate will force + // us to ignore the renderOnShow value so we don't undo whatever the developer was + // intending + if (!this.generated && !this.canGenerate && this.renderOnShow) { + nextControl = this.getNextControl(); + if (nextControl && !this.addBefore) this.addBefore = nextControl; + this.set('canGenerate', true); + this.render(); + } + + this.sendShowingChangedEvent(was); + } + + // if we are supposed to be hiding the control then we need to cache our current + // display state + else if (was && !this.showing) { + this.sendShowingChangedEvent(was); + // we can't truly cache this because it _could_ potentially be set to multiple + // values throughout its lifecycle although that seems highly unlikely... + this._display = this.hasNode() ? this.node.style.display : ''; + this.applyStyle('display', 'none'); + } + + }, + + /** + * @private + */ + renderOnShowChanged: function () { + // ensure that the default value assigned to showing is actually a boolean + // and that it is only true if the renderOnShow is also false + this.showing = ((!!this.showing) && !this.renderOnShow); + // we want to check and make sure that the canGenerate value is correct given + // the state of renderOnShow + this.canGenerate = (this.canGenerate && !this.renderOnShow); + }, + + /** + * @private + */ + sendShowingChangedEvent: function (was) { + var waterfall = (was === true || was === false), + parent = this.parent; + + // make sure that we don't trigger the waterfall when this method + // is arbitrarily called during _create_ and it should only matter + // that it changed if our parent's are all showing as well + if (waterfall && (parent ? parent.getAbsoluteShowing(true) : true)) { + this.waterfall('onShowingChanged', {originator: this, showing: this.showing}); + } + }, + + /** + * Returns `true` if this control and all parents are showing. + * + * @param {Boolean} ignoreBounds - If `true`, it will not force a layout by retrieving + * computed bounds and rely on the return from [showing]{@link module:enyo/Control~Control#showing} + * exclusively. + * @returns {Boolean} Whether the control is showing (visible). + * @public + */ + getAbsoluteShowing: function (ignoreBounds) { + var bounds = !ignoreBounds ? this.getBounds() : null, + parent = this.parent; + + if (!this.generated || this.destroyed || !this.showing || (bounds && + bounds.height === 0 && bounds.width === 0)) { + return false; + } + + if (parent && parent.getAbsoluteShowing) { + + // we actually don't care what the parent says if it is the floating layer + if (!this.parentNode || (this.parentNode !== Control.floatingLayer.hasNode())) { + return parent.getAbsoluteShowing(ignoreBounds); + } + } + + return true; + }, + + /** + * Handles the `onShowingChanged` event that is waterfalled by controls when + * their `showing` value is modified. If the control is not showing itself + * already, it will not continue the waterfall. Overload this method to + * provide additional handling for this event. + * + * @private + */ + showingChangedHandler: function (sender, event) { + // If we have deferred a reflow, do it now... + if (this.showing && this._needsReflow) { + this.reflow(); + } + + // Then propagate `onShowingChanged` if appropriate + return sender === this ? false : !this.showing; + }, + + /** + * Overriding reflow() so that we can take `showing` into + * account and defer reflowing accordingly. + * + * @private + */ + reflow: function () { + if (this.layout) { + this._needsReflow = this.showing ? this.layout.reflow() : true; + } + }, + + /** + * @private + */ + fitChanged: function () { + this.parent.reflow(); + }, + + /** + * Determines whether we are in fullscreen mode or not. + * + * @returns {Boolean} Whether we are currently in fullscreen mode. + * @public + */ + isFullscreen: function () { + return (this.hasNode() && this.node === Control.Fullscreen.getFullscreenElement()); + }, + + /** + * Requests that this control be displayed fullscreen (like a video + * container). If the request is granted, the control fills the screen and + * `true` is returned; if the request is denied, the control is not resized + * and `false` is returned. + * + * @returns {Boolean} `true` on success; otherwise, `false`. + * @public + */ + requestFullscreen: function () { + if (!this.hasNode()) return false; + + if (Control.Fullscreen.requestFullscreen(this)) { + return true; + } + + return false; + }, + + /** + * Ends fullscreen mode for this control. + * + * @returns {Boolean} If the control was in fullscreen mode before this + * method was called, it is taken out of that mode and `true` is returned; + * otherwise, `false` is returned. + * @public + */ + cancelFullscreen: function() { + if (this.isFullscreen()) { + Control.Fullscreen.cancelFullscreen(); + return true; + } + + return false; + }, + + // ................................. + + // ................................. + // RENDER-SCHEME API + + /** + * Indicates whether the control is allowed to be generated, i.e., rendered + * into the [DOM]{@glossary DOM} tree. + * + * @type {Boolean} + * @default true + * @public + */ + canGenerate: true, + + /** + * Indicates whether the control is visible. + * + * @type {Boolean} + * @default true + * @public + */ + showing: true, + + /** + * The [node]{@glossary Node} that this control will be rendered into. + * + * @type {module:enyo/Control~Control} + * @default null + * @public + */ + renderDelegate: null, + + /** + * Indicates whether the control has been generated yet. + * + * @type {Boolean} + * @default false + * @private + */ + generated: false, + + /** + * Forces the control to be rendered. You should use this sparingly, as it + * can be costly, but it may be necessary in cases where a control or its + * contents have been updated surreptitiously. + * + * @returns {this} The callee for chaining. + * @public + */ + render: function () { + + // prioritize the delegate set for this control otherwise use the default + var delegate = this.renderDelegate || Control.renderDelegate; + + // the render delegate acts on the control + delegate.render(this); + + return this; + }, + + /** + * Takes this control and drops it into a (new/different) + * [DOM node]{@glossary Node}. This will replace any existing nodes in the + * target `parentNode`. + * + * @param {Node} parentNode - The new parent of this control. + * @param {Boolean} preventRooting - If `true`, this control will not be treated as a root + * view and will not be added to the set of roots. + * @returns {this} The callee for chaining. + * @public + */ + renderInto: function (parentNode, preventRooting) { + var delegate = this.renderDelegate || Control.renderDelegate, + noFit = this.fit === false; + + // attempt to retrieve the parentNode + parentNode = Dom.byId(parentNode); + + // teardown in case of previous render + delegate.teardownRender(this); + + if (parentNode == document.body && !noFit) this.setupBodyFitting(); + else if (this.fit) this.addClass('enyo-fit enyo-clip'); + + // for IE10 support, we want full support over touch actions in enyo-rendered areas + this.addClass('enyo-no-touch-action'); + + // add css to enable hw-accelerated scrolling on non-android platforms + // ENYO-900, ENYO-901 + this.setupOverflowScrolling(); + + // if there are unflushed body classes we flush them now... + Dom.flushBodyClasses(); + + // we inject this as a root view because, well, apparently that is just an assumption + // we've been making... + if (!preventRooting) { + roots.addToRoots(this); + } + + // now let the delegate render it the way it needs to + delegate.renderInto(this, parentNode); + + Dom.updateScaleFactor(); + + return this; + }, + + /** + * A function that fires after the control has rendered. This performs a + * reflow. + * + * @public + */ + rendered: function () { + var child, + i = 0; + + // CAVEAT: Currently we use one entry point ('reflow') for + // post-render layout work *and* post-resize layout work. + this.reflow(); + + for (; (child = this.children[i]); ++i) { + if (child.generated) child.rendered(); + } + }, + + /** + * You should generally not need to call this method in your app code. + * It is used internally by some Enyo UI libraries to handle a rare + * issue that sometimes arises when using a virtualized list or repeater + * on a touch device. + * + * This issue occurs when a gesture (e.g. a drag) originates with a DOM + * node that ends up being destroyed in mid-gesture as the list updates. + * When the node is destroyed, the stream of DOM events representing the + * gesture stops, causing the associated action to stop or otherwise + * fail. + * + * You can prevent this problem from occurring by calling `retainNode` + * on the {@link module:enyo/Control~Control} from which the gesture originates. Doing + * so will cause Enyo to keep the DOM node around (hidden from view) + * until you explicitly release it. You should call `retainNode` in the + * event handler for the event that starts the gesture. + * + * `retainNode` returns a function that you must call when the gesture + * ends to release the node. Make sure you call this function to avoid + * "leaking" the DOM node (failing to remove it from the DOM). + * + * @param {Node} node - Optional. Defaults to the node associated with + * the Control (`Control.node`). You can generally omit this parameter + * when working with {@link module:enyo/DataList~DataList} or {@link module:enyo/DataGridList~DataGridList}, + * but should generally pass in the event's target node (`event.target`) + * when working with {@link module:layout/List~List}. (Because {@link module:layout/List~List} is + * based on the Flyweight pattern, the event's target node is often not + * the node currently associated with the Control at the time the event + * occurs.) + * @returns {Function} Keep a reference to this function and call it + * to release the node when the gesture has ended. + * @public + */ + retainNode: function(node) { + var control = this, + retainedNode = this._retainedNode = (node || this.hasNode()); + return function() { + if (control && (control._retainedNode == retainedNode)) { + control._retainedNode = null; + } else { + releaseRetainedNode(retainedNode); + } + }; + }, + + /** + * @param {Boolean} [cache] - Whether or not we are tearing down as part of a destroy + * operation, or if we are just caching. If `true`, the `showing` and `canGenerate` + * properties of the control will not be reset. + * @private + */ + teardownRender: function (cache) { + var delegate = this.renderDelegate || Control.renderDelegate; + + if (this._retainedNode) { + storeRetainedNode(this); + } + + delegate.teardownRender(this, cache); + + // if the original state was set with renderOnShow true then we need to reset these + // values as well to coordinate the original intent + if (this.renderOnShow && !cache) { + this.set('showing', false); + this.set('canGenerate', false); + } + }, + + /** + * @private + */ + teardownChildren: function () { + var delegate = this.renderDelegate || Control.renderDelegate; + + delegate.teardownChildren(this); + }, + + /** + * @private + */ + addNodeToParent: function () { + var pn; + + if (this.node) { + pn = this.getParentNode(); + if (pn) { + if (this.addBefore !== undefined) { + this.insertNodeInParent(pn, this.addBefore && this.addBefore.hasNode()); + } else this.appendNodeToParent(pn); + } + } + }, + + /** + * @private + */ + appendNodeToParent: function(parentNode) { + parentNode.appendChild(this.node); + }, + + /** + * @private + */ + insertNodeInParent: function(parentNode, beforeNode) { + parentNode.insertBefore(this.node, beforeNode || parentNode.firstChild); + }, + + /** + * @private + */ + removeNodeFromDom: function() { + var node = this.hasNode(); + if (node) { + if (node.remove) node.remove(); + else if (node.parentNode) node.parentNode.removeChild(node); + } + }, + + /** + * @private + */ + getParentNode: function () { + return this.parentNode || (this.parent && ( + this.parent.hasNode() || this.parent.getParentNode()) + ); + }, + + // ................................. + + /** + * @private + */ + constructor: kind.inherit(function (sup) { + return function (props) { + var attrs = props && props.attributes; + + // ensure that we both keep an instance copy of defined attributes but also + // update the hash with any additional instance definitions at runtime + this.attributes = this.attributes ? utils.clone(this.attributes) : {}; + if (attrs) { + utils.mixin(this.attributes, attrs); + delete props.attributes; + } + + return sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + create: kind.inherit(function (sup) { + return function (props) { + var classes; + + // initialize the styles for this instance + this.style = this.kindStyle + this.style; + + // set initial values based on renderOnShow + this.renderOnShowChanged(); + + // super initialization + sup.apply(this, arguments); + + // ensure that if we aren't showing -> true then the correct style + // is applied - note that there might be issues with this because we are + // trying not to have to parse out any other explicit display value during + // initialization and we can't check because we haven't rendered yet + if (!this.showing) this.style += ' display: none;'; + + // try and make it so we only need to call the method once during + // initialization and only then when we have something to add + classes = this.kindClasses; + if (classes && this.classes) classes += (' ' + this.classes); + else if (this.classes) classes = this.classes; + + // if there are known classes needed to be applied from the kind + // definition and the instance definition (such as a component block) + this.classes = this.attributes['class'] = classes ? classes.trim() : classes; + + // setup the id for this control if we have one + this.idChanged(); + this.contentChanged(); + }; + }), + + /** + * Destroys the control and removes it from the [DOM]{@glossary DOM}. Also + * removes the control's ability to receive bubbled events. + * + * @public + */ + destroy: kind.inherit(function (sup) { + return function() { + // if the control has been rendered we ensure it is removed from the DOM + this.removeNodeFromDom(); + + // ensure no other bubbled events can be dispatched to this control + dispatcher.$[this.id] = null; + sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + dispatchEvent: kind.inherit(function (sup) { + return function (name, event, sender) { + // prevent dispatch and bubble of events that are strictly internal (e.g. + // enter/leave) + if (this.strictlyInternalEvents[name] && this.isInternalEvent(event)) { + return true; + } + return sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + addChild: kind.inherit(function (sup) { + return function (control) { + control.addClass(this.controlClasses); + sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + removeChild: kind.inherit(function (sup) { + return function (control) { + sup.apply(this, arguments); + control.removeClass(this.controlClasses); + }; + }), + + /** + * @private + */ + set: kind.inherit(function (sup) { + return function (path, value, opts) { + // this should be updated if a better api for hooking becomes available but for + // now we just do this directly to ensure that the showing value is actually + // a boolean + if (path == 'showing') { + return sup.call(this, path, !! value, opts); + } else return sup.apply(this, arguments); + }; + }), + + // ................................. + // BACKWARDS COMPATIBLE API, LEGACY METHODS AND PUBLIC PROPERTY + // METHODS OR PROPERTIES THAT PROBABLY SHOULD NOT BE HERE BUT ARE ANYWAY + + /** + * Apparently used by Ares 2 still but we have the property embedded in the kind... + * + * @deprecated + * @private + */ + isContainer: false, + + /** + * @private + */ + rtl: false, + + /** + * @private + */ + setupBodyFitting: function () { + Dom.applyBodyFit(); + this.addClass('enyo-fit enyo-clip'); + }, + + /* + * If the platform is Android or Android-Chrome, don't include the css rule + * `-webkit-overflow-scrolling: touch`, as it is not supported in Android and leads to + * overflow issues (ENYO-900 and ENYO-901). Similarly, BB10 has issues repainting + * out-of-viewport content when `-webkit-overflow-scrolling` is used (ENYO-1396). + * + * @private + */ + setupOverflowScrolling: function () { + if(platform.android || platform.androidChrome || platform.blackberry) { + return; + } + Dom.addBodyClass('webkitOverflowScrolling'); + }, + + /** + * Sets the control's directionality based on its content, or an optional `stringInstead`. + * + * @param {String} [stringInstead] An alternate string for consideration may be sent instead, + * in-case the string to test the directionality of the control is stored in `this.value`, + * or some other property, for example. + * @private + */ + detectTextDirectionality: function (stringInstead) { + // If an argument was supplied at all, use it, even if it's undefined. + // Values that are null or undefined, or are numbers, arrays, and some objects are safe + // to be tested. + var str = (arguments.length) ? stringInstead : this.content; + if (str || str === 0) { + this.rtl = utils.isRtl(str); + this.applyStyle('direction', this.rtl ? 'rtl' : 'ltr'); + } else { + this.applyStyle('direction', null); + } + + }, + + // ................................. + + // ................................. + // DEPRECATED + + /** + * @deprecated + * @public + */ + getTag: function () { + return this.tag; + }, + + /** + * @deprecated + * @public + */ + setTag: function (tag) { + var was = this.tag; + + if (tag && typeof tag == 'string') { + this.tag = tag; + if (was !== tag) this.notify('tag', was, tag); + } + return this; + }, + + /** + * @deprecated + * @public + */ + getAttributes: function () { + return this.attributes; + }, + + /** + * @deprecated + * @public + */ + setAttributes: function (attrs) { + var was = this.attributes; + + if (typeof attrs == 'object') { + this.attributes = attrs; + if (attrs !== was) this.notify('attributes', was, attrs); + } + + return this; + }, + + /** + * @deprecated + * @public + */ + getClasses: function () { + return this.classes; + }, + + /** + * @deprecated + * @public + */ + setClasses: function (classes) { + var was = this.classes; + + this.classes = classes; + if (was != classes) this.notify('classes', was, classes); + + return this; + }, + + /** + * @deprecated + * @public + */ + getStyle: function () { + return this.style; + }, + + /** + * @deprecated + * @public + */ + setStyle: function (style) { + var was = this.style; + + this.style = style; + if (was != style) this.notify('style', was, style); + + return this; + }, + + /** + * @deprecated + * @public + */ + getContent: function () { + return this.content; + }, + + /** + * @deprecated + * @public + */ + setContent: function (content) { + var was = this.content; + this.content = content; + + if (was != content) this.notify('content', was, content); + + return this; + }, + + /** + * @deprecated + * @public + */ + getShowing: function () { + return this.showing; + }, + + /** + * @deprecated + * @public + */ + setShowing: function (showing) { + var was = this.showing; + + // force the showing property to always be a boolean value + this.showing = !! showing; + + if (was != showing) this.notify('showing', was, showing); + + return this; + }, + + /** + * @deprecated + * @public + */ + getAllowHtml: function () { + return this.allowHtml; + }, + + /** + * @deprecated + * @public + */ + setAllowHtml: function (allow) { + var was = this.allowHtml; + this.allowHtml = !! allow; + + if (was !== allow) this.notify('allowHtml', was, allow); + + return this; + }, + + /** + * @deprecated + * @public + */ + getCanGenerate: function () { + return this.canGenerate; + }, + + /** + * @deprecated + * @public + */ + setCanGenerate: function (can) { + var was = this.canGenerate; + this.canGenerate = !! can; + + if (was !== can) this.notify('canGenerate', was, can); + + return this; + }, + + /** + * @deprecated + * @public + */ + getFit: function () { + return this.fit; + }, + + /** + * @deprecated + * @public + */ + setFit: function (fit) { + var was = this.fit; + this.fit = !! fit; + + if (was !== fit) this.notify('fit', was, fit); + + return this; + }, + + /** + * @ares + * @deprecated + * @public + */ + getIsContainer: function () { + return this.isContainer; + }, + + /** + * @ares + * @deprecated + * @public + */ + setIsContainer: function (isContainer) { + var was = this.isContainer; + this.isContainer = !! isContainer; + + if (was !== isContainer) this.notify('isContainer', was, isContainer); + + return this; + } + + // ................................. + +}); + +/** +* @static +* @public +*/ +kind.setDefaultCtor(Control); + +/** +* @static +* @public +*/ +Control.renderDelegate = HTMLStringDelegate; + +/** +* @private +*/ +Control.registerDomEvents = function (id, control) { + dispatcher.$[id] = control; +}; + +/** +* @private +*/ +Control.unregisterDomEvents = function (id) { + dispatcher.$[id] = null; +}; + +/** +* @private +*/ +Control.normalizeCssStyleString = function (style) { + return style ? ( + (";" + style) + // add a semi-colon if it's not the last character (also trim possible unnecessary whitespace) + .replace(/([^;])\s*$/, "$1;") + // ensure we have one space after each colon or semi-colon + .replace(/\s*;\s*([\w-]+)\s*:\s*/g, "; $1: ") + // remove first semi-colon and space + .substr(2).trim() + ) : ""; +}; + +/** +* @private +*/ +Control.concat = function (ctor, props, instance) { + var proto = ctor.prototype || ctor, + attrs, + str; + + if (props.classes) { + if (instance) { + str = (proto.classes ? (proto.classes + ' ') : '') + props.classes; + proto.classes = str; + } else { + str = (proto.kindClasses || '') + (proto.classes ? (' ' + proto.classes) : ''); + proto.kindClasses = str; + proto.classes = props.classes; + } + delete props.classes; + } + + if (props.style) { + if (instance) { + str = (proto.style ? proto.style : '') + props.style; + proto.style = Control.normalizeCssStyleString(str); + } else { + str = proto.kindStyle ? proto.kindStyle : ''; + str += proto.style ? (';' + proto.style) : ''; + str += props.style; + + // moved it all to kindStyle so that it will be available whenever instanced + proto.kindStyle = Control.normalizeCssStyleString(str); + } + delete props.style; + } + + if (props.attributes) { + attrs = proto.attributes; + proto.attributes = attrs ? utils.mixin({}, [attrs, props.attributes]) : props.attributes; + delete props.attributes; + } +}; + +Control.prototype.defaultKind = Control; + +// Control has to be *completely* set up before creating the floating layer setting up the +// fullscreen object because fullscreen depends on floating layer which depends on Control. + +/** +* @static +* @public +*/ +Control.FloatingLayer = FloatingLayer(Control); + +/** +* @static +* @public +*/ +Control.floatingLayer = new Control.FloatingLayer({id: 'floatingLayer'}); + +/** +* @static +* @public +*/ +Control.Fullscreen = fullscreen(Control); +},{'../kind':'enyo/kind','../utils':'enyo/utils','../platform':'enyo/platform','../dispatcher':'enyo/dispatcher','../options':'enyo/options','../roots':'enyo/roots','../AccessibilitySupport':'enyo/AccessibilitySupport','../UiComponent':'enyo/UiComponent','../HTMLStringDelegate':'enyo/HTMLStringDelegate','../dom':'enyo/dom','./fullscreen':'enyo/Control/fullscreen','./floatingLayer':'enyo/Control/floatingLayer','../gesture':'enyo/gesture'}],'enyo/touch':[function (module,exports,global,require,request){ +require('enyo'); + +var + utils = require('./utils'), + gesture = require('./gesture'), + dispatcher = require('./dispatcher'), + platform = require('./platform'); + +var + Dom = require('./dom'), + Job = require('./job'); + +function dispatch (e) { + return dispatcher.dispatch(e); +} + +/** +* @private +*/ +Dom.requiresWindow(function() { + + /** + * Add touch-specific gesture feature + * + * @private + */ + + /** + * @private + */ + var oldevents = gesture.events; + + /** + * @private + */ + gesture.events.touchstart = function (e) { + // for duration of this touch, only handle touch events. Old event + // structure will be restored during touchend. + gesture.events = touchGesture; + gesture.events.touchstart(e); + }; + + /** + * @private + */ + var touchGesture = { + + /** + * @private + */ + _touchCount: 0, + + /** + * @private + */ + touchstart: function (e) { + this._touchCount += e.changedTouches.length; + this.excludedTarget = null; + var event = this.makeEvent(e); + //store the finger which generated the touchstart event + this.currentIdentifier = event.identifier; + gesture.down(event); + // generate a new event object since over is a different event + event = this.makeEvent(e); + this.overEvent = event; + gesture.over(event); + }, + + /** + * @private + */ + touchmove: function (e) { + Job.stop('resetGestureEvents'); + // NOTE: allow user to supply a node to exclude from event + // target finding via the drag event. + var de = gesture.drag.dragEvent; + this.excludedTarget = de && de.dragInfo && de.dragInfo.node; + var event = this.makeEvent(e); + // do not generate the move event if this touch came from a different + // finger than the starting touch + if (this.currentIdentifier !== event.identifier) { + return; + } + gesture.move(event); + // prevent default document scrolling if enyo.bodyIsFitting == true + // avoid window scrolling by preventing default on this event + // note: this event can be made unpreventable (native scrollers do this) + if (Dom.bodyIsFitting) { + e.preventDefault(); + } + // synthesize over and out (normally generated via mouseout) + if (this.overEvent && this.overEvent.target != event.target) { + this.overEvent.relatedTarget = event.target; + event.relatedTarget = this.overEvent.target; + gesture.out(this.overEvent); + gesture.over(event); + } + this.overEvent = event; + }, + + /** + * @private + */ + touchend: function (e) { + gesture.up(this.makeEvent(e)); + // NOTE: in touch land, there is no distinction between + // a pointer enter/leave and a drag over/out. + // While it may make sense to send a leave event when a touch + // ends, it does not make sense to send a dragout. + // We avoid this by processing out after up, but + // this ordering is ad hoc. + gesture.out(this.overEvent); + // reset the event handlers back to the mouse-friendly ones after + // a short timeout. We can't do this directly in this handler + // because it messes up Android to handle the mouseup event. + // FIXME: for 2.1 release, conditional on platform being + // desktop Chrome, since we're seeing issues in PhoneGap with this + // code. + this._touchCount -= e.changedTouches.length; + }, + + /** + * Use `mouseup()` after touches are done to reset {@glossary event} handling + * back to default; this works as long as no one did a `preventDefault()` on + * the touch events. + * + * @private + */ + mouseup: function () { + if (this._touchCount === 0) { + this.sawMousedown = false; + gesture.events = oldevents; + } + }, + + /** + * @private + */ + makeEvent: function (e) { + var event = utils.clone(e.changedTouches[0]); + event.srcEvent = e; + event.target = this.findTarget(event); + // normalize "mouse button" info + event.which = 1; + //enyo.log("target for " + inEvent.type + " at " + e.pageX + ", " + e.pageY + " is " + (e.target ? e.target.id : "none")); + return event; + }, + + /** + * @private + */ + calcNodeOffset: function (node) { + if (node.getBoundingClientRect) { + var o = node.getBoundingClientRect(); + return { + left: o.left, + top: o.top, + width: o.width, + height: o.height + }; + } + }, + + /** + * @private + */ + findTarget: function (e) { + return document.elementFromPoint(e.clientX, e.clientY); + }, + + /** + * NOTE: Will find only 1 element under the touch and will fail if an element is + * positioned outside the bounding box of its parent. + * + * @private + */ + findTargetTraverse: function (node, x, y) { + var n = node || document.body; + var o = this.calcNodeOffset(n); + if (o && n != this.excludedTarget) { + var adjX = x - o.left; + var adjY = y - o.top; + //enyo.log("test: " + n.id + " (left: " + o.left + ", top: " + o.top + ", width: " + o.width + ", height: " + o.height + ")"); + if (adjX>0 && adjY>0 && adjX<=o.width && adjY<=o.height) { + //enyo.log("IN: " + n.id + " -> [" + adjX + "," + adjY + " in " + o.width + "x" + o.height + "] (children: " + n.childNodes.length + ")"); + var target; + for (var n$=n.childNodes, i=n$.length-1, c; (c=n$[i]); i--) { + target = this.findTargetTraverse(c, x, y); + if (target) { + return target; + } + } + return n; + } + } + }, + + /** + * @private + */ + connect: function () { + utils.forEach(['touchstart', 'touchmove', 'touchend', 'gesturestart', 'gesturechange', 'gestureend'], function(e) { + if(platform.ie < 9){ + document['on' + e] = dispatch; + } else { + // on iOS7 document.ongesturechange is never called + document.addEventListener(e, dispatch, false); + } + }); + + if (platform.androidChrome <= 18 || platform.silk === 2) { + // HACK: on Chrome for Android v18 on devices with higher density displays, + // document.elementFromPoint expects screen coordinates, not document ones + // bug also appears on Kindle Fire HD + this.findTarget = function(e) { + return document.elementFromPoint(e.screenX, e.screenY); + }; + } else if (!document.elementFromPoint) { + this.findTarget = function(e) { + return this.findTargetTraverse(null, e.clientX, e.clientY); + }; + } + } + }; + + touchGesture.connect(); +}); + +},{'./utils':'enyo/utils','./gesture':'enyo/gesture','./dispatcher':'enyo/dispatcher','./platform':'enyo/platform','./dom':'enyo/dom','./job':'enyo/job'}],'enyo/Application':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Application~Application} kind. +* @module enyo/Application +*/ + +var + kind = require('./kind'), + utils = require('./utils'), + master = require('./master'); + +var + ViewController = require('./ViewController'), + Controller = require('./Controller'); + +var + applications = {}; + +/** +* {@link module:enyo/Application~Application} is a type of {@link module:enyo/ViewController~ViewController} that +* encapsulates a collection of [controllers]{@link module:enyo/Controller~Controller} and a +* hierarchy of [controls]{@link module:enyo/Control~Control}. There may be multiple instances +* of an [application]{@link module:enyo/Application~Application} at a given time, with unique +* names and target [DOM nodes]{@glossary Node}. Within a given application, a +* reference to the application is available on all [components]{@link module:enyo/Component~Component} +* via the [app]{@link module:enyo/ApplicationSupport#app} property. +* +* @class Application +* @extends module:enyo/ViewController~ViewController +* @public +*/ +exports = module.exports = kind( + /** @lends module:enyo/Application~Application.prototype */ { + + name: 'enyo.Application', + + /** + * @private + */ + kind: ViewController, + + /** + * If set to `true` (the default), the [application's]{@link module:enyo/Application~Application} + * [start()]{@link module:enyo/Application~Application#start} method will automatically be called + * once its [create()]{@link module:enyo/Application~Application#create} method has completed + * execution. Set this to `false` if additional setup (or an asynchronous + * {@glossary event}) is required before starting. + * + * @type {Boolean} + * @default true + * @public + */ + autoStart: true, + + /** + * If set to `true` (the default), the [application]{@link module:enyo/Application~Application} will immediately + * [render]{@link module:enyo/Application~Application#render} its [view]{@link module:enyo/ViewController~ViewController#view} when + * the [start()]{@link module:enyo/Application~Application#start} method has completed execution. Set this to + * `false` to delay rendering if additional setup (or an asynchronous {@glossary event}) is + * required before rendering. + * + * @type {Boolean} + * @default true + * @public + */ + renderOnStart: true, + + /** + * The `defaultKind` for {@link module:enyo/Application~Application} is {@link module:enyo/Controller~Controller}. + * + * @type {Object} + * @default module:enyo/Controller~Controller + * @public + */ + defaultKind: Controller, + + /** + * A [bindable]{@link module:enyo/BindingSupport~BindingSupport}, read-only property that indicates whether the + * [view]{@link module:enyo/ViewController~ViewController#view} has been rendered. + * + * @readonly + * @type {Boolean} + * @default false + * @public + */ + viewReady: false, + + /** + * An abstract method to allow for additional setup to take place after the application has + * completed its initialization and is ready to be rendered. Overload this method to suit + * your app's specific requirements. + * + * @returns {this} The callee for chaining. + * @public + */ + start: function () { + if (this.renderOnStart) this.render(); + return this; + }, + + /** + * @method + * @private + */ + render: kind.inherit(function (sup) { + return function () { + // call the super method render() from ViewController + sup.apply(this, arguments); + if (this.view && this.view.generated) this.set('viewReady', true); + }; + }), + + /** + * @method + * @private + */ + constructor: kind.inherit(function (sup) { + return function (props) { + if (props && typeof props.name == 'string') { + utils.setPath(props.name, this); + // since applications are stored by their id's we set it + // to the name if it exists + this.id = (props && props.name); + } + sup.apply(this, arguments); + // we alias the `controllers` property to the `$` property to preserve + // backwards compatibility for the deprecated API for now + this.controllers = this.$; + applications[this.id || this.makeId()] = this; + }; + }), + + /** + * Allows normal creation flow and then executes the application's + * [start()]{@link module:enyo/Application~Application#start} method if the + * [autoStart]{@link module:enyo/Application~Application#autoStart} property is `true`. + * + * @method + * @private + */ + create: kind.inherit(function (sup) { + return function () { + // ensure that we create() all of the components before continuing + sup.apply(this, arguments); + if (this.autoStart) this.start(); + + }; + }), + + /** + * Ensures that all [components]{@link module:enyo/Component~Component} created by this application have + * their [app]{@link module:enyo/ApplicationSupport#app} property set correctly. + * + * @method + * @private + */ + adjustComponentProps: kind.inherit(function (sup) { + return function (props) { + props.app = this; + sup.apply(this, arguments); + }; + }), + + /** + * Cleans up the registration for the application. + * + * @method + * @private + */ + destroy: kind.inherit(function (sup) { + return function () { + delete applications[this.id]; + sup.apply(this, arguments); + }; + }), + + /** + * Ensures that [events]{@glossary event} bubbling from the views will reach + * {@link module:enyo/master} as expected. + * + * @private + */ + owner: master +}); + +/** +* Any {@link module:enyo/Application~Application} instances will be available by name from this +* [object]{@glossary Object}. If no name is provided for an +* [application]{@link module:enyo/Application~Application}, a name will be generated for it. +* +* @public +* @type {Object} +* @default {} +*/ +exports.applications = applications; + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./master':'enyo/master','./ViewController':'enyo/ViewController','./Controller':'enyo/Controller'}],'enyo/Image':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Image~Image} kind. +* @module enyo/Image +*/ + +var + kind = require('../kind'), + ri = require('../resolution'), + dispatcher = require('../dispatcher'), + path = require('../pathResolver'); +var + Control = require('../Control'); + +/** +* Fires when the [image]{@link module:enyo/Image~Image} has loaded. +* +* @event module:enyo/Image~Image#onload +* @type {Object} +* @property {Object} sender - The [component]{@link module:enyo/Component~Component} that most recently +* propagated the {@glossary event}. +* @property {Object} event - An [object]{@glossary Object} containing event information. +* @public +*/ + +/** +* Fires when there has been an error while loading the [image]{@link module:enyo/Image~Image}. +* +* @event module:enyo/Image~Image#onerror +* @type {Object} +* @property {Object} sender - The [component]{@link module:enyo/Component~Component} that most recently +* propagated the {@glossary event}. +* @property {Object} event - An [object]{@glossary Object} containing event information. +* @public +*/ + +/** +* {@link module:enyo/Image~Image} implements an HTML [<img>]{@glossary img} element and, optionally, +* [bubbles]{@link module:enyo/Component~Component#bubble} the [onload]{@link module:enyo/Image~Image#onload} and +* [onerror]{@link module:enyo/Image~Image#onerror} [events]{@glossary event}. Image dragging is suppressed by +* default, so as not to interfere with touch interfaces. +* +* When [sizing]{@link module:enyo/Image~Image#sizing} is used, the control will not have a natural size and must be +* manually sized using CSS `height` and `width`. Also, when [placeholder]{@link module:enyo/Image~Image#placeholder} is used +* without `sizing`, you may wish to specify the size, as the image will not have a +* natural size until the image loads, causing the placeholder to not be visible. +* +* {@link module:enyo/Image~Image} also has support for multi-resolution images. If you are developing assets +* for specific screen sizes, HD (720p), FHD (1080p), UHD (4k), for example, you may provide +* specific image assets in a hash/object format to the `src` property, instead of the usual +* string. The image sources will be used automatically when the screen resolution is less than +* or equal to those screen types. For more informaton on our resolution support, and how to +* enable this feature, see our [resolution independence documentation]{@link module:enyo/resolution}. +* +* ``` +* // Take advantage of the multi-rez mode +* var +* kind = require('enyo/kind'), +* Image = require('enyo/Image'); +* +* {kind: Image, src: { +* 'hd': 'http://lorempixel.com/64/64/city/1/', +* 'fhd': 'http://lorempixel.com/128/128/city/1/', +* 'uhd': 'http://lorempixel.com/256/256/city/1/' +* }, alt: 'Multi-rez'}, +* +* // Standard string `src` +* {kind: Image, src: 'http://lorempixel.com/128/128/city/1/', alt: 'Large'} +* ``` +* +* @class Image +* @extends module:enyo/Control~Control +* @ui +* @public +*/ +module.exports = kind( + /** @lends module:enyo/Image~Image.prototype */ { + + /** + * @private + */ + name: 'enyo.Image', + + /** + * @private + */ + kind: Control, + + /** + * When `true`, no [onload]{@link module:enyo/Image~Image#onload} or + * [onerror]{@link module:enyo/Image~Image#onerror} {@glossary event} handlers will be + * created. + * + * @type {Boolean} + * @default false + * @public + */ + noEvents: false, + + /** + * @private + */ + published: + /** @lends module:enyo/Image~Image.prototype */ { + + /** + * Maps to the `src` attribute of an [<img> tag]{@glossary img}. This also supports + * a multi-resolution hash object. See + * [the above description of enyo.Image]{@link module:enyo/Image~Image} for more details and examples + * or our [resolution independence docs]{@link module:enyo/resolution}. + * + * @type {String} + * @default '' + * @public + */ + src: '', + + /** + * Maps to the `alt` attribute of an [<img> tag]{@glossary img}. + * + * @type {String} + * @default '' + * @public + */ + alt: '', + + /** + * By default, the [image]{@link module:enyo/Image~Image} is rendered using an `` tag. + * When this property is set to `'cover'` or `'constrain'`, the image will be + * rendered using a `
`, utilizing `background-image` and `background-size`. + * + * Set this property to `'contain'` to letterbox the image in the available + * space, or `'cover'` to cover the available space with the image (cropping the + * larger dimension). Note that when `sizing` is set, the control must be + * explicitly sized. + * + * @type {String} + * @default '' + * @public + */ + sizing: '', + + /** + * When [sizing]{@link module:enyo/Image~Image#sizing} is used, this property sets the positioning of + * the [image]{@link module:enyo/Image~Image} within the bounds, corresponding to the + * [`background-position`]{@glossary backgroundPosition} CSS property. + * + * @type {String} + * @default 'center' + * @public + */ + position: 'center', + + /** + * Provides a default image displayed while the URL specified by `src` is loaded or when that + * image fails to load. + * + * @type {String} + * @default '' + * @public + */ + placeholder: '' + }, + + /** + * @private + */ + tag: 'img', + + /** + * @private + */ + classes: 'enyo-image', + + /** + * `src` copied here to avoid overwriting the user-provided value when loading values + * + * @private + */ + _src: null, + + /** + * @type {Object} + * @property {Boolean} draggable - This attribute will take one of the following + * [String]{@glossary String} values: 'true', 'false' (the default), or 'auto'. + * Setting Boolean `false` will remove the attribute. + * @public + */ + attributes: { + draggable: 'false' + }, + + /** + * @private + */ + handlers: { + onload: 'handleLoad', + onerror: 'handleError' + }, + + /** + * @private + */ + observers: [ + {method: 'updateSource', path: ['_src', 'placeholder']} + ], + + /** + * @method + * @private + */ + create: kind.inherit(function (sup) { + return function () { + if (this.noEvents) { + delete this.attributes.onload; + delete this.attributes.onerror; + } + sup.apply(this, arguments); + this.altChanged(); + this.sizingChanged(); + this.srcChanged(); + this.positionChanged(); + }; + }), + + /** + * Cache the value of user-provided `src` value in `_src` + * + * @private + */ + srcChanged: function () { + this.set('_src', this.src); + }, + + /** + * @private + */ + altChanged: function () { + this.setAttribute('alt', this.alt); + }, + + /** + * @private + */ + sizingChanged: function (was) { + this.tag = this.sizing ? 'div' : 'img'; + this.addRemoveClass('sized', !!this.sizing); + if (was) { + this.removeClass(was); + } + if (this.sizing) { + this.addClass(this.sizing); + } + this.updateSource(); + if (this.generated) { + this.render(); + } + }, + + /** + * @private + */ + positionChanged: function () { + if (this.sizing) { + this.applyStyle('background-position', this.position); + } + }, + + /** + * When the image is loaded successfully, we want to clear out the background image so it doesn't + * show through the transparency of the image. This only works when not using `sizing` because we + * do not get load/error events for failed background-image's. + * + * @private + */ + handleLoad: function () { + if (!this.sizing && this.placeholder) { + this.applyStyle('background-image', null); + } + }, + + /** + * @private + */ + handleError: function () { + if (this.placeholder) { + this.set('_src', null); + } + }, + + /** + * Updates the Image's src or background-image based on the values of _src and placeholder + * + * @private + */ + updateSource: function (was, is, prop) { + var src = ri.selectSrc(this._src), + srcUrl = src ? 'url(\'' + path.rewrite(src) + '\')' : null, + plUrl = this.placeholder ? 'url(\'' + path.rewrite(this.placeholder) + '\')' : null, + url; + + if (this.sizing) { + // use either both urls, src, placeholder, or 'none', in that order + url = srcUrl && plUrl && (srcUrl + ',' + plUrl) || srcUrl || plUrl || 'none'; + this.applyStyle('background-image', url); + } + // if we've haven't failed to load src (this.src && this._src == this.src), we don't want to + // add the bg image that may have already been removed by handleLoad + else if (!(prop == 'placeholder' && this.src && this._src == this.src)) { + this.applyStyle('background-image', plUrl); + this.setAttribute('src', src); + } + }, + + /** + * @fires module:enyo/Image~Image#onload + * @fires module:enyo/Image~Image#onerror + * @private + */ + rendered: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + dispatcher.makeBubble(this, 'load', 'error'); + }; + }), + + /** + * @lends module:enyo/Image~Image + * @private + */ + statics: { + /** + * A globally accessible data URL that describes a simple + * placeholder image that may be used in samples and applications + * until final graphics are provided. As an SVG image, it will + * expand to fill the desired width and height set in the style. + * + * @type {String} + * @public + */ + placeholder: + 'data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC' + + '9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj48cmVjdCB3aWR0aD0iMTAw' + + 'JSIgaGVpZ2h0PSIxMDAlIiBzdHlsZT0ic3Ryb2tlOiAjNDQ0OyBzdHJva2Utd2lkdGg6IDE7IGZpbGw6ICNhYW' + + 'E7IiAvPjxsaW5lIHgxPSIwIiB5MT0iMCIgeDI9IjEwMCUiIHkyPSIxMDAlIiBzdHlsZT0ic3Ryb2tlOiAjNDQ0' + + 'OyBzdHJva2Utd2lkdGg6IDE7IiAvPjxsaW5lIHgxPSIxMDAlIiB5MT0iMCIgeDI9IjAiIHkyPSIxMDAlIiBzdH' + + 'lsZT0ic3Ryb2tlOiAjNDQ0OyBzdHJva2Utd2lkdGg6IDE7IiAvPjwvc3ZnPg==' + }, + + // Accessibility + + /** + * @default img + * @type {String} + * @see enyo/AccessibilitySupport~AccessibilitySupport#accessibilityRole + * @public + */ + accessibilityRole: 'img' +}); + +},{'../kind':'enyo/kind','../resolution':'enyo/resolution','../dispatcher':'enyo/dispatcher','../pathResolver':'enyo/pathResolver','../Control':'enyo/Control'}],'enyo/GroupItem':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/GroupItem~GroupItem} kind. +* @module enyo/GroupItem +*/ + +var + kind = require('./kind'); +var + Control = require('./Control'); + +/** +* Fires when the [active state]{@link module:enyo/GroupItem~GroupItem#active} has changed. +* +* @event module:enyo/GroupItem~GroupItem#onActivate +* @type {Object} +* @property {Object} sender - The [component]{@link module:enyo/Component~Component} that most recently +* propagated the {@glossary event}. +* @property {Object} event - An [object]{@glossary Object} containing event information. +* @public +*/ + +/** +* {@link module:enyo/GroupItem~GroupItem} is the base [kind]{@glossary kind} for the +* [Grouping]{@link module:enyo/Group~Group} API. It manages the +* [active state]{@link module:enyo/GroupItem~GroupItem#active} of the [component]{@link module:enyo/Component~Component} +* (or the [inheriting]{@glossary subkind} component). A subkind may call `setActive()` +* to set the [active]{@link module:enyo/GroupItem~GroupItem#active} property to the desired state; this +* will additionally [bubble]{@link module:enyo/Component~Component#bubble} an +* [onActivate]{@link module:enyo/GroupItem~GroupItem#onActivate} {@glossary event}, which may +* be handled as needed by the containing components. This is useful for creating +* groups of items whose state should be managed collectively. +* +* For an example of how this works, see the {@link module:enyo/Group~Group} kind, which enables the +* creation of radio groups from arbitrary components that support the Grouping API. +* +* @class GroupItem +* @extends module:enyo/Control~Control +* @ui +* @public +*/ +module.exports = kind( + /** @lends module:enyo/Groupitem~Groupitem.prototype */ { + + /** + * @private + */ + name: 'enyo.GroupItem', + + /** + * @private + */ + kind: Control, + + /** + * @private + */ + published: + /** @lends module:enyo/Groupitem~Groupitem.prototype */ { + + /** + * Will be `true` if the item is currently selected. + * + * @type {Boolean} + * @default false + * @public + */ + active: false + }, + + /** + * @method + * @private + */ + rendered: kind.inherit(function (sup) { + return function() { + sup.apply(this, arguments); + this.activeChanged(); + }; + }), + + /** + * @fires module:enyo/GroupItem~GroupItem#onActivate + * @private + */ + activeChanged: function () { + this.bubble('onActivate'); + } +}); + +},{'./kind':'enyo/kind','./Control':'enyo/Control'}],'enyo/DataRepeater':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/DataRepeater~DataRepeater} kind. +* @module enyo/DataRepeater +*/ + +var + kind = require('./kind'), + utils = require('./utils'); +var + Control = require('./Control'), + RepeaterChildSupport = require('./RepeaterChildSupport'); + +function at (idx) { + return this[idx]; +} + +function arrayFilter (record) { + return record[this.selectionProperty]; +} + +function modelFilter (record) { + return record.get(this.selectionProperty); +} + +/** +* {@link module:enyo/DataRepeater~DataRepeater} iterates over the items in an {@link module:enyo/Collection~Collection} to +* repeatedly render and synchronize records (instances of {@link module:enyo/Model~Model}) to its +* own children. For any record in the collection, a new child will be rendered in +* the repeater. If the record is destroyed, the child will be destroyed. These +* [controls]{@link module:enyo/Control~Control} will automatically update when properties on the +* underlying records are modified if they have been bound using +* [bindings]{@link module:enyo/Binding~Binding}. +* +* @class DataRepeater +* @extends module:enyo/Control~Control +* @ui +* @public +*/ +var DataRepeater = module.exports = kind( + /** @lends module:enyo/DataRepeater~DataRepeater.prototype */ { + + /** + * @private + */ + name: 'enyo.DataRepeater', + + /** + * @private + */ + kind: Control, + + /** + * Set this to `true` to enable selection support. Note that selection stores a + * reference to the [model]{@link module:enyo/Model~Model} that is selected, via the + * [selected]{@link module:enyo/DataRepeater~DataRepeater#selected} method. + * + * @type {Boolean} + * @default true + * @public + */ + selection: true, + + /** + * Specifies the type of selection (if enabled), that we want to enable. The possible values + * are 'single', 'multi', and 'group'. The default is 'single' selection mode, which enables + * selection and deselection of a single item at a time. The 'multi' selection mode allows + * multiple children to be selected simultaneously, while the 'group' selection mode allows + * group-selection behavior such that only one child may be selected at a time and, once a + * child is selected, it cannot be deselected via user input. The child may still be + * deselected via the selection API methods. + * + * @type {String} + * @default 'single' + * @public + */ + selectionType: 'single', + + /** + * Set this to `true` to allow multiple children to be selected simultaneously. + * + * @deprecated since version 2.6 + * @type {Boolean} + * @default false + * @public + */ + multipleSelection: false, + + /** + * Set this to `true` to allow group-selection behavior such that only one child + * may be selected at a time and, once a child is selected, it cannot be + * deselected via user input. The child may still be deselected via the selection + * API methods. Note that setting this property to `true` will set the + * [multipleSelection]{@link module:enyo/DataRepeater~DataRepeater#multipleSelection} property to + * `false`. + * + * @deprecated since version 2.6 + * @type {Boolean} + * @default false + * @public + */ + groupSelection: false, + + /** + * This class will be applied to the [repeater]{@link module:enyo/DataRepeater~DataRepeater} when + * [selection]{@link module:enyo/DataRepeater~DataRepeater#selection} is enabled. It will also be + * applied if [multipleSelection]{@link module:enyo/DataRepeater~DataRepeater#multipleSelection} + * is `true`. + * + * @type {String} + * @default 'selection-enabled' + * @public + */ + selectionClass: 'selection-enabled', + + /** + * This class will be applied to the [repeater]{@link module:enyo/DataRepeater~DataRepeater} when + * [selectionType]{@link module:enyo/DataRepeater~DataRepeater#selectionType} is `multi`. + * When that is the case, the [selectionClass]{@link module:enyo/DataRepeater~DataRepeater#selectionClass} + * will also be applied. + * + * @type {String} + * @default 'multiple-selection-enabled' + * @public + */ + multipleSelectionClass: 'multiple-selection-enabled', + + /** + * In cases where selection should be detected from the state of the + * [model]{@link module:enyo/Model~Model}, this property should be set to the property on + * the model that the [repeater]{@link module:enyo/DataRepeater~DataRepeater} should observe for + * changes. If the model changes, the repeater will reflect the change without + * having to interact directly with the model. Note that this property must be + * part of the model's schema, or else its changes will not be detected + * properly. + * + * @type {String} + * @default '' + * @public + */ + selectionProperty: '', + + /** + * Set this to a space-delimited string of [events]{@glossary event} or an + * [array]{@glossary Array} that can trigger the selection of a particular + * child. To prevent selection entirely, set + * [selection]{@link module:enyo/DataRepeater~DataRepeater#selection} to `false`. + * + * @type {String} + * @default 'ontap' + * @public + */ + selectionEvents: 'ontap', + + /** + * Use this [hash]{@glossary Object} to define default [binding]{@link module:enyo/Binding~Binding} + * properties for **all** children (even children of children) of this + * [repeater]{@link module:enyo/DataRepeater~DataRepeater}. This can eliminate the need to write the + * same paths over and over. You may also use any binding macros. Any property + * defined here will be superseded by the same property if defined for an individual + * binding. + * + * @type {Object} + * @default null + * @public + */ + childBindingDefaults: null, + + /** + * @private + */ + initComponents: function () { + this.initContainer(); + var components = this.kindComponents || this.components || [], + owner = this.getInstanceOwner(), + props = this.defaultProps? utils.clone(this.defaultProps, true): (this.defaultProps = {}); + // ensure that children know who their binding owner is + props.bindingTransformOwner = this; + props.bindingDefaults = this.childBindingDefaults; + if (components) { + // if there are multiple components in the components block they will become nested + // children of the default kind set for the repeater + if (components.length > 1) { + props.components = components; + } + // if there is only one child, the properties will be the default kind of the repeater + else { + utils.mixin(props, components[0]); + } + props.repeater = this; + props.owner = owner; + props.mixins = props.mixins? props.mixins.concat(this.childMixins): this.childMixins; + } + + this.defaultProps = props; + }, + + /** + * @method + * @private + */ + constructor: kind.inherit(function (sup) { + return function () { + this._selection = []; + // we need to initialize our selectionEvents array + var se = this.selectionEvents; + this.selectionEvents = (typeof se == 'string'? se.split(' '): se); + // we need to pre-bind these methods so they can easily be added + // and removed as listeners later + var h = this._handlers = utils.clone(this._handlers); + for (var e in h) { + h[e] = this.bindSafely(h[e]); + } + sup.apply(this, arguments); + }; + }), + + /** + * @method + * @private + */ + create: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + this.collectionChanged(); + // Converting deprecated selection properties to our current selection API + this.selectionType = this.multipleSelection ? (this.groupSelection ? 'group' : 'multi') : this.selectionType; + this.selectionTypeChanged(); + }; + }), + + /** + * @private + */ + groupSelectionChanged: function () { + this.set('selectionType', this.groupSelection ? 'group' : 'single'); + }, + + /** + * @private + */ + multipleSelectionChanged: function () { + this.set('selectionType', this.multipleSelection ? 'multi' : 'single'); + }, + + /** + * @private + */ + selectionTypeChanged: function (was) { + // Synchronizing our deprecated properties + this.groupSelection = this.selectionType == 'group'; + this.multipleSelection = this.selectionType == 'multi'; + + if (was == 'multi') { + if (this._selection.length > 1) { + this.deselectAll(); + } + } + this.selectionChanged(); + }, + + /** + * @private + */ + selectionChanged: function () { + this.addRemoveClass(this.selectionClass, this.selection); + this.addRemoveClass(this.multipleSelectionClass, this.selectionType == 'multi' && this.selection); + }, + + /** + * Destroys any existing children in the [repeater]{@link module:enyo/DataRepeater~DataRepeater} and creates all + * new children based on the current [data]{@link module:enyo/Repeater~Repeater#data}. + * + * @public + */ + reset: function () { + // use the facaded dataset because this could be any + // collection of records + var dd = this.get('data'); + // destroy the client controls we might already have + this.destroyClientControls(); + // and now we create new ones for each new record we have + for (var i=0, r; (r=dd.at(i)); ++i) { + this.add(r, i); + } + this.hasReset = true; + }, + /** + * Refreshes each [control]{@link module:enyo/Control~Control} in the dataset. + * + * @param {Boolean} immediate - If `true`, refresh will occur immediately; otherwise, + * it will be queued up as a job. + * @public + */ + refresh: function (immediate) { + if (!this.hasReset) { return this.reset(); } + var refresh = this.bindSafely(function () { + var dd = this.get('data'), + cc = this.getClientControls(); + for (var i=0, c, d; (d=dd.at(i)); ++i) { + c = cc[i]; + if (c) { + c.set('model', d); + } else { + this.add(d, i); + } + } + this.prune(); + }); + + // refresh is used as the event handler for + // collection resets so checking for truthy isn't + // enough. it must be true. + if(immediate === true) { + refresh(); + } else { + this.startJob('refreshing', refresh, 16); + } + }, + + /** + * @method + * @private + */ + rendered: kind.inherit(function (sup) { + return function () { + var dd; + + sup.apply(this, arguments); + + dd = this.get('data'); + + if (dd && dd.length) { + this.reset(); + } + this.hasRendered = true; + }; + }), + + /** + * Adds a [record]{@link module:enyo/Model~Model} at a particular index. + * + * @param {module:enyo/Model~Model} rec - The [record]{@link module:enyo/Model~Model} to add. + * @param {Number} idx - The index at which the record should be added. + * @public + */ + add: function (rec, idx) { + var c = this.createComponent({model: rec, index: idx}); + if (this.generated && !this.batching) { + c.render(); + } + }, + + /** + * Removes the [record]{@link module:enyo/Model~Model} at a particular index. + * + * @param {Number} idx - The index of the [record]{@link module:enyo/Model~Model} to be removed. + * @public + */ + remove: function (idx) { + var controls = this.getClientControls() + , control; + + control = controls[idx]; + + if (control) control.destroy(); + }, + + /** + * Removes any [controls]{@link module:enyo/Control~Control} that are outside the boundaries of the + * [data]{@link module:enyo/DataRepeater~DataRepeater#data} [collection]{@link module:enyo/Collection~Collection} for the + * [repeater]{@link module:enyo/DataRepeater~DataRepeater}. + * + * @public + */ + prune: function () { + var g = this.getClientControls(), + dd = this.get('data'), + len = (dd ? dd.length: 0), + x; + if (g.length > len) { + x = g.slice(len); + for (var i=0, c; (c=x[i]); ++i) { + c.destroy(); + } + } + }, + + /** + * Syncs the bindings of all repeater children. Designed for use cases where + * the repeater's collection is a native JavaScript array with native JavaScript + * objects as models (as opposed to Enyo Collections and Models). In this case, + * you can't depend on bindings to update automatically when the underlying data + * changes, so if you know that there has been a change to the data, you can force + * an update by sync'ing all of the bindings. + * + * This API is to be considered experimental and is subject to change. + * + * @public + */ + syncChildBindings: function (opts) { + this.getClientControls().forEach(function (c) { + c.syncBindings(opts); + }); + }, + + /** + * @private + */ + initContainer: function () { + var ops = this.get('containerOptions'), + nom = ops.name || (ops.name = this.containerName); + this.createChrome([ops]); + this.discoverControlParent(); + if (nom != this.containerName) { + this.$[this.containerName] = this.$[nom]; + } + }, + + /** + * @private + */ + handlers: { + onSelected: 'childSelected', + onDeselected: 'childDeselected' + }, + + /** + * @private + */ + _handlers: { + add: 'modelsAdded', + remove: 'modelsRemoved', + reset: 'refresh', + sort: 'refresh', + filter: 'refresh' + }, + + /** + * @private + */ + componentsChanged: function (p) { + this.initComponents(); + this.reset(); + }, + + /** + * @private + */ + collectionChanged: function (p) { + var c = this.collection; + if (typeof c == 'string') { + c = this.collection = utils.getPath.call(global, c); + } + if (c) { + this.initCollection(c, p); + } + }, + + /** + * @private + */ + initCollection: function (c, p) { + var e, filter, isArray = c && c instanceof Array; + + if (c && c.addListener) { + for (e in this._handlers) { + c.addListener(e, this._handlers[e]); + } + } + // Decorate native JS array with at() so that we can + // access members of our dataset consistently, regardless + // of whether our data is in an array or a Collection + if (c && !c.at) { + Object.defineProperty(c, 'at', {value: at, enumerable: false}); + } + if (p && p.removeListener) { + for (e in this._handlers) { + p.removeListener(e, this._handlers[e]); + } + } + if (c && this.selectionProperty) { + filter = isArray ? arrayFilter : modelFilter; + this._selection = c.filter(filter, this); + } else { + this._selection = []; + } + }, + + /** + * @private + */ + modelsAdded: function (sender, e, props) { + if (sender === this.collection) this.refresh(); + }, + + /** + * @private + */ + modelsRemoved: function (sender, e, props) { + if (sender === this.collection) { + this.deselectRemovedModels(props.models); + this.refresh(); + } + }, + + /** + * Deselect removed models from _selected array. + * After calling it, we can ensure that the removed models aren't currently selected. + * @param {array} models - The array of models that are removed from collection. + * @private + */ + deselectRemovedModels: function(models) { + var selected = this._selection, + orig, + model, + idx, + len = selected && selected.length, + i = models.length - 1; + + // We have selected models + if (len) { + // unfortunately we need to make a copy to preserve what the original was + // so we can pass it with the notification if any of these are deselected + orig = selected.slice(); + + // we have _selected array to track currently selected models + // if some removed models are in _selected, we should remove them from _selected + // clearly we won't need to continue checking if selected does not have any models + for (; (model = models[i]) && selected.length; --i) { + idx = selected.indexOf(model); + if (idx > -1) selected.splice(idx, 1); + } + + // Some selected models are discovered, so we need to notify + if (len != selected.length) { + if (this.selection) { + if (this.selectionType == 'multi') this.notify('selected', orig, selected); + else this.notify('selected', orig[0], selected[0] || null); + } + } + } + }, + + /** + * @private + */ + batchingChanged: function (prev, val) { + if (this.generated && false === val) { + this.$[this.containerName].render(); + this.refresh(true); + } + }, + + /** + * Calls [childForIndex()]{@link module:enyo/DataRepeater~DataRepeater#getChildForIndex}. Leaving for posterity. + * + * @param {Number} idx - The index of the child to retrieve. + * @returns {module:enyo/Control~Control|undefined} The [control]{@link module:enyo/Control~Control} at the specified + * index, or `undefined` if it could not be found or the index is out of bounds. + * @public + */ + getChildForIndex: function (idx) { + return this.childForIndex(idx); + }, + + /** + * Attempts to return the [control]{@link module:enyo/Control~Control} representation at a particular index. + * + * @param {Number} idx - The index of the child to retrieve. + * @returns {module:enyo/Control~Control|undefined} The [control]{@link module:enyo/Control~Control} at the specified + * index, or `undefined` if it could not be found or the index is out of bounds. + * @public + */ + childForIndex: function (idx) { + return this.$.container.children[idx]; + }, + + /** + * Retrieves the data associated with the [repeater]{@link module:enyo/DataRepeater~DataRepeater}. + * + * @returns {module:enyo/Collection~Collection} The {@link module:enyo/Collection~Collection} that comprises the data. + * @public + */ + data: function () { + return this.collection; + }, + + /** + * Consolidates selection logic and allows for deselection of a [model]{@link module:enyo/Model~Model} + * that has already been removed from the [collection]{@link module:enyo/Collection~Collection}. + * + * @private + */ + _select: function (idx, model, select) { + if (!this.selection) { + return; + } + + var c = this.getChildForIndex(idx), + s = this._selection, + i = utils.indexOf(model, s), + dd = this.get('data'), + p = this.selectionProperty; + + if (select) { + if(i == -1) { + if(this.selectionType != 'multi') { + while (s.length) { + i = dd.indexOf(s.pop()); + this.deselect(i); + } + } + + s.push(model); + } + } else { + if(i >= 0) { + s.splice(i, 1); + } + } + + if (c) { + c.set('selected', select); + } + if (p && model) { + if (typeof model.set === 'function') { + model.set(p, select); + } + else { + model[p] = select; + if(c) c.syncBindings({force: true, all: true}); + } + } + this.notifyObservers('selected'); + }, + + /** + * Selects the item at the given index. + * + * @param {Number} idx - The index of the item to select. + * @public + */ + select: function (idx) { + var dd = this.get('data'); + + this._select(idx, dd.at(idx), true); + }, + + /** + * Deselects the item at the given index. + * + * @param {Number} idx - The index of the item to deselect. + * @public + */ + deselect: function (idx) { + var dd = this.get('data'); + + this._select(idx, dd.at(idx), false); + }, + + /** + * Determines whether a [model]{@link module:enyo/Model~Model} is currently selected. + * + * @param {module:enyo/Model~Model} model - The [model]{@link module:enyo/Model~Model} whose selection status + * is to be determined. + * @returns {Boolean} `true` if the given model is selected; otherwise, `false`. + * @public + */ + isSelected: function (model) { + return !!~utils.indexOf(model, this._selection); + }, + + /** + * Selects all items (if [selectionType]{@link module:enyo/DataRepeater~DataRepeater#selectionType} is `multi`). + * + * @public + */ + selectAll: function () { + var dd = this.get('data'); + + if (this.selectionType == 'multi') { + this.stopNotifications(); + var s = this._selection + , len = dd ? dd.length: 0; + s.length = 0; + for (var i=0; i= 0) { + c = o[ci]; + o.splice(ci, 1); + om.splice(ci, 1); + o2.push(c); + this.assignChild(m, idx, c); + } + else { + needed.push(i); + } + om2.push(m); + } + else { + break; + } + } + nNeeded = needed.length; + for (j = 0; j < nNeeded; ++j) { + i = needed[j]; + idx = f + i; + m = om2[i]; + c = om.pop() && o.pop() || b.pop(); + if (c) { + this.assignChild(m, idx, c); + } + else { + c = this.createComponent({model: m}); + this.assignChild(m, idx, c); + // TODO: Rethink child lifecycle hooks (currently deploy / update / retire) + if (typeof this.deployChild === 'function') this.deployChild(c); + c.render(); + } + o2.splice(i, 0, c); + } + needed.length = 0; + while (o.length) { + c = om.pop() && o.pop(); + c.set('model', null); + c.hide(); + b.push(c); + } + this.orderedChildren = o2; + this.orderedChildren_alt = o; + this.orderedModels = om2; + this.orderedModels_alt = om; + ln = o2.length; + if (ln) { + o2[0].addClass('enyo-vdr-first'); + o2[ln - 1].addClass('enyo-vdr-last'); + if (typeof this.positionChildren === 'function') this.positionChildren(); + } + }, + + fwd: function() { + this.set('first', this.first + 1); + }, + + bak: function() { + this.set('first', this.first - 1); + }, + + set: kind.inherit(function (sup) { + return function (prop, val) { + if (prop === 'first') { + this.setExtent(val); + return this; + } + if (prop === 'numItems') { + this.setExtent(null, val); + return this; + } + return sup.apply(this, arguments); + }; + }), + + stabilizeExtent: function() { + var f = this.first, + n = this.numItems, + c = this.collection, + l; + + if (c) { + l = c.length; + f = Math.min(f, l - n); + f = this.first = Math.max(f, 0); + this._last = Math.min(f + n, l) - 1; + } + }, + + dataChanged: function() { + if (this.get('data') && this.hasRendered) { + this.reset(); + } + } +}); + +},{'./kind':'enyo/kind','./DataRepeater':'enyo/DataRepeater'}],'enyo/Button':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Button~Button} kind. +* @module enyo/Button +*/ + +var + kind = require('../kind'); +var + ToolDecorator = require('../ToolDecorator'); + +/** +* {@link module:enyo/Button~Button} implements an HTML [button]{@glossary button}, with support +* for grouping using {@link module:enyo/Group~Group}. +* +* For more information, see the documentation on +* [Buttons]{@linkplain $dev-guide/building-apps/controls/buttons.html} in the +* Enyo Developer Guide. +* +* @class Button +* @extends module:enyo/ToolDecorator~ToolDecorator +* @ui +* @public +*/ +module.exports = kind( + /** @lends module:enyo/Button~Button.prototype */ { + + /** + * @private + */ + name: 'enyo.Button', + + /** + * @private + */ + kind: ToolDecorator, + + /** + * @private + */ + tag: 'button', + + /** + * @private + */ + attributes: { + /** + * Set to `'button'`; otherwise, the default value would be `'submit'`, which + * can cause unexpected problems when [controls]{@link module:enyo/Control~Control} are used + * inside of a [form]{@glossary form}. + * + * @type {String} + * @private + */ + type: 'button' + }, + + /** + * @private + */ + published: + /** @lends module:enyo/Button~Button.prototype */ { + + /** + * When `true`, the [button]{@glossary button} is shown as disabled and does not + * generate tap [events]{@glossary event}. + * + * @type {Boolean} + * @default false + * @public + */ + disabled: false + }, + + /** + * @method + * @private + */ + create: kind.inherit(function (sup) { + return function() { + sup.apply(this, arguments); + this.disabledChanged(); + }; + }), + + /** + * @private + */ + disabledChanged: function () { + this.setAttribute('disabled', this.disabled); + }, + + /** + * @private + */ + tap: function () { + if (this.disabled) { + // work around for platforms like Chrome on Android or Opera that send + // mouseup to disabled form controls + return true; + } else { + this.setActive(true); + } + }, + + // Accessibility + + /** + * @default button + * @type {String} + * @see enyo/AccessibilitySupport~AccessibilitySupport#accessibilityRole + * @public + */ + accessibilityRole: 'button', + + /** + * When `true`, `aria-pressed` will reflect the state of + * {@link module:enyo/GroupItem~GroupItem#active} + * + * @type {Boolean} + * @default false + * @public + */ + accessibilityPressed: false, + + /** + * @private + */ + ariaObservers: [ + {from: 'disabled', to: 'aria-disabled'}, + {path: ['active', 'accessibilityPressed'], method: function () { + this.setAriaAttribute('aria-pressed', this.accessibilityPressed ? String(this.active) : null); + }}, + {from: 'accessibilityRole', to: 'role'} + ] +}); + +},{'../kind':'enyo/kind','../ToolDecorator':'enyo/ToolDecorator'}],'enyo/NewDataList':[function (module,exports,global,require,request){ +require('enyo'); + +var + kind = require('./kind'), + dom = require('./dom'); + +var + Scrollable = require('./Scrollable'), + VirtualDataRepeater = require('./VirtualDataRepeater'); + +module.exports = kind({ + name: 'enyo.NewDataList', + kind: VirtualDataRepeater, + direction: 'vertical', + itemHeight: 100, + itemWidth: 100, + spacing: 0, + rows: 'auto', + columns: 'auto', + overhang: 3, + // Experimental + scrollToBoundaries: false, + mixins: [Scrollable], + observers: [ + {method: 'reset', path: [ + 'direction', 'columns', 'rows', + 'itemHeight', 'itemWidth', 'columns' + ]} + ], + /** + * Returns an array of list items that are currently visible (whether partially + * or fully). + * + * Experimental API -- subject to change. + * + * @public + */ + getVisibleItems: function() { + return this.orderedChildren.slice(this.firstVisibleI, this.lastVisibleI + 1); + }, + + /** + * Returns an array of list items that are currently fully visible. + * + * Experimental API -- subject to change. + * + * @public + */ + getFullyVisibleItems: function() { + return this.orderedChildren.slice(this.firstFullyVisibleI, this.lastFullyVisibleI + 1); + }, + + /** + * Scrolls to a list item (specified by index). + * + * @param {number} index - The (zero-based) index of the item to scroll to + * @param {Object} opts - Scrolling options (see enyo/Scrollable#scrollTo) + * @public + */ + scrollToItem: function (index, opts) { + var b = this.getItemBounds(index); + + // If the item is near the horizontal or vertical + // origin, scroll all the way there + if (b.x <= this.spacing) { + b.x = 0; + } + if (b.y <= this.spacing) { + b.y = 0; + } + + this.scrollTo(b.x, b.y, opts); + }, + + /** + * @private + */ + calculateMetrics: function(opts) { + var sp = this.spacing, + n = this.hasNode(), + oT = this.topOffset, + oR = this.rightOffset, + oB = this.bottomOffset, + oL = this.leftOffset, + cw = (opts && opts.width !== undefined) ? + opts.width : + n.clientWidth - oR - oL, + ch = (opts && opts.height !== undefined) ? + opts.height : + n.clientHeight - oT - oB, + s1, s2, md1, md2, d2x, si, is1, is2, d1, d2, minMax, num; + + if (this.direction == 'vertical') { + s1 = ch; + s2 = cw; + md1 = this.minItemHeight; + md2 = this.minItemWidth; + is1 = this.itemHeight; + is2 = this.itemWidth; + d2x = this.columns; + si = 'verticalSnapIncrement'; + } + else { + s1 = cw; + s2 = ch; + md1 = this.minItemWidth; + md2 = this.minItemHeight; + is1 = this.itemWidth; + is2 = this.itemHeight; + d2x = this.rows; + si = 'horizontalSnapIncrement'; + } + + this.sizeItems = (md1 && md2); + + if (this.sizeItems) { + // the number of columns is the ratio of the available width minus the spacing + // by the minimum tile width plus the spacing + d2x = Math.max(Math.floor((s2 - (sp * 2)) / (md2 + sp)), 1); + // the actual tile width is a ratio of the remaining width after all columns + // and spacing are accounted for and the number of columns that we know we should have + is2 = Math.round((s2 - (sp * (d2x + 1))) / d2x); + // the actual tile height is related to the tile width + is1 = Math.round(md1 * (is2 / md2)); + } + else if (d2x === 'auto') { + d2x = 1; + } + + d1 = sp + is1; + d2 = sp + is2; + + minMax = d1 * 2; + this.threshold = { min: -Infinity, max: minMax, minMax: minMax }; + + num = d2x * (Math.ceil(s1 / d1) + this.overhang); + + this.dim2extent = d2x; + this.itemSize = is1; + this.itemSize2 = is2; + this.delta = d1; + this.delta2 = d2; + this.size = s1; + this.size2 = s2; + + if (this.scrollToBoundaries) { + this.set(si, d1); + } + // We don't call the setter here, because doing so would trigger a + // redundant and expensive call to doIt(). This approach should be fine + // as long as calculateMetrics() is called only by reset(). + this.numItems = num; + }, + /** + * @private + */ + scroll: function() { + var tt = this.threshold, + v = (this.direction === 'vertical'), + val = v ? this.scrollTop : this.scrollLeft, + dir = v ? this.yDir : this.xDir, + delta = this.delta, + cb = this.cachedBounds ? this.cachedBounds : this._getScrollBounds(), + maxVal = v ? cb.maxTop : cb.maxLeft, + minMax = this.threshold.minMax, + maxMin = maxVal - minMax, + d2x = this.dim2extent, + d, st, j; + if (dir == 1 && val > tt.max) { + d = val - tt.max; + st = Math.ceil(d / delta); + j = st * delta; + tt.max = Math.min(maxVal, tt.max + j); + tt.min = Math.min(maxMin, tt.max - delta); + this.set('first', (d2x * Math.ceil(this.first / d2x)) + (st * d2x)); + } + else if (dir == -1 && val < tt.min) { + d = tt.min - val; + st = Math.ceil(d / delta); + j = st * delta; + tt.max = Math.max(minMax, tt.min - (j - delta)); + tt.min = (tt.max > minMax) ? tt.max - delta : -Infinity; + this.set('first', (d2x * Math.ceil(this.first / d2x)) - (st * d2x)); + } + if (tt.max > maxVal) { + if (maxVal < minMax) { + tt.max = minMax; + tt.min = -Infinity; + } + else { + tt.max = maxVal; + tt.min = maxMin; + } + } + this.positionChildren(); + }, + /** + * @private + */ + positionChildren: function() { + var oc = this.orderedChildren, + e = this.dim2extent, + v = (this.direction == 'vertical'), + sd = v ? 'scrollTop' : 'scrollLeft', + sv = Math.round(this[sd]), + sp = this.spacing, + is = this.itemSize, + is2 = this.itemSize2, + i, c, idx, g, p, g2, p2, a, b, w, h, fvi, ffvi, lvi, lfvi; + + if (oc) { + for (i = 0; i < oc.length; i++) { + c = oc[i]; + idx = c.index; + g = Math.floor(idx / e); + g2 = idx % e; + p = sp + (g * this.delta) - sv; + p2 = sp + (g2 * this.delta2); + if (v) { + a = p2; + b = p; + w = is2; + h = is; + } + else { + a = p; + b = p2; + w = is; + h = is2; + } + if (this.rtl) { + a = -a; + } + if (this.sizeItems) { + c.applyStyle('width', w + 'px'); + c.applyStyle('height', h + 'px'); + } + if (fvi === undefined && (p + is) > 0) { + fvi = i; + } + if (ffvi === undefined && p >= 0) { + ffvi = i; + } + if ((p + is) <= this.size) { + lfvi = i; + } + if (p < this.size) { + lvi = i; + } + dom.transform(c, {translate3d: a + 'px, ' + b + 'px, 0'}); + } + this.firstVisibleI = fvi; + this.lastVisibleI = lvi; + this.firstFullyVisibleI = ffvi; + this.lastFullyVisibleI = lfvi; + } + }, + + // Choosing perf over DRY, so duplicating some positioning + // logic also implemented in positionChildren() + /** + * @private + */ + getItemBounds: function (index) { + var d2x = this.dim2extent, + sp = this.spacing, + is = this.itemSize, + is2 = this.itemSize2, + g, g2, p, p2; + + g = Math.floor(index / d2x); + g2 = index % d2x; + p = sp + (g * this.delta); + p2 = sp + (g2 * this.delta2); + + return (this.direction == 'vertical') + ? { x: p2, y: p, w: is2, h: is } + : { x: p, y: p2, w: is, h: is2 } + ; + }, + + /** + * @private + */ + getScrollHeight: function () { + return (this.direction === 'vertical' ? this.getVirtualScrollDimension() : null); + }, + /** + * @private + */ + getScrollWidth: function () { + return (this.direction === 'horizontal' ? this.getVirtualScrollDimension() : null); + }, + /** + * @private + */ + getVirtualScrollDimension: function() { + var len = this.collection ? this.collection.length : 0; + + return (Math.ceil(len / this.dim2extent) * this.delta) + this.spacing; + }, + + /** + * @private + */ + modelsAdded: kind.inherit(function (sup) { + return function() { + this.calcBoundaries(); + sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + modelsRemoved: kind.inherit(function (sup) { + return function() { + this.calcBoundaries(); + sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + showingChangedHandler: kind.inherit(function (sup) { + return function () { + if (this.needsReset) { + this.reset(); + } + else if (this.needsRefresh) { + this.refresh(); + } + return sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + refresh: kind.inherit(function (sup) { + return function () { + if (this.getAbsoluteShowing()) { + if (arguments[1] === 'reset') { + this.calcBoundaries(); + } + this.needsRefresh = false; + sup.apply(this, arguments); + } + else { + this.needsRefresh = true; + } + }; + }), + + /** + * @private + */ + reset: kind.inherit(function (sup) { + return function () { + var v; + if (this.getAbsoluteShowing()) { + v = (this.direction === 'vertical'); + this.set('scrollTop', 0); + this.set('scrollLeft', 0); + this.set('vertical', v ? 'auto' : 'hidden'); + this.set('horizontal', v ? 'hidden' : 'auto'); + this.calculateMetrics(); + this.calcBoundaries(); + this.first = 0; + this.needsReset = false; + sup.apply(this, arguments); + } + else { + this.needsReset = true; + } + }; + }) +}); + +},{'./kind':'enyo/kind','./dom':'enyo/dom','./Scrollable':'enyo/Scrollable','./VirtualDataRepeater':'enyo/VirtualDataRepeater'}] + }; + + + // below is where the generated entries list will be assigned if there is one + entries = null; + + + // if a different require exists then we can't actually work because we don't know the call- + // pattern of the existing function to be able to consume it + // @todo it could be possible to use an alternate name internally when another one exists but + // this would lead to inconsistency of expectations to end-devs in the promise that we expose + // our module's via the require function + if (require && !require.hasOwnProperty('enyo')) { + throw new Error('Incompatible require function found in scope'); + } + if (!require || typeof require != 'function') { + require = scope.require = function (target) { + var entry, exports, ctx, map, mod, value, lreq; + // it may have already been resolved + if (exported.hasOwnProperty(target)) return exported[target]; + entry = manifest[target]; + if (!entry) throw new Error( + 'Could not find the required module: "' + target + '"' + ); + if (!(entry instanceof Array)) { + if (typeof entry == 'object' && (entry.source || entry.style)) { + throw new Error( + 'Attempt to require a requested module: "' + target + '"' + ); + } else if (typeof entry == 'string') { + throw new Error( + 'Attempt to require a bundle entry: "' + target + '"' + ); + } + throw new Error( + 'The shared manifest has been corrupted, the module is invalid: "' + target + '"' + ); + } + mod = entry[0]; + map = entry[1]; + if (typeof mod != 'function') throw new Error( + 'The shared manifest has been corrupted, the module is invalid: "' + target + '"' + ); + ctx = {exports: {}}; + if (scope.request) { + if (map) { + lreq = function (name) { + return scope.request(map.hasOwnProperty(name) ? map[name] : name); + }; + lreq.isRequest = scope.request.isRequest; + } else lreq = scope.request; + } + mod( + ctx, + ctx.exports, + scope, + // primarily for sanity/debugging will give a little bit more context when errors + // are encountered + !map ? scope.require : function (name) { + return require(map.hasOwnProperty(name) ? map[name] : name); + }, + lreq + ); + exports = exported[target] = ctx.exports; + return exports; + }; + if (Object.defineProperty) Object.defineProperty(require, 'enyo', { + value: true, + enumerable: false, + configurable: false, + writable: false + }); + else require.enyo = true; + } + + // in occassions where requests api are being used, below this comment that implementation will + // be injected + + + // if another bundle has already established the shared manifest we need to update it with + // our modules + if (require.manifest) { + Object.keys(manifest).forEach(function (key) { + var value = manifest[key], existing; + if ((existing = require.manifest[key])) { + // if it is an array, we automatically accept it because it is a module definition + // and one definition should never overwrite another, the potential fail cases are + // if two requested bundles reference another external bundle and the second + // removes the first one + if (!(value instanceof Array)) return; + } + require.manifest[key] = value; + }); + manifest = require.manifest; + exported = require.exported; + } + // otherwise we need to set it to our manifest + else { + if (Object.defineProperties) { + Object.defineProperties(require, { + manifest: { + value: manifest, + enumerable: false, + configurable: false, + writable: false + }, + exported: { + value: (exported = {}), + enumerable: false, + configurable: false, + writable: false + } + }); + } else { + require.manifest = manifest; + require.exported = (exported = {}); + } + } + + // if this bundle contains any entries that need to be executed it will do that now + if (entries && entries instanceof Array) entries.forEach(function (name) { require(name); }); +})(this); +//# sourceMappingURL=enyo.js.map \ No newline at end of file diff --git a/tests/Tween/reports/Tween_module_getcoeff_test_report_after.pdf b/tests/Tween/reports/Tween_module_getcoeff_test_report_after.pdf new file mode 100644 index 0000000000000000000000000000000000000000..bfc6f7f66fdefce73ff1dcbc266da5fb6ceec0b6 GIT binary patch literal 104209 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

++J&N@;h?w#H;tCO|(Q@Q66K#>@3& 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

--XAr}^|8FS?NgCOM(0srH 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 b3P9Qs{LiyjyvlG3|Hj`du+o*YTk&*amaYC#17X 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 b1!sTw2e|kJEb1c!90`X|(lFTGgZTdkA|{9@ literal 0 HcmV?d00001 diff --git a/tests/Tween/reports/Tween_module_getcoeff_test_report_before.pdf b/tests/Tween/reports/Tween_module_getcoeff_test_report_before.pdf new file mode 100644 index 0000000000000000000000000000000000000000..decd804751e040545a070507c8e4e256b11ec7ca GIT binary patch literal 161636 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
` to `document.body` to serve as a +* container for retained DOM nodes. +* +* @private +*/ +function getNodePurgatory () { + var p = nodePurgatory; + if (!p) { + p = nodePurgatory = document.createElement("div"); + p.id = "node_purgatory"; + p.style.display = "none"; + document.body.appendChild(p); + } + return p; +} + +/** +* {@link module:enyo/Control~Control} is a [component]{@link module:enyo/UiComponent~UiComponent} that controls +* a [DOM]{@glossary DOM} [node]{@glossary Node} (i.e., an element in the user +* interface). Controls are generally visible and the user often interacts with +* them directly. While things like buttons and input boxes are obviously +* controls, in Enyo, a control may be as simple as a text item or as complex +* as an entire application. Both inherit the same basic core capabilities from +* this kind. +* +* For more information, see the documentation on +* [Controls]{@linkplain $dev-guide/key-concepts/controls.html} in the +* Enyo Developer Guide. +* +* **If you make changes to `enyo.Control`, be sure to add or update the +* appropriate unit tests.** +* +* @class Control +* @extends module:enyo/UiComponent~UiComponent +* @ui +* @public +*/ +var Control = module.exports = kind( + /** @lends module:enyo/Control~Control.prototype */ { + + name: 'enyo.Control', + + /** + * @private + */ + kind: UiComponent, + + /** + * @private + */ + mixins: options.accessibility ? [AccessibilitySupport] : null, + + /** + * @type {String} + * @default 'module:enyo/Control~Control' + * @public + */ + defaultKind: null, // set after the fact + + /** + * The [DOM node]{@glossary DOM} tag name that should be created. + * + * @type {String} + * @default 'div' + * @public + */ + tag: 'div', + + /** + * A [hash]{@glossary Object} of attributes to be applied to the created + * [DOM]{@glossary DOM} node. + * + * @type {Object} + * @default null + * @public + */ + attributes: null, + + /** + * [Boolean]{@glossary Boolean} flag indicating whether this element should + * "fit", or fill its container's size. + * + * @type {Boolean} + * @default null + * @public + */ + fit: null, + + /** + * [Boolean]{@glossary Boolean} flag indicating whether HTML is allowed in + * this control's [content]{@link module:enyo/Control~Control#content} property. If `false` + * (the default), HTML will be encoded into [HTML entities]{@glossary entity} + * (e.g., `<` and `>`) for literal visual representation. + * + * @type {Boolean} + * @default null + * @public + */ + allowHtml: false, + + /** + * Mimics the HTML `style` attribute. + * + * @type {String} + * @default '' + * @public + */ + style: '', + + /** + * @private + */ + kindStyle: '', + + /** + * Mimics the HTML `class` attribute. + * + * @type {String} + * @default '' + * @public + */ + classes: '', + + /** + * @private + */ + kindClasses: '', + + /** + * [Classes]{@link module:enyo/Control~Control#classes} that are applied to all controls. + * + * @type {String} + * @default '' + * @public + */ + controlClasses: '', + + /** + * The text-based content of the Control. If the [allowHtml]{@link module:enyo/Control~Control#allowHtml} + * flag is set to `true`, you may set this property to an HTML string. + * @public + */ + content: '', + + /** + * If true or 'inherit' and enyo/gesture#doubleTabEnabled == true, will fire a doubletap + * event, and will temporarily suppress a single tap while waiting for a double tap. + * + * @type {String|Boolean} + * @default 'inherit' + * @public + */ + doubleTapEnabled: 'inherit', + + /** + * Time in milliseconds to wait to detect a double tap + * + * @type {Number} + * @default 300 + * @public + */ + doubleTapInterval: 300, + + /** + * If set to `true`, the [control]{@link module:enyo/Control~Control} will not be rendered until its + * [showing]{@link module:enyo/Control~Control#showing} property has been set to `true`. This can be used + * directly or is used by some widgets to control when children are rendered. + * + * It is important to note that setting this to `true` will _force_ + * [canGenerate]{@link module:enyo/Control~Control#canGenerate} and [showing]{@link module:enyo/Control~Control#showing} + * to be `false`. Arbitrarily modifying the values of these properties prior to its initial + * render may have unexpected results. + * + * Once a control has been shown/rendered with `renderOnShow` `true` the behavior will not + * be used again. + * + * @type {Boolean} + * @default false + * @public + */ + renderOnShow: false, + + /** + * @todo Find out how to document "handlers". + * @public + */ + handlers: { + ontap: 'tap', + onShowingChanged: 'showingChangedHandler' + }, + + /** + * @private + */ + strictlyInternalEvents: {onenter: 1, onleave: 1}, + + /** + * @private + */ + isInternalEvent: function (event) { + var rdt = dispatcher.findDispatchTarget(event.relatedTarget); + return rdt && rdt.isDescendantOf(this); + }, + + // ................................. + // DOM NODE MANIPULATION API + + /** + * Gets the bounds for this control. The `top` and `left` properties returned + * by this method represent the control's positional distance in pixels from + * either A) the first parent of this control that is absolutely or relatively + * positioned, or B) the `document.body`. + * + * This is a shortcut convenience method for {@link module:enyo/dom#getBounds}. + * + * @returns {Object} An [object]{@glossary Object} containing `top`, `left`, + * `width`, and `height` properties. + * @public + */ + getBounds: function () { + var node = this.hasNode(), + bounds = node && Dom.getBounds(node); + + return bounds || {left: undefined, top: undefined, width: undefined, height: undefined}; + }, + + /** + * Sets the absolute/relative position and/or size for this control. Values + * of `null` or `undefined` for the `bounds` properties will be ignored. You + * may optionally specify a `unit` (i.e., a valid CSS measurement unit) as a + * [string]{@glossary String} to be applied to each of the position/size + * assignments. + * + * @param {Object} bounds - An [object]{@glossary Object}, optionally + * containing one or more of the following properties: `width`, `height`, + * `top`, `right`, `bottom`, and `left`. + * @param {String} [unit='px'] + * @public + */ + setBounds: function (bounds, unit) { + var newStyle = '', + extents = ['width', 'height', 'left', 'top', 'right', 'bottom'], + i = 0, + val, + ext; + + // if no unit is supplied, we default to pixels + unit = unit || 'px'; + + for (; (ext = extents[i]); ++i) { + val = bounds[ext]; + if (val || val === 0) { + newStyle += (ext + ':' + val + (typeof val == 'string' ? '' : unit) + ';'); + } + } + + this.set('style', this.style + newStyle); + }, + + /** + * Gets the bounds for this control. The `top` and `left` properties returned + * by this method represent the control's positional distance in pixels from + * `document.body`. To get the bounds relative to this control's parent(s), + * use [getBounds()]{@link module:enyo/Control~Control#getBounds}. + * + * This is a shortcut convenience method for {@link module:enyo/dom#getAbsoluteBounds}. + * + * @returns {Object} An [object]{@glossary Object} containing `top`, `left`, + * `width`, and `height` properties. + * @public + */ + getAbsoluteBounds: function () { + var node = this.hasNode(), + bounds = node && Dom.getAbsoluteBounds(node); + + return bounds || { + left: undefined, + top: undefined, + width: undefined, + height: undefined, + bottom: undefined, + right: undefined + }; + }, + + /** + * Shortcut method to set [showing]{@link module:enyo/Control~Control#showing} to `true`. + * + * @public + */ + show: function () { + this.set('showing', true); + }, + + /** + * Shortcut method to set [showing]{@link module:enyo/Control~Control#showing} to `false`. + * + * @public + */ + hide: function () { + this.set('showing', false); + }, + + /** + * Sets this control to be [focused]{@glossary focus}. + * + * @public + */ + focus: function () { + if (this.hasNode()) this.node.focus(); + }, + + /** + * [Blurs]{@glossary blur} this control. (The opposite of + * [focus()]{@link module:enyo/Control~Control#focus}.) + * + * @public + */ + blur: function () { + if (this.hasNode()) this.node.blur(); + }, + + /** + * Determines whether this control currently has the [focus]{@glossary focus}. + * + * @returns {Boolean} Whether this control has focus. `true` if the control + * has focus; otherwise, `false`. + * @public + */ + hasFocus: function () { + if (this.hasNode()) return document.activeElement === this.node; + }, + + /** + * Determines whether this control's [DOM node]{@glossary Node} has been created. + * + * @returns {Boolean} Whether this control's [DOM node]{@glossary Node} has + * been created. `true` if it has been created; otherwise, `false`. + * @public + */ + hasNode: function () { + return this.generated && (this.node || this.findNodeById()); + }, + + /** + * Gets the requested property (`name`) from the control's attributes + * [hash]{@glossary Object}, from its cache of node attributes, or, if it has + * yet to be cached, from the [node]{@glossary Node} itself. + * + * @param {String} name - The attribute name to get. + * @returns {(String|Null)} The value of the requested attribute, or `null` + * if there isn't a [DOM node]{@glossary Node} yet. + * @public + */ + getAttribute: function (name) { + var node; + + // TODO: This is a fixed API assuming that no changes will happen to the DOM that + // do not use it...original implementation of this method used the node's own + // getAttribute method every time it could but we really only need to do that if we + // weren't the ones that set the value to begin with -- in slow DOM situations this + // could still be faster but it needs to be verified + if (this.attributes.hasOwnProperty(name)) return this.attributes[name]; + else { + node = this.hasNode(); + + // we store the value so that next time we'll know what it is + /*jshint -W093 */ + return (this.attributes[name] = (node ? node.getAttribute(name) : null)); + /*jshint +W093 */ + } + }, + + /** + * Assigns an attribute to a control's [node]{@glossary Node}. Assigning + * `name` a value of `null`, `false`, or the empty string `("")` will remove + * the attribute from the node altogether. + * + * @param {String} name - Attribute name to assign/remove. + * @param {(String|Number|null)} value - The value to assign to `name` + * @returns {this} Callee for chaining. + * @public + */ + setAttribute: function (name, value) { + var attrs = this.attributes, + node = this.hasNode(), + delegate = this.renderDelegate || Control.renderDelegate; + + if (name) { + attrs[name] = value; + + if (node) { + if (value == null || value === false || value === '') { + node.removeAttribute(name); + } else node.setAttribute(name, value); + } + + delegate.invalidate(this, 'attributes'); + } + + return this; + }, + + /** + * Reads the `name` property directly from the [node]{@glossary Node}. You + * may provide a default (`def`) to use if there is no node yet. + * + * @param {String} name - The [node]{@glossary Node} property name to get. + * @param {*} def - The default value to apply if there is no node. + * @returns {String} The value of the `name` property, or `def` if the node + * was not available. + * @public + */ + getNodeProperty: function (name, def) { + return this.hasNode() ? this.node[name] : def; + }, + + /** + * Sets the value of a property (`name`) directly on the [node]{@glossary Node}. + * + * @param {String} name - The [node]{@glossary Node} property name to set. + * @param {*} value - The value to assign to the property. + * @returns {this} The callee for chaining. + * @public + */ + setNodeProperty: function (name, value) { + if (this.hasNode()) this.node[name] = value; + return this; + }, + + /** + * Appends additional content to this control. + * + * @param {String} content - The new string to add to the end of the `content` + * property. + * @returns {this} The callee for chaining. + * @public + */ + addContent: function (content) { + return this.set('content', this.get('content') + content); + }, + + // ................................. + + // ................................. + // STYLE/CLASS API + + /** + * Determines whether this control has the class `name`. + * + * @param {String} name - The name of the class (or classes) to check for. + * @returns {Boolean} Whether the control has the class `name`. + * @public + */ + hasClass: function (name) { + return name && (' ' + this.classes + ' ').indexOf(' ' + name + ' ') > -1; + }, + + /** + * Adds the specified class to this control's list of classes. + * + * @param {String} name - The name of the class to add. + * @returns {this} The callee for chaining. + * @public + */ + addClass: function (name) { + var classes = this.classes || ''; + + // NOTE: Because this method accepts a string and for efficiency does not wish to + // parse it to determine if it is actually multiple classes we later pull a trick + // to keep it normalized and synchronized with our attributes hash and the node's + if (!this.hasClass(name)) { + + // this is hooked + this.set('classes', classes + (classes ? (' ' + name) : name)); + } + + return this; + }, + + /** + * Removes the specified class from this control's list of classes. + * + * **Note: It is not advisable to pass a string of multiple, space-delimited + * class names into this method. Instead, call the method once for each class + * name that you want to remove.** + * + * @param {String} name - The name of the class to remove. + * @returns {this} The callee for chaining. + * @public + */ + removeClass: function (name) { + var classes = this.classes; + + if (name) { + this.set('classes', (' ' + classes + ' ').replace(' ' + name + ' ', ' ').trim()); + } + + return this; + }, + + /** + * Adds or removes the specified class conditionally, based on the state + * of the `add` argument. + * + * @param {String} name - The name of the class to add or remove. + * @param {Boolean} add - If `true`, `name` will be added as a class; if + * `false`, it will be removed. + * @returns {this} The callee for chaining. + * @public + */ + addRemoveClass: function (name, add) { + return name ? this[add ? 'addClass' : 'removeClass'](name) : this; + }, + + /** + * @private + */ + classesChanged: function () { + var classes = this.classes, + node = this.hasNode(), + attrs = this.attributes, + delegate = this.renderDelegate || Control.renderDelegate; + + if (node) { + if (classes || this.kindClasses) { + node.setAttribute('class', classes || this.kindClasses); + } else node.removeAttribute('class'); + + this.classes = classes = node.getAttribute('class'); + } + + // we need to update our attributes.class value and flag ourselves to be + // updated + attrs['class'] = classes; + + // we want to notify the delegate that the attributes have changed in case it wants + // to handle this is some special way + delegate.invalidate(this, 'attributes'); + }, + + /** + * Applies a CSS style directly to the control. Use the `prop` argument to + * specify the CSS property name you'd like to set, and `value` to specify + * the desired value. Setting `value` to `null` will remove the CSS property + * `prop` altogether. + * + * @param {String} prop - The CSS property to assign. + * @param {(String|Number|null|undefined)} value - The value to assign to + * `prop`. Setting a value of `null`, `undefined`, or the empty string `("")` + * will remove the property `prop` from the control. + * @returns {this} Callee for chaining. + * @public + */ + applyStyle: function (prop, value) { + + // NOTE: This method deliberately avoids calling set('style', ...) for performance + // as it will have already been parsed by the browser so we pass it on via the + // notification system which is the same + + // TODO: Wish we could delay this potentially... + // if we have a node we render the value immediately and update our style string + // in the process to keep them synchronized + var node = this.hasNode(), + style = this.style, + delegate = this.renderDelegate || Control.renderDelegate; + + // FIXME: This is put in place for a Firefox bug where setting a style value of a node + // via its CSSStyleDeclaration object (by accessing its node.style property) does + // not work when using a CSS property name that contains one or more dash, and requires + // setting the property via the JavaScript-style property name. This fix should be + // removed once this issue has been resolved in the Firefox mainline and its variants + // (it is currently resolved in the 36.0a1 nightly): + // https://bugzilla.mozilla.org/show_bug.cgi?id=1083457 + if (node && (platform.firefox < 35 || platform.firefoxOS || platform.androidFirefox)) { + prop = prop.replace(/-([a-z])/gi, function(match, submatch) { + return submatch.toUpperCase(); + }); + } + + if (value !== null && value !== '' && value !== undefined) { + // update our current cached value + if (node) { + node.style[prop] = value; + + // cssText is an internal property used to help know when to sync and not + // sync with the node in styleChanged + this.style = this.cssText = node.style.cssText; + + // otherwise we have to try and prepare it for the next time it is rendered we + // will need to update it because it will not be synchronized + } else this.set('style', style + (' ' + prop + ':' + value + ';')); + } else { + + // in this case we are trying to clear the style property so if we have the node + // we let the browser handle whatever the value should be now and otherwise + // we have to parse it out of the style string and wait to be rendered + + if (node) { + node.style[prop] = ''; + this.style = this.cssText = node.style.cssText; + + // we need to invalidate the style for the delegate + delegate.invalidate(this, 'style'); + } else { + + // this is a rare case to nullify the style of a control that is not + // rendered or does not have a node + style = style.replace(new RegExp( + // This looks a lot worse than it is. The complexity stems from needing to + // match a url container that can have other characters including semi- + // colon and also that the last property may/may-not end with one + '\\s*' + prop + '\\s*:\\s*[a-zA-Z0-9\\ ()_\\-\'"%,]*(?:url\\(.*\\)\\s*[a-zA-Z0-9\\ ()_\\-\'"%,]*)?\\s*(?:;|;?$)', + 'gi' + ),''); + this.set('style', style); + } + } + // we need to invalidate the style for the delegate -- regardless of whether or + // not the node exists to ensure that the tag is updated properly the next time + // it is rendered + delegate.invalidate(this, 'style'); + + return this; + }, + + /** + * Allows the addition of several CSS properties and values at once, via a + * single string, similar to how the HTML `style` attribute works. + * + * @param {String} css - A string containing one or more valid CSS styles. + * @returns {this} The callee for chaining. + * @public + */ + addStyles: function (css) { + var key, + newStyle = ''; + + if (typeof css == 'object') { + for (key in css) newStyle += (key + ':' + css[key] + ';'); + } else newStyle = css || ''; + + this.set('style', this.style + newStyle); + }, + + /** + * @private + */ + styleChanged: function () { + var delegate = this.renderDelegate || Control.renderDelegate; + + // if the cssText internal string doesn't match then we know style was set directly + if (this.cssText !== this.style) { + + // we need to render the changes and synchronize - this means that the style + // property was set directly so we will reset it prepending it with the original + // style (if any) for the kind and keeping whatever the browser is keeping + if (this.hasNode()) { + this.node.style.cssText = this.kindStyle + (this.style || ''); + // now we store the parsed version + this.cssText = this.style = this.node.style.cssText; + } + + // we need to ensure that the delegate has an opportunity to handle this change + // separately if it needs to + delegate.invalidate(this, 'style'); + } + }, + + /** + * Retrieves a control's CSS property value. This doesn't just pull the + * assigned value of `prop`; it returns the browser's understanding of `prop`, + * the "computed" value. If the control isn't been rendered yet, and you need + * a default value (such as `0`), include it in the arguments as `def`. + * + * @param {String} prop - The property name to get. + * @param {*} [def] - An optional default value, in case the control isn't + * rendered yet. + * @returns {(String|Number)} The computed value of `prop`, as the browser + * sees it. + * @public + */ + getComputedStyleValue: function (prop, def) { + return this.hasNode() ? Dom.getComputedStyleValue(this.node, prop) : def; + }, + + /** + * @private + */ + findNodeById: function () { + return this.id && (this.node = Dom.byId(this.id)); + }, + + /** + * @private + */ + idChanged: function (was) { + if (was) Control.unregisterDomEvents(was); + if (this.id) { + Control.registerDomEvents(this.id, this); + this.setAttribute('id', this.id); + } + }, + + /** + * @private + */ + contentChanged: function () { + var delegate = this.renderDelegate || Control.renderDelegate; + delegate.invalidate(this, 'content'); + }, + + /** + * If the control has been generated, re-flows the control. + * + * @public + */ + beforeChildRender: function () { + // if we are generated, we should flow before rendering a child; + // if not, the render context isn't ready anyway + if (this.generated) this.flow(); + }, + + /** + * @private + */ + showingChanged: function (was) { + var nextControl; + // if we are changing from not showing to showing we attempt to find whatever + // our last known value for display was or use the default + if (!was && this.showing) { + this.applyStyle('display', this._display || ''); + + // note the check for generated and canGenerate as changes to canGenerate will force + // us to ignore the renderOnShow value so we don't undo whatever the developer was + // intending + if (!this.generated && !this.canGenerate && this.renderOnShow) { + nextControl = this.getNextControl(); + if (nextControl && !this.addBefore) this.addBefore = nextControl; + this.set('canGenerate', true); + this.render(); + } + + this.sendShowingChangedEvent(was); + } + + // if we are supposed to be hiding the control then we need to cache our current + // display state + else if (was && !this.showing) { + this.sendShowingChangedEvent(was); + // we can't truly cache this because it _could_ potentially be set to multiple + // values throughout its lifecycle although that seems highly unlikely... + this._display = this.hasNode() ? this.node.style.display : ''; + this.applyStyle('display', 'none'); + } + + }, + + /** + * @private + */ + renderOnShowChanged: function () { + // ensure that the default value assigned to showing is actually a boolean + // and that it is only true if the renderOnShow is also false + this.showing = ((!!this.showing) && !this.renderOnShow); + // we want to check and make sure that the canGenerate value is correct given + // the state of renderOnShow + this.canGenerate = (this.canGenerate && !this.renderOnShow); + }, + + /** + * @private + */ + sendShowingChangedEvent: function (was) { + var waterfall = (was === true || was === false), + parent = this.parent; + + // make sure that we don't trigger the waterfall when this method + // is arbitrarily called during _create_ and it should only matter + // that it changed if our parent's are all showing as well + if (waterfall && (parent ? parent.getAbsoluteShowing(true) : true)) { + this.waterfall('onShowingChanged', {originator: this, showing: this.showing}); + } + }, + + /** + * Returns `true` if this control and all parents are showing. + * + * @param {Boolean} ignoreBounds - If `true`, it will not force a layout by retrieving + * computed bounds and rely on the return from [showing]{@link module:enyo/Control~Control#showing} + * exclusively. + * @returns {Boolean} Whether the control is showing (visible). + * @public + */ + getAbsoluteShowing: function (ignoreBounds) { + var bounds = !ignoreBounds ? this.getBounds() : null, + parent = this.parent; + + if (!this.generated || this.destroyed || !this.showing || (bounds && + bounds.height === 0 && bounds.width === 0)) { + return false; + } + + if (parent && parent.getAbsoluteShowing) { + + // we actually don't care what the parent says if it is the floating layer + if (!this.parentNode || (this.parentNode !== Control.floatingLayer.hasNode())) { + return parent.getAbsoluteShowing(ignoreBounds); + } + } + + return true; + }, + + /** + * Handles the `onShowingChanged` event that is waterfalled by controls when + * their `showing` value is modified. If the control is not showing itself + * already, it will not continue the waterfall. Overload this method to + * provide additional handling for this event. + * + * @private + */ + showingChangedHandler: function (sender, event) { + // If we have deferred a reflow, do it now... + if (this.showing && this._needsReflow) { + this.reflow(); + } + + // Then propagate `onShowingChanged` if appropriate + return sender === this ? false : !this.showing; + }, + + /** + * Overriding reflow() so that we can take `showing` into + * account and defer reflowing accordingly. + * + * @private + */ + reflow: function () { + if (this.layout) { + this._needsReflow = this.showing ? this.layout.reflow() : true; + } + }, + + /** + * @private + */ + fitChanged: function () { + this.parent.reflow(); + }, + + /** + * Determines whether we are in fullscreen mode or not. + * + * @returns {Boolean} Whether we are currently in fullscreen mode. + * @public + */ + isFullscreen: function () { + return (this.hasNode() && this.node === Control.Fullscreen.getFullscreenElement()); + }, + + /** + * Requests that this control be displayed fullscreen (like a video + * container). If the request is granted, the control fills the screen and + * `true` is returned; if the request is denied, the control is not resized + * and `false` is returned. + * + * @returns {Boolean} `true` on success; otherwise, `false`. + * @public + */ + requestFullscreen: function () { + if (!this.hasNode()) return false; + + if (Control.Fullscreen.requestFullscreen(this)) { + return true; + } + + return false; + }, + + /** + * Ends fullscreen mode for this control. + * + * @returns {Boolean} If the control was in fullscreen mode before this + * method was called, it is taken out of that mode and `true` is returned; + * otherwise, `false` is returned. + * @public + */ + cancelFullscreen: function() { + if (this.isFullscreen()) { + Control.Fullscreen.cancelFullscreen(); + return true; + } + + return false; + }, + + // ................................. + + // ................................. + // RENDER-SCHEME API + + /** + * Indicates whether the control is allowed to be generated, i.e., rendered + * into the [DOM]{@glossary DOM} tree. + * + * @type {Boolean} + * @default true + * @public + */ + canGenerate: true, + + /** + * Indicates whether the control is visible. + * + * @type {Boolean} + * @default true + * @public + */ + showing: true, + + /** + * The [node]{@glossary Node} that this control will be rendered into. + * + * @type {module:enyo/Control~Control} + * @default null + * @public + */ + renderDelegate: null, + + /** + * Indicates whether the control has been generated yet. + * + * @type {Boolean} + * @default false + * @private + */ + generated: false, + + /** + * Forces the control to be rendered. You should use this sparingly, as it + * can be costly, but it may be necessary in cases where a control or its + * contents have been updated surreptitiously. + * + * @returns {this} The callee for chaining. + * @public + */ + render: function () { + + // prioritize the delegate set for this control otherwise use the default + var delegate = this.renderDelegate || Control.renderDelegate; + + // the render delegate acts on the control + delegate.render(this); + + return this; + }, + + /** + * Takes this control and drops it into a (new/different) + * [DOM node]{@glossary Node}. This will replace any existing nodes in the + * target `parentNode`. + * + * @param {Node} parentNode - The new parent of this control. + * @param {Boolean} preventRooting - If `true`, this control will not be treated as a root + * view and will not be added to the set of roots. + * @returns {this} The callee for chaining. + * @public + */ + renderInto: function (parentNode, preventRooting) { + var delegate = this.renderDelegate || Control.renderDelegate, + noFit = this.fit === false; + + // attempt to retrieve the parentNode + parentNode = Dom.byId(parentNode); + + // teardown in case of previous render + delegate.teardownRender(this); + + if (parentNode == document.body && !noFit) this.setupBodyFitting(); + else if (this.fit) this.addClass('enyo-fit enyo-clip'); + + // for IE10 support, we want full support over touch actions in enyo-rendered areas + this.addClass('enyo-no-touch-action'); + + // add css to enable hw-accelerated scrolling on non-android platforms + // ENYO-900, ENYO-901 + this.setupOverflowScrolling(); + + // if there are unflushed body classes we flush them now... + Dom.flushBodyClasses(); + + // we inject this as a root view because, well, apparently that is just an assumption + // we've been making... + if (!preventRooting) { + roots.addToRoots(this); + } + + // now let the delegate render it the way it needs to + delegate.renderInto(this, parentNode); + + Dom.updateScaleFactor(); + + return this; + }, + + /** + * A function that fires after the control has rendered. This performs a + * reflow. + * + * @public + */ + rendered: function () { + var child, + i = 0; + + // CAVEAT: Currently we use one entry point ('reflow') for + // post-render layout work *and* post-resize layout work. + this.reflow(); + + for (; (child = this.children[i]); ++i) { + if (child.generated) child.rendered(); + } + }, + + /** + * You should generally not need to call this method in your app code. + * It is used internally by some Enyo UI libraries to handle a rare + * issue that sometimes arises when using a virtualized list or repeater + * on a touch device. + * + * This issue occurs when a gesture (e.g. a drag) originates with a DOM + * node that ends up being destroyed in mid-gesture as the list updates. + * When the node is destroyed, the stream of DOM events representing the + * gesture stops, causing the associated action to stop or otherwise + * fail. + * + * You can prevent this problem from occurring by calling `retainNode` + * on the {@link module:enyo/Control~Control} from which the gesture originates. Doing + * so will cause Enyo to keep the DOM node around (hidden from view) + * until you explicitly release it. You should call `retainNode` in the + * event handler for the event that starts the gesture. + * + * `retainNode` returns a function that you must call when the gesture + * ends to release the node. Make sure you call this function to avoid + * "leaking" the DOM node (failing to remove it from the DOM). + * + * @param {Node} node - Optional. Defaults to the node associated with + * the Control (`Control.node`). You can generally omit this parameter + * when working with {@link module:enyo/DataList~DataList} or {@link module:enyo/DataGridList~DataGridList}, + * but should generally pass in the event's target node (`event.target`) + * when working with {@link module:layout/List~List}. (Because {@link module:layout/List~List} is + * based on the Flyweight pattern, the event's target node is often not + * the node currently associated with the Control at the time the event + * occurs.) + * @returns {Function} Keep a reference to this function and call it + * to release the node when the gesture has ended. + * @public + */ + retainNode: function(node) { + var control = this, + retainedNode = this._retainedNode = (node || this.hasNode()); + return function() { + if (control && (control._retainedNode == retainedNode)) { + control._retainedNode = null; + } else { + releaseRetainedNode(retainedNode); + } + }; + }, + + /** + * @param {Boolean} [cache] - Whether or not we are tearing down as part of a destroy + * operation, or if we are just caching. If `true`, the `showing` and `canGenerate` + * properties of the control will not be reset. + * @private + */ + teardownRender: function (cache) { + var delegate = this.renderDelegate || Control.renderDelegate; + + if (this._retainedNode) { + storeRetainedNode(this); + } + + delegate.teardownRender(this, cache); + + // if the original state was set with renderOnShow true then we need to reset these + // values as well to coordinate the original intent + if (this.renderOnShow && !cache) { + this.set('showing', false); + this.set('canGenerate', false); + } + }, + + /** + * @private + */ + teardownChildren: function () { + var delegate = this.renderDelegate || Control.renderDelegate; + + delegate.teardownChildren(this); + }, + + /** + * @private + */ + addNodeToParent: function () { + var pn; + + if (this.node) { + pn = this.getParentNode(); + if (pn) { + if (this.addBefore !== undefined) { + this.insertNodeInParent(pn, this.addBefore && this.addBefore.hasNode()); + } else this.appendNodeToParent(pn); + } + } + }, + + /** + * @private + */ + appendNodeToParent: function(parentNode) { + parentNode.appendChild(this.node); + }, + + /** + * @private + */ + insertNodeInParent: function(parentNode, beforeNode) { + parentNode.insertBefore(this.node, beforeNode || parentNode.firstChild); + }, + + /** + * @private + */ + removeNodeFromDom: function() { + var node = this.hasNode(); + if (node) { + if (node.remove) node.remove(); + else if (node.parentNode) node.parentNode.removeChild(node); + } + }, + + /** + * @private + */ + getParentNode: function () { + return this.parentNode || (this.parent && ( + this.parent.hasNode() || this.parent.getParentNode()) + ); + }, + + // ................................. + + /** + * @private + */ + constructor: kind.inherit(function (sup) { + return function (props) { + var attrs = props && props.attributes; + + // ensure that we both keep an instance copy of defined attributes but also + // update the hash with any additional instance definitions at runtime + this.attributes = this.attributes ? utils.clone(this.attributes) : {}; + if (attrs) { + utils.mixin(this.attributes, attrs); + delete props.attributes; + } + + return sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + create: kind.inherit(function (sup) { + return function (props) { + var classes; + + // initialize the styles for this instance + this.style = this.kindStyle + this.style; + + // set initial values based on renderOnShow + this.renderOnShowChanged(); + + // super initialization + sup.apply(this, arguments); + + // ensure that if we aren't showing -> true then the correct style + // is applied - note that there might be issues with this because we are + // trying not to have to parse out any other explicit display value during + // initialization and we can't check because we haven't rendered yet + if (!this.showing) this.style += ' display: none;'; + + // try and make it so we only need to call the method once during + // initialization and only then when we have something to add + classes = this.kindClasses; + if (classes && this.classes) classes += (' ' + this.classes); + else if (this.classes) classes = this.classes; + + // if there are known classes needed to be applied from the kind + // definition and the instance definition (such as a component block) + this.classes = this.attributes['class'] = classes ? classes.trim() : classes; + + // setup the id for this control if we have one + this.idChanged(); + this.contentChanged(); + }; + }), + + /** + * Destroys the control and removes it from the [DOM]{@glossary DOM}. Also + * removes the control's ability to receive bubbled events. + * + * @public + */ + destroy: kind.inherit(function (sup) { + return function() { + // if the control has been rendered we ensure it is removed from the DOM + this.removeNodeFromDom(); + + // ensure no other bubbled events can be dispatched to this control + dispatcher.$[this.id] = null; + sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + dispatchEvent: kind.inherit(function (sup) { + return function (name, event, sender) { + // prevent dispatch and bubble of events that are strictly internal (e.g. + // enter/leave) + if (this.strictlyInternalEvents[name] && this.isInternalEvent(event)) { + return true; + } + return sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + addChild: kind.inherit(function (sup) { + return function (control) { + control.addClass(this.controlClasses); + sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + removeChild: kind.inherit(function (sup) { + return function (control) { + sup.apply(this, arguments); + control.removeClass(this.controlClasses); + }; + }), + + /** + * @private + */ + set: kind.inherit(function (sup) { + return function (path, value, opts) { + // this should be updated if a better api for hooking becomes available but for + // now we just do this directly to ensure that the showing value is actually + // a boolean + if (path == 'showing') { + return sup.call(this, path, !! value, opts); + } else return sup.apply(this, arguments); + }; + }), + + // ................................. + // BACKWARDS COMPATIBLE API, LEGACY METHODS AND PUBLIC PROPERTY + // METHODS OR PROPERTIES THAT PROBABLY SHOULD NOT BE HERE BUT ARE ANYWAY + + /** + * Apparently used by Ares 2 still but we have the property embedded in the kind... + * + * @deprecated + * @private + */ + isContainer: false, + + /** + * @private + */ + rtl: false, + + /** + * @private + */ + setupBodyFitting: function () { + Dom.applyBodyFit(); + this.addClass('enyo-fit enyo-clip'); + }, + + /* + * If the platform is Android or Android-Chrome, don't include the css rule + * `-webkit-overflow-scrolling: touch`, as it is not supported in Android and leads to + * overflow issues (ENYO-900 and ENYO-901). Similarly, BB10 has issues repainting + * out-of-viewport content when `-webkit-overflow-scrolling` is used (ENYO-1396). + * + * @private + */ + setupOverflowScrolling: function () { + if(platform.android || platform.androidChrome || platform.blackberry) { + return; + } + Dom.addBodyClass('webkitOverflowScrolling'); + }, + + /** + * Sets the control's directionality based on its content, or an optional `stringInstead`. + * + * @param {String} [stringInstead] An alternate string for consideration may be sent instead, + * in-case the string to test the directionality of the control is stored in `this.value`, + * or some other property, for example. + * @private + */ + detectTextDirectionality: function (stringInstead) { + // If an argument was supplied at all, use it, even if it's undefined. + // Values that are null or undefined, or are numbers, arrays, and some objects are safe + // to be tested. + var str = (arguments.length) ? stringInstead : this.content; + if (str || str === 0) { + this.rtl = utils.isRtl(str); + this.applyStyle('direction', this.rtl ? 'rtl' : 'ltr'); + } else { + this.applyStyle('direction', null); + } + + }, + + // ................................. + + // ................................. + // DEPRECATED + + /** + * @deprecated + * @public + */ + getTag: function () { + return this.tag; + }, + + /** + * @deprecated + * @public + */ + setTag: function (tag) { + var was = this.tag; + + if (tag && typeof tag == 'string') { + this.tag = tag; + if (was !== tag) this.notify('tag', was, tag); + } + return this; + }, + + /** + * @deprecated + * @public + */ + getAttributes: function () { + return this.attributes; + }, + + /** + * @deprecated + * @public + */ + setAttributes: function (attrs) { + var was = this.attributes; + + if (typeof attrs == 'object') { + this.attributes = attrs; + if (attrs !== was) this.notify('attributes', was, attrs); + } + + return this; + }, + + /** + * @deprecated + * @public + */ + getClasses: function () { + return this.classes; + }, + + /** + * @deprecated + * @public + */ + setClasses: function (classes) { + var was = this.classes; + + this.classes = classes; + if (was != classes) this.notify('classes', was, classes); + + return this; + }, + + /** + * @deprecated + * @public + */ + getStyle: function () { + return this.style; + }, + + /** + * @deprecated + * @public + */ + setStyle: function (style) { + var was = this.style; + + this.style = style; + if (was != style) this.notify('style', was, style); + + return this; + }, + + /** + * @deprecated + * @public + */ + getContent: function () { + return this.content; + }, + + /** + * @deprecated + * @public + */ + setContent: function (content) { + var was = this.content; + this.content = content; + + if (was != content) this.notify('content', was, content); + + return this; + }, + + /** + * @deprecated + * @public + */ + getShowing: function () { + return this.showing; + }, + + /** + * @deprecated + * @public + */ + setShowing: function (showing) { + var was = this.showing; + + // force the showing property to always be a boolean value + this.showing = !! showing; + + if (was != showing) this.notify('showing', was, showing); + + return this; + }, + + /** + * @deprecated + * @public + */ + getAllowHtml: function () { + return this.allowHtml; + }, + + /** + * @deprecated + * @public + */ + setAllowHtml: function (allow) { + var was = this.allowHtml; + this.allowHtml = !! allow; + + if (was !== allow) this.notify('allowHtml', was, allow); + + return this; + }, + + /** + * @deprecated + * @public + */ + getCanGenerate: function () { + return this.canGenerate; + }, + + /** + * @deprecated + * @public + */ + setCanGenerate: function (can) { + var was = this.canGenerate; + this.canGenerate = !! can; + + if (was !== can) this.notify('canGenerate', was, can); + + return this; + }, + + /** + * @deprecated + * @public + */ + getFit: function () { + return this.fit; + }, + + /** + * @deprecated + * @public + */ + setFit: function (fit) { + var was = this.fit; + this.fit = !! fit; + + if (was !== fit) this.notify('fit', was, fit); + + return this; + }, + + /** + * @ares + * @deprecated + * @public + */ + getIsContainer: function () { + return this.isContainer; + }, + + /** + * @ares + * @deprecated + * @public + */ + setIsContainer: function (isContainer) { + var was = this.isContainer; + this.isContainer = !! isContainer; + + if (was !== isContainer) this.notify('isContainer', was, isContainer); + + return this; + } + + // ................................. + +}); + +/** +* @static +* @public +*/ +kind.setDefaultCtor(Control); + +/** +* @static +* @public +*/ +Control.renderDelegate = HTMLStringDelegate; + +/** +* @private +*/ +Control.registerDomEvents = function (id, control) { + dispatcher.$[id] = control; +}; + +/** +* @private +*/ +Control.unregisterDomEvents = function (id) { + dispatcher.$[id] = null; +}; + +/** +* @private +*/ +Control.normalizeCssStyleString = function (style) { + return style ? ( + (";" + style) + // add a semi-colon if it's not the last character (also trim possible unnecessary whitespace) + .replace(/([^;])\s*$/, "$1;") + // ensure we have one space after each colon or semi-colon + .replace(/\s*;\s*([\w-]+)\s*:\s*/g, "; $1: ") + // remove first semi-colon and space + .substr(2).trim() + ) : ""; +}; + +/** +* @private +*/ +Control.concat = function (ctor, props, instance) { + var proto = ctor.prototype || ctor, + attrs, + str; + + if (props.classes) { + if (instance) { + str = (proto.classes ? (proto.classes + ' ') : '') + props.classes; + proto.classes = str; + } else { + str = (proto.kindClasses || '') + (proto.classes ? (' ' + proto.classes) : ''); + proto.kindClasses = str; + proto.classes = props.classes; + } + delete props.classes; + } + + if (props.style) { + if (instance) { + str = (proto.style ? proto.style : '') + props.style; + proto.style = Control.normalizeCssStyleString(str); + } else { + str = proto.kindStyle ? proto.kindStyle : ''; + str += proto.style ? (';' + proto.style) : ''; + str += props.style; + + // moved it all to kindStyle so that it will be available whenever instanced + proto.kindStyle = Control.normalizeCssStyleString(str); + } + delete props.style; + } + + if (props.attributes) { + attrs = proto.attributes; + proto.attributes = attrs ? utils.mixin({}, [attrs, props.attributes]) : props.attributes; + delete props.attributes; + } +}; + +Control.prototype.defaultKind = Control; + +// Control has to be *completely* set up before creating the floating layer setting up the +// fullscreen object because fullscreen depends on floating layer which depends on Control. + +/** +* @static +* @public +*/ +Control.FloatingLayer = FloatingLayer(Control); + +/** +* @static +* @public +*/ +Control.floatingLayer = new Control.FloatingLayer({id: 'floatingLayer'}); + +/** +* @static +* @public +*/ +Control.Fullscreen = fullscreen(Control); +},{'../kind':'enyo/kind','../utils':'enyo/utils','../platform':'enyo/platform','../dispatcher':'enyo/dispatcher','../options':'enyo/options','../roots':'enyo/roots','../AccessibilitySupport':'enyo/AccessibilitySupport','../UiComponent':'enyo/UiComponent','../HTMLStringDelegate':'enyo/HTMLStringDelegate','../dom':'enyo/dom','./fullscreen':'enyo/Control/fullscreen','./floatingLayer':'enyo/Control/floatingLayer','../gesture':'enyo/gesture'}],'enyo/touch':[function (module,exports,global,require,request){ +require('enyo'); + +var + utils = require('./utils'), + gesture = require('./gesture'), + dispatcher = require('./dispatcher'), + platform = require('./platform'); + +var + Dom = require('./dom'), + Job = require('./job'); + +function dispatch (e) { + return dispatcher.dispatch(e); +} + +/** +* @private +*/ +Dom.requiresWindow(function() { + + /** + * Add touch-specific gesture feature + * + * @private + */ + + /** + * @private + */ + var oldevents = gesture.events; + + /** + * @private + */ + gesture.events.touchstart = function (e) { + // for duration of this touch, only handle touch events. Old event + // structure will be restored during touchend. + gesture.events = touchGesture; + gesture.events.touchstart(e); + }; + + /** + * @private + */ + var touchGesture = { + + /** + * @private + */ + _touchCount: 0, + + /** + * @private + */ + touchstart: function (e) { + this._touchCount += e.changedTouches.length; + this.excludedTarget = null; + var event = this.makeEvent(e); + //store the finger which generated the touchstart event + this.currentIdentifier = event.identifier; + gesture.down(event); + // generate a new event object since over is a different event + event = this.makeEvent(e); + this.overEvent = event; + gesture.over(event); + }, + + /** + * @private + */ + touchmove: function (e) { + Job.stop('resetGestureEvents'); + // NOTE: allow user to supply a node to exclude from event + // target finding via the drag event. + var de = gesture.drag.dragEvent; + this.excludedTarget = de && de.dragInfo && de.dragInfo.node; + var event = this.makeEvent(e); + // do not generate the move event if this touch came from a different + // finger than the starting touch + if (this.currentIdentifier !== event.identifier) { + return; + } + gesture.move(event); + // prevent default document scrolling if enyo.bodyIsFitting == true + // avoid window scrolling by preventing default on this event + // note: this event can be made unpreventable (native scrollers do this) + if (Dom.bodyIsFitting) { + e.preventDefault(); + } + // synthesize over and out (normally generated via mouseout) + if (this.overEvent && this.overEvent.target != event.target) { + this.overEvent.relatedTarget = event.target; + event.relatedTarget = this.overEvent.target; + gesture.out(this.overEvent); + gesture.over(event); + } + this.overEvent = event; + }, + + /** + * @private + */ + touchend: function (e) { + gesture.up(this.makeEvent(e)); + // NOTE: in touch land, there is no distinction between + // a pointer enter/leave and a drag over/out. + // While it may make sense to send a leave event when a touch + // ends, it does not make sense to send a dragout. + // We avoid this by processing out after up, but + // this ordering is ad hoc. + gesture.out(this.overEvent); + // reset the event handlers back to the mouse-friendly ones after + // a short timeout. We can't do this directly in this handler + // because it messes up Android to handle the mouseup event. + // FIXME: for 2.1 release, conditional on platform being + // desktop Chrome, since we're seeing issues in PhoneGap with this + // code. + this._touchCount -= e.changedTouches.length; + }, + + /** + * Use `mouseup()` after touches are done to reset {@glossary event} handling + * back to default; this works as long as no one did a `preventDefault()` on + * the touch events. + * + * @private + */ + mouseup: function () { + if (this._touchCount === 0) { + this.sawMousedown = false; + gesture.events = oldevents; + } + }, + + /** + * @private + */ + makeEvent: function (e) { + var event = utils.clone(e.changedTouches[0]); + event.srcEvent = e; + event.target = this.findTarget(event); + // normalize "mouse button" info + event.which = 1; + //enyo.log("target for " + inEvent.type + " at " + e.pageX + ", " + e.pageY + " is " + (e.target ? e.target.id : "none")); + return event; + }, + + /** + * @private + */ + calcNodeOffset: function (node) { + if (node.getBoundingClientRect) { + var o = node.getBoundingClientRect(); + return { + left: o.left, + top: o.top, + width: o.width, + height: o.height + }; + } + }, + + /** + * @private + */ + findTarget: function (e) { + return document.elementFromPoint(e.clientX, e.clientY); + }, + + /** + * NOTE: Will find only 1 element under the touch and will fail if an element is + * positioned outside the bounding box of its parent. + * + * @private + */ + findTargetTraverse: function (node, x, y) { + var n = node || document.body; + var o = this.calcNodeOffset(n); + if (o && n != this.excludedTarget) { + var adjX = x - o.left; + var adjY = y - o.top; + //enyo.log("test: " + n.id + " (left: " + o.left + ", top: " + o.top + ", width: " + o.width + ", height: " + o.height + ")"); + if (adjX>0 && adjY>0 && adjX<=o.width && adjY<=o.height) { + //enyo.log("IN: " + n.id + " -> [" + adjX + "," + adjY + " in " + o.width + "x" + o.height + "] (children: " + n.childNodes.length + ")"); + var target; + for (var n$=n.childNodes, i=n$.length-1, c; (c=n$[i]); i--) { + target = this.findTargetTraverse(c, x, y); + if (target) { + return target; + } + } + return n; + } + } + }, + + /** + * @private + */ + connect: function () { + utils.forEach(['touchstart', 'touchmove', 'touchend', 'gesturestart', 'gesturechange', 'gestureend'], function(e) { + if(platform.ie < 9){ + document['on' + e] = dispatch; + } else { + // on iOS7 document.ongesturechange is never called + document.addEventListener(e, dispatch, false); + } + }); + + if (platform.androidChrome <= 18 || platform.silk === 2) { + // HACK: on Chrome for Android v18 on devices with higher density displays, + // document.elementFromPoint expects screen coordinates, not document ones + // bug also appears on Kindle Fire HD + this.findTarget = function(e) { + return document.elementFromPoint(e.screenX, e.screenY); + }; + } else if (!document.elementFromPoint) { + this.findTarget = function(e) { + return this.findTargetTraverse(null, e.clientX, e.clientY); + }; + } + } + }; + + touchGesture.connect(); +}); + +},{'./utils':'enyo/utils','./gesture':'enyo/gesture','./dispatcher':'enyo/dispatcher','./platform':'enyo/platform','./dom':'enyo/dom','./job':'enyo/job'}],'enyo/Application':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Application~Application} kind. +* @module enyo/Application +*/ + +var + kind = require('./kind'), + utils = require('./utils'), + master = require('./master'); + +var + ViewController = require('./ViewController'), + Controller = require('./Controller'); + +var + applications = {}; + +/** +* {@link module:enyo/Application~Application} is a type of {@link module:enyo/ViewController~ViewController} that +* encapsulates a collection of [controllers]{@link module:enyo/Controller~Controller} and a +* hierarchy of [controls]{@link module:enyo/Control~Control}. There may be multiple instances +* of an [application]{@link module:enyo/Application~Application} at a given time, with unique +* names and target [DOM nodes]{@glossary Node}. Within a given application, a +* reference to the application is available on all [components]{@link module:enyo/Component~Component} +* via the [app]{@link module:enyo/ApplicationSupport#app} property. +* +* @class Application +* @extends module:enyo/ViewController~ViewController +* @public +*/ +exports = module.exports = kind( + /** @lends module:enyo/Application~Application.prototype */ { + + name: 'enyo.Application', + + /** + * @private + */ + kind: ViewController, + + /** + * If set to `true` (the default), the [application's]{@link module:enyo/Application~Application} + * [start()]{@link module:enyo/Application~Application#start} method will automatically be called + * once its [create()]{@link module:enyo/Application~Application#create} method has completed + * execution. Set this to `false` if additional setup (or an asynchronous + * {@glossary event}) is required before starting. + * + * @type {Boolean} + * @default true + * @public + */ + autoStart: true, + + /** + * If set to `true` (the default), the [application]{@link module:enyo/Application~Application} will immediately + * [render]{@link module:enyo/Application~Application#render} its [view]{@link module:enyo/ViewController~ViewController#view} when + * the [start()]{@link module:enyo/Application~Application#start} method has completed execution. Set this to + * `false` to delay rendering if additional setup (or an asynchronous {@glossary event}) is + * required before rendering. + * + * @type {Boolean} + * @default true + * @public + */ + renderOnStart: true, + + /** + * The `defaultKind` for {@link module:enyo/Application~Application} is {@link module:enyo/Controller~Controller}. + * + * @type {Object} + * @default module:enyo/Controller~Controller + * @public + */ + defaultKind: Controller, + + /** + * A [bindable]{@link module:enyo/BindingSupport~BindingSupport}, read-only property that indicates whether the + * [view]{@link module:enyo/ViewController~ViewController#view} has been rendered. + * + * @readonly + * @type {Boolean} + * @default false + * @public + */ + viewReady: false, + + /** + * An abstract method to allow for additional setup to take place after the application has + * completed its initialization and is ready to be rendered. Overload this method to suit + * your app's specific requirements. + * + * @returns {this} The callee for chaining. + * @public + */ + start: function () { + if (this.renderOnStart) this.render(); + return this; + }, + + /** + * @method + * @private + */ + render: kind.inherit(function (sup) { + return function () { + // call the super method render() from ViewController + sup.apply(this, arguments); + if (this.view && this.view.generated) this.set('viewReady', true); + }; + }), + + /** + * @method + * @private + */ + constructor: kind.inherit(function (sup) { + return function (props) { + if (props && typeof props.name == 'string') { + utils.setPath(props.name, this); + // since applications are stored by their id's we set it + // to the name if it exists + this.id = (props && props.name); + } + sup.apply(this, arguments); + // we alias the `controllers` property to the `$` property to preserve + // backwards compatibility for the deprecated API for now + this.controllers = this.$; + applications[this.id || this.makeId()] = this; + }; + }), + + /** + * Allows normal creation flow and then executes the application's + * [start()]{@link module:enyo/Application~Application#start} method if the + * [autoStart]{@link module:enyo/Application~Application#autoStart} property is `true`. + * + * @method + * @private + */ + create: kind.inherit(function (sup) { + return function () { + // ensure that we create() all of the components before continuing + sup.apply(this, arguments); + if (this.autoStart) this.start(); + + }; + }), + + /** + * Ensures that all [components]{@link module:enyo/Component~Component} created by this application have + * their [app]{@link module:enyo/ApplicationSupport#app} property set correctly. + * + * @method + * @private + */ + adjustComponentProps: kind.inherit(function (sup) { + return function (props) { + props.app = this; + sup.apply(this, arguments); + }; + }), + + /** + * Cleans up the registration for the application. + * + * @method + * @private + */ + destroy: kind.inherit(function (sup) { + return function () { + delete applications[this.id]; + sup.apply(this, arguments); + }; + }), + + /** + * Ensures that [events]{@glossary event} bubbling from the views will reach + * {@link module:enyo/master} as expected. + * + * @private + */ + owner: master +}); + +/** +* Any {@link module:enyo/Application~Application} instances will be available by name from this +* [object]{@glossary Object}. If no name is provided for an +* [application]{@link module:enyo/Application~Application}, a name will be generated for it. +* +* @public +* @type {Object} +* @default {} +*/ +exports.applications = applications; + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./master':'enyo/master','./ViewController':'enyo/ViewController','./Controller':'enyo/Controller'}],'enyo/Image':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Image~Image} kind. +* @module enyo/Image +*/ + +var + kind = require('../kind'), + ri = require('../resolution'), + dispatcher = require('../dispatcher'), + path = require('../pathResolver'); +var + Control = require('../Control'); + +/** +* Fires when the [image]{@link module:enyo/Image~Image} has loaded. +* +* @event module:enyo/Image~Image#onload +* @type {Object} +* @property {Object} sender - The [component]{@link module:enyo/Component~Component} that most recently +* propagated the {@glossary event}. +* @property {Object} event - An [object]{@glossary Object} containing event information. +* @public +*/ + +/** +* Fires when there has been an error while loading the [image]{@link module:enyo/Image~Image}. +* +* @event module:enyo/Image~Image#onerror +* @type {Object} +* @property {Object} sender - The [component]{@link module:enyo/Component~Component} that most recently +* propagated the {@glossary event}. +* @property {Object} event - An [object]{@glossary Object} containing event information. +* @public +*/ + +/** +* {@link module:enyo/Image~Image} implements an HTML [<img>]{@glossary img} element and, optionally, +* [bubbles]{@link module:enyo/Component~Component#bubble} the [onload]{@link module:enyo/Image~Image#onload} and +* [onerror]{@link module:enyo/Image~Image#onerror} [events]{@glossary event}. Image dragging is suppressed by +* default, so as not to interfere with touch interfaces. +* +* When [sizing]{@link module:enyo/Image~Image#sizing} is used, the control will not have a natural size and must be +* manually sized using CSS `height` and `width`. Also, when [placeholder]{@link module:enyo/Image~Image#placeholder} is used +* without `sizing`, you may wish to specify the size, as the image will not have a +* natural size until the image loads, causing the placeholder to not be visible. +* +* {@link module:enyo/Image~Image} also has support for multi-resolution images. If you are developing assets +* for specific screen sizes, HD (720p), FHD (1080p), UHD (4k), for example, you may provide +* specific image assets in a hash/object format to the `src` property, instead of the usual +* string. The image sources will be used automatically when the screen resolution is less than +* or equal to those screen types. For more informaton on our resolution support, and how to +* enable this feature, see our [resolution independence documentation]{@link module:enyo/resolution}. +* +* ``` +* // Take advantage of the multi-rez mode +* var +* kind = require('enyo/kind'), +* Image = require('enyo/Image'); +* +* {kind: Image, src: { +* 'hd': 'http://lorempixel.com/64/64/city/1/', +* 'fhd': 'http://lorempixel.com/128/128/city/1/', +* 'uhd': 'http://lorempixel.com/256/256/city/1/' +* }, alt: 'Multi-rez'}, +* +* // Standard string `src` +* {kind: Image, src: 'http://lorempixel.com/128/128/city/1/', alt: 'Large'} +* ``` +* +* @class Image +* @extends module:enyo/Control~Control +* @ui +* @public +*/ +module.exports = kind( + /** @lends module:enyo/Image~Image.prototype */ { + + /** + * @private + */ + name: 'enyo.Image', + + /** + * @private + */ + kind: Control, + + /** + * When `true`, no [onload]{@link module:enyo/Image~Image#onload} or + * [onerror]{@link module:enyo/Image~Image#onerror} {@glossary event} handlers will be + * created. + * + * @type {Boolean} + * @default false + * @public + */ + noEvents: false, + + /** + * @private + */ + published: + /** @lends module:enyo/Image~Image.prototype */ { + + /** + * Maps to the `src` attribute of an [<img> tag]{@glossary img}. This also supports + * a multi-resolution hash object. See + * [the above description of enyo.Image]{@link module:enyo/Image~Image} for more details and examples + * or our [resolution independence docs]{@link module:enyo/resolution}. + * + * @type {String} + * @default '' + * @public + */ + src: '', + + /** + * Maps to the `alt` attribute of an [<img> tag]{@glossary img}. + * + * @type {String} + * @default '' + * @public + */ + alt: '', + + /** + * By default, the [image]{@link module:enyo/Image~Image} is rendered using an `` tag. + * When this property is set to `'cover'` or `'constrain'`, the image will be + * rendered using a `
`, utilizing `background-image` and `background-size`. + * + * Set this property to `'contain'` to letterbox the image in the available + * space, or `'cover'` to cover the available space with the image (cropping the + * larger dimension). Note that when `sizing` is set, the control must be + * explicitly sized. + * + * @type {String} + * @default '' + * @public + */ + sizing: '', + + /** + * When [sizing]{@link module:enyo/Image~Image#sizing} is used, this property sets the positioning of + * the [image]{@link module:enyo/Image~Image} within the bounds, corresponding to the + * [`background-position`]{@glossary backgroundPosition} CSS property. + * + * @type {String} + * @default 'center' + * @public + */ + position: 'center', + + /** + * Provides a default image displayed while the URL specified by `src` is loaded or when that + * image fails to load. + * + * @type {String} + * @default '' + * @public + */ + placeholder: '' + }, + + /** + * @private + */ + tag: 'img', + + /** + * @private + */ + classes: 'enyo-image', + + /** + * `src` copied here to avoid overwriting the user-provided value when loading values + * + * @private + */ + _src: null, + + /** + * @type {Object} + * @property {Boolean} draggable - This attribute will take one of the following + * [String]{@glossary String} values: 'true', 'false' (the default), or 'auto'. + * Setting Boolean `false` will remove the attribute. + * @public + */ + attributes: { + draggable: 'false' + }, + + /** + * @private + */ + handlers: { + onload: 'handleLoad', + onerror: 'handleError' + }, + + /** + * @private + */ + observers: [ + {method: 'updateSource', path: ['_src', 'placeholder']} + ], + + /** + * @method + * @private + */ + create: kind.inherit(function (sup) { + return function () { + if (this.noEvents) { + delete this.attributes.onload; + delete this.attributes.onerror; + } + sup.apply(this, arguments); + this.altChanged(); + this.sizingChanged(); + this.srcChanged(); + this.positionChanged(); + }; + }), + + /** + * Cache the value of user-provided `src` value in `_src` + * + * @private + */ + srcChanged: function () { + this.set('_src', this.src); + }, + + /** + * @private + */ + altChanged: function () { + this.setAttribute('alt', this.alt); + }, + + /** + * @private + */ + sizingChanged: function (was) { + this.tag = this.sizing ? 'div' : 'img'; + this.addRemoveClass('sized', !!this.sizing); + if (was) { + this.removeClass(was); + } + if (this.sizing) { + this.addClass(this.sizing); + } + this.updateSource(); + if (this.generated) { + this.render(); + } + }, + + /** + * @private + */ + positionChanged: function () { + if (this.sizing) { + this.applyStyle('background-position', this.position); + } + }, + + /** + * When the image is loaded successfully, we want to clear out the background image so it doesn't + * show through the transparency of the image. This only works when not using `sizing` because we + * do not get load/error events for failed background-image's. + * + * @private + */ + handleLoad: function () { + if (!this.sizing && this.placeholder) { + this.applyStyle('background-image', null); + } + }, + + /** + * @private + */ + handleError: function () { + if (this.placeholder) { + this.set('_src', null); + } + }, + + /** + * Updates the Image's src or background-image based on the values of _src and placeholder + * + * @private + */ + updateSource: function (was, is, prop) { + var src = ri.selectSrc(this._src), + srcUrl = src ? 'url(\'' + path.rewrite(src) + '\')' : null, + plUrl = this.placeholder ? 'url(\'' + path.rewrite(this.placeholder) + '\')' : null, + url; + + if (this.sizing) { + // use either both urls, src, placeholder, or 'none', in that order + url = srcUrl && plUrl && (srcUrl + ',' + plUrl) || srcUrl || plUrl || 'none'; + this.applyStyle('background-image', url); + } + // if we've haven't failed to load src (this.src && this._src == this.src), we don't want to + // add the bg image that may have already been removed by handleLoad + else if (!(prop == 'placeholder' && this.src && this._src == this.src)) { + this.applyStyle('background-image', plUrl); + this.setAttribute('src', src); + } + }, + + /** + * @fires module:enyo/Image~Image#onload + * @fires module:enyo/Image~Image#onerror + * @private + */ + rendered: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + dispatcher.makeBubble(this, 'load', 'error'); + }; + }), + + /** + * @lends module:enyo/Image~Image + * @private + */ + statics: { + /** + * A globally accessible data URL that describes a simple + * placeholder image that may be used in samples and applications + * until final graphics are provided. As an SVG image, it will + * expand to fill the desired width and height set in the style. + * + * @type {String} + * @public + */ + placeholder: + 'data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC' + + '9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj48cmVjdCB3aWR0aD0iMTAw' + + 'JSIgaGVpZ2h0PSIxMDAlIiBzdHlsZT0ic3Ryb2tlOiAjNDQ0OyBzdHJva2Utd2lkdGg6IDE7IGZpbGw6ICNhYW' + + 'E7IiAvPjxsaW5lIHgxPSIwIiB5MT0iMCIgeDI9IjEwMCUiIHkyPSIxMDAlIiBzdHlsZT0ic3Ryb2tlOiAjNDQ0' + + 'OyBzdHJva2Utd2lkdGg6IDE7IiAvPjxsaW5lIHgxPSIxMDAlIiB5MT0iMCIgeDI9IjAiIHkyPSIxMDAlIiBzdH' + + 'lsZT0ic3Ryb2tlOiAjNDQ0OyBzdHJva2Utd2lkdGg6IDE7IiAvPjwvc3ZnPg==' + }, + + // Accessibility + + /** + * @default img + * @type {String} + * @see enyo/AccessibilitySupport~AccessibilitySupport#accessibilityRole + * @public + */ + accessibilityRole: 'img' +}); + +},{'../kind':'enyo/kind','../resolution':'enyo/resolution','../dispatcher':'enyo/dispatcher','../pathResolver':'enyo/pathResolver','../Control':'enyo/Control'}],'enyo/GroupItem':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/GroupItem~GroupItem} kind. +* @module enyo/GroupItem +*/ + +var + kind = require('./kind'); +var + Control = require('./Control'); + +/** +* Fires when the [active state]{@link module:enyo/GroupItem~GroupItem#active} has changed. +* +* @event module:enyo/GroupItem~GroupItem#onActivate +* @type {Object} +* @property {Object} sender - The [component]{@link module:enyo/Component~Component} that most recently +* propagated the {@glossary event}. +* @property {Object} event - An [object]{@glossary Object} containing event information. +* @public +*/ + +/** +* {@link module:enyo/GroupItem~GroupItem} is the base [kind]{@glossary kind} for the +* [Grouping]{@link module:enyo/Group~Group} API. It manages the +* [active state]{@link module:enyo/GroupItem~GroupItem#active} of the [component]{@link module:enyo/Component~Component} +* (or the [inheriting]{@glossary subkind} component). A subkind may call `setActive()` +* to set the [active]{@link module:enyo/GroupItem~GroupItem#active} property to the desired state; this +* will additionally [bubble]{@link module:enyo/Component~Component#bubble} an +* [onActivate]{@link module:enyo/GroupItem~GroupItem#onActivate} {@glossary event}, which may +* be handled as needed by the containing components. This is useful for creating +* groups of items whose state should be managed collectively. +* +* For an example of how this works, see the {@link module:enyo/Group~Group} kind, which enables the +* creation of radio groups from arbitrary components that support the Grouping API. +* +* @class GroupItem +* @extends module:enyo/Control~Control +* @ui +* @public +*/ +module.exports = kind( + /** @lends module:enyo/Groupitem~Groupitem.prototype */ { + + /** + * @private + */ + name: 'enyo.GroupItem', + + /** + * @private + */ + kind: Control, + + /** + * @private + */ + published: + /** @lends module:enyo/Groupitem~Groupitem.prototype */ { + + /** + * Will be `true` if the item is currently selected. + * + * @type {Boolean} + * @default false + * @public + */ + active: false + }, + + /** + * @method + * @private + */ + rendered: kind.inherit(function (sup) { + return function() { + sup.apply(this, arguments); + this.activeChanged(); + }; + }), + + /** + * @fires module:enyo/GroupItem~GroupItem#onActivate + * @private + */ + activeChanged: function () { + this.bubble('onActivate'); + } +}); + +},{'./kind':'enyo/kind','./Control':'enyo/Control'}],'enyo/DataRepeater':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/DataRepeater~DataRepeater} kind. +* @module enyo/DataRepeater +*/ + +var + kind = require('./kind'), + utils = require('./utils'); +var + Control = require('./Control'), + RepeaterChildSupport = require('./RepeaterChildSupport'); + +function at (idx) { + return this[idx]; +} + +function arrayFilter (record) { + return record[this.selectionProperty]; +} + +function modelFilter (record) { + return record.get(this.selectionProperty); +} + +/** +* {@link module:enyo/DataRepeater~DataRepeater} iterates over the items in an {@link module:enyo/Collection~Collection} to +* repeatedly render and synchronize records (instances of {@link module:enyo/Model~Model}) to its +* own children. For any record in the collection, a new child will be rendered in +* the repeater. If the record is destroyed, the child will be destroyed. These +* [controls]{@link module:enyo/Control~Control} will automatically update when properties on the +* underlying records are modified if they have been bound using +* [bindings]{@link module:enyo/Binding~Binding}. +* +* @class DataRepeater +* @extends module:enyo/Control~Control +* @ui +* @public +*/ +var DataRepeater = module.exports = kind( + /** @lends module:enyo/DataRepeater~DataRepeater.prototype */ { + + /** + * @private + */ + name: 'enyo.DataRepeater', + + /** + * @private + */ + kind: Control, + + /** + * Set this to `true` to enable selection support. Note that selection stores a + * reference to the [model]{@link module:enyo/Model~Model} that is selected, via the + * [selected]{@link module:enyo/DataRepeater~DataRepeater#selected} method. + * + * @type {Boolean} + * @default true + * @public + */ + selection: true, + + /** + * Specifies the type of selection (if enabled), that we want to enable. The possible values + * are 'single', 'multi', and 'group'. The default is 'single' selection mode, which enables + * selection and deselection of a single item at a time. The 'multi' selection mode allows + * multiple children to be selected simultaneously, while the 'group' selection mode allows + * group-selection behavior such that only one child may be selected at a time and, once a + * child is selected, it cannot be deselected via user input. The child may still be + * deselected via the selection API methods. + * + * @type {String} + * @default 'single' + * @public + */ + selectionType: 'single', + + /** + * Set this to `true` to allow multiple children to be selected simultaneously. + * + * @deprecated since version 2.6 + * @type {Boolean} + * @default false + * @public + */ + multipleSelection: false, + + /** + * Set this to `true` to allow group-selection behavior such that only one child + * may be selected at a time and, once a child is selected, it cannot be + * deselected via user input. The child may still be deselected via the selection + * API methods. Note that setting this property to `true` will set the + * [multipleSelection]{@link module:enyo/DataRepeater~DataRepeater#multipleSelection} property to + * `false`. + * + * @deprecated since version 2.6 + * @type {Boolean} + * @default false + * @public + */ + groupSelection: false, + + /** + * This class will be applied to the [repeater]{@link module:enyo/DataRepeater~DataRepeater} when + * [selection]{@link module:enyo/DataRepeater~DataRepeater#selection} is enabled. It will also be + * applied if [multipleSelection]{@link module:enyo/DataRepeater~DataRepeater#multipleSelection} + * is `true`. + * + * @type {String} + * @default 'selection-enabled' + * @public + */ + selectionClass: 'selection-enabled', + + /** + * This class will be applied to the [repeater]{@link module:enyo/DataRepeater~DataRepeater} when + * [selectionType]{@link module:enyo/DataRepeater~DataRepeater#selectionType} is `multi`. + * When that is the case, the [selectionClass]{@link module:enyo/DataRepeater~DataRepeater#selectionClass} + * will also be applied. + * + * @type {String} + * @default 'multiple-selection-enabled' + * @public + */ + multipleSelectionClass: 'multiple-selection-enabled', + + /** + * In cases where selection should be detected from the state of the + * [model]{@link module:enyo/Model~Model}, this property should be set to the property on + * the model that the [repeater]{@link module:enyo/DataRepeater~DataRepeater} should observe for + * changes. If the model changes, the repeater will reflect the change without + * having to interact directly with the model. Note that this property must be + * part of the model's schema, or else its changes will not be detected + * properly. + * + * @type {String} + * @default '' + * @public + */ + selectionProperty: '', + + /** + * Set this to a space-delimited string of [events]{@glossary event} or an + * [array]{@glossary Array} that can trigger the selection of a particular + * child. To prevent selection entirely, set + * [selection]{@link module:enyo/DataRepeater~DataRepeater#selection} to `false`. + * + * @type {String} + * @default 'ontap' + * @public + */ + selectionEvents: 'ontap', + + /** + * Use this [hash]{@glossary Object} to define default [binding]{@link module:enyo/Binding~Binding} + * properties for **all** children (even children of children) of this + * [repeater]{@link module:enyo/DataRepeater~DataRepeater}. This can eliminate the need to write the + * same paths over and over. You may also use any binding macros. Any property + * defined here will be superseded by the same property if defined for an individual + * binding. + * + * @type {Object} + * @default null + * @public + */ + childBindingDefaults: null, + + /** + * @private + */ + initComponents: function () { + this.initContainer(); + var components = this.kindComponents || this.components || [], + owner = this.getInstanceOwner(), + props = this.defaultProps? utils.clone(this.defaultProps, true): (this.defaultProps = {}); + // ensure that children know who their binding owner is + props.bindingTransformOwner = this; + props.bindingDefaults = this.childBindingDefaults; + if (components) { + // if there are multiple components in the components block they will become nested + // children of the default kind set for the repeater + if (components.length > 1) { + props.components = components; + } + // if there is only one child, the properties will be the default kind of the repeater + else { + utils.mixin(props, components[0]); + } + props.repeater = this; + props.owner = owner; + props.mixins = props.mixins? props.mixins.concat(this.childMixins): this.childMixins; + } + + this.defaultProps = props; + }, + + /** + * @method + * @private + */ + constructor: kind.inherit(function (sup) { + return function () { + this._selection = []; + // we need to initialize our selectionEvents array + var se = this.selectionEvents; + this.selectionEvents = (typeof se == 'string'? se.split(' '): se); + // we need to pre-bind these methods so they can easily be added + // and removed as listeners later + var h = this._handlers = utils.clone(this._handlers); + for (var e in h) { + h[e] = this.bindSafely(h[e]); + } + sup.apply(this, arguments); + }; + }), + + /** + * @method + * @private + */ + create: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + this.collectionChanged(); + // Converting deprecated selection properties to our current selection API + this.selectionType = this.multipleSelection ? (this.groupSelection ? 'group' : 'multi') : this.selectionType; + this.selectionTypeChanged(); + }; + }), + + /** + * @private + */ + groupSelectionChanged: function () { + this.set('selectionType', this.groupSelection ? 'group' : 'single'); + }, + + /** + * @private + */ + multipleSelectionChanged: function () { + this.set('selectionType', this.multipleSelection ? 'multi' : 'single'); + }, + + /** + * @private + */ + selectionTypeChanged: function (was) { + // Synchronizing our deprecated properties + this.groupSelection = this.selectionType == 'group'; + this.multipleSelection = this.selectionType == 'multi'; + + if (was == 'multi') { + if (this._selection.length > 1) { + this.deselectAll(); + } + } + this.selectionChanged(); + }, + + /** + * @private + */ + selectionChanged: function () { + this.addRemoveClass(this.selectionClass, this.selection); + this.addRemoveClass(this.multipleSelectionClass, this.selectionType == 'multi' && this.selection); + }, + + /** + * Destroys any existing children in the [repeater]{@link module:enyo/DataRepeater~DataRepeater} and creates all + * new children based on the current [data]{@link module:enyo/Repeater~Repeater#data}. + * + * @public + */ + reset: function () { + // use the facaded dataset because this could be any + // collection of records + var dd = this.get('data'); + // destroy the client controls we might already have + this.destroyClientControls(); + // and now we create new ones for each new record we have + for (var i=0, r; (r=dd.at(i)); ++i) { + this.add(r, i); + } + this.hasReset = true; + }, + /** + * Refreshes each [control]{@link module:enyo/Control~Control} in the dataset. + * + * @param {Boolean} immediate - If `true`, refresh will occur immediately; otherwise, + * it will be queued up as a job. + * @public + */ + refresh: function (immediate) { + if (!this.hasReset) { return this.reset(); } + var refresh = this.bindSafely(function () { + var dd = this.get('data'), + cc = this.getClientControls(); + for (var i=0, c, d; (d=dd.at(i)); ++i) { + c = cc[i]; + if (c) { + c.set('model', d); + } else { + this.add(d, i); + } + } + this.prune(); + }); + + // refresh is used as the event handler for + // collection resets so checking for truthy isn't + // enough. it must be true. + if(immediate === true) { + refresh(); + } else { + this.startJob('refreshing', refresh, 16); + } + }, + + /** + * @method + * @private + */ + rendered: kind.inherit(function (sup) { + return function () { + var dd; + + sup.apply(this, arguments); + + dd = this.get('data'); + + if (dd && dd.length) { + this.reset(); + } + this.hasRendered = true; + }; + }), + + /** + * Adds a [record]{@link module:enyo/Model~Model} at a particular index. + * + * @param {module:enyo/Model~Model} rec - The [record]{@link module:enyo/Model~Model} to add. + * @param {Number} idx - The index at which the record should be added. + * @public + */ + add: function (rec, idx) { + var c = this.createComponent({model: rec, index: idx}); + if (this.generated && !this.batching) { + c.render(); + } + }, + + /** + * Removes the [record]{@link module:enyo/Model~Model} at a particular index. + * + * @param {Number} idx - The index of the [record]{@link module:enyo/Model~Model} to be removed. + * @public + */ + remove: function (idx) { + var controls = this.getClientControls() + , control; + + control = controls[idx]; + + if (control) control.destroy(); + }, + + /** + * Removes any [controls]{@link module:enyo/Control~Control} that are outside the boundaries of the + * [data]{@link module:enyo/DataRepeater~DataRepeater#data} [collection]{@link module:enyo/Collection~Collection} for the + * [repeater]{@link module:enyo/DataRepeater~DataRepeater}. + * + * @public + */ + prune: function () { + var g = this.getClientControls(), + dd = this.get('data'), + len = (dd ? dd.length: 0), + x; + if (g.length > len) { + x = g.slice(len); + for (var i=0, c; (c=x[i]); ++i) { + c.destroy(); + } + } + }, + + /** + * Syncs the bindings of all repeater children. Designed for use cases where + * the repeater's collection is a native JavaScript array with native JavaScript + * objects as models (as opposed to Enyo Collections and Models). In this case, + * you can't depend on bindings to update automatically when the underlying data + * changes, so if you know that there has been a change to the data, you can force + * an update by sync'ing all of the bindings. + * + * This API is to be considered experimental and is subject to change. + * + * @public + */ + syncChildBindings: function (opts) { + this.getClientControls().forEach(function (c) { + c.syncBindings(opts); + }); + }, + + /** + * @private + */ + initContainer: function () { + var ops = this.get('containerOptions'), + nom = ops.name || (ops.name = this.containerName); + this.createChrome([ops]); + this.discoverControlParent(); + if (nom != this.containerName) { + this.$[this.containerName] = this.$[nom]; + } + }, + + /** + * @private + */ + handlers: { + onSelected: 'childSelected', + onDeselected: 'childDeselected' + }, + + /** + * @private + */ + _handlers: { + add: 'modelsAdded', + remove: 'modelsRemoved', + reset: 'refresh', + sort: 'refresh', + filter: 'refresh' + }, + + /** + * @private + */ + componentsChanged: function (p) { + this.initComponents(); + this.reset(); + }, + + /** + * @private + */ + collectionChanged: function (p) { + var c = this.collection; + if (typeof c == 'string') { + c = this.collection = utils.getPath.call(global, c); + } + if (c) { + this.initCollection(c, p); + } + }, + + /** + * @private + */ + initCollection: function (c, p) { + var e, filter, isArray = c && c instanceof Array; + + if (c && c.addListener) { + for (e in this._handlers) { + c.addListener(e, this._handlers[e]); + } + } + // Decorate native JS array with at() so that we can + // access members of our dataset consistently, regardless + // of whether our data is in an array or a Collection + if (c && !c.at) { + Object.defineProperty(c, 'at', {value: at, enumerable: false}); + } + if (p && p.removeListener) { + for (e in this._handlers) { + p.removeListener(e, this._handlers[e]); + } + } + if (c && this.selectionProperty) { + filter = isArray ? arrayFilter : modelFilter; + this._selection = c.filter(filter, this); + } else { + this._selection = []; + } + }, + + /** + * @private + */ + modelsAdded: function (sender, e, props) { + if (sender === this.collection) this.refresh(); + }, + + /** + * @private + */ + modelsRemoved: function (sender, e, props) { + if (sender === this.collection) { + this.deselectRemovedModels(props.models); + this.refresh(); + } + }, + + /** + * Deselect removed models from _selected array. + * After calling it, we can ensure that the removed models aren't currently selected. + * @param {array} models - The array of models that are removed from collection. + * @private + */ + deselectRemovedModels: function(models) { + var selected = this._selection, + orig, + model, + idx, + len = selected && selected.length, + i = models.length - 1; + + // We have selected models + if (len) { + // unfortunately we need to make a copy to preserve what the original was + // so we can pass it with the notification if any of these are deselected + orig = selected.slice(); + + // we have _selected array to track currently selected models + // if some removed models are in _selected, we should remove them from _selected + // clearly we won't need to continue checking if selected does not have any models + for (; (model = models[i]) && selected.length; --i) { + idx = selected.indexOf(model); + if (idx > -1) selected.splice(idx, 1); + } + + // Some selected models are discovered, so we need to notify + if (len != selected.length) { + if (this.selection) { + if (this.selectionType == 'multi') this.notify('selected', orig, selected); + else this.notify('selected', orig[0], selected[0] || null); + } + } + } + }, + + /** + * @private + */ + batchingChanged: function (prev, val) { + if (this.generated && false === val) { + this.$[this.containerName].render(); + this.refresh(true); + } + }, + + /** + * Calls [childForIndex()]{@link module:enyo/DataRepeater~DataRepeater#getChildForIndex}. Leaving for posterity. + * + * @param {Number} idx - The index of the child to retrieve. + * @returns {module:enyo/Control~Control|undefined} The [control]{@link module:enyo/Control~Control} at the specified + * index, or `undefined` if it could not be found or the index is out of bounds. + * @public + */ + getChildForIndex: function (idx) { + return this.childForIndex(idx); + }, + + /** + * Attempts to return the [control]{@link module:enyo/Control~Control} representation at a particular index. + * + * @param {Number} idx - The index of the child to retrieve. + * @returns {module:enyo/Control~Control|undefined} The [control]{@link module:enyo/Control~Control} at the specified + * index, or `undefined` if it could not be found or the index is out of bounds. + * @public + */ + childForIndex: function (idx) { + return this.$.container.children[idx]; + }, + + /** + * Retrieves the data associated with the [repeater]{@link module:enyo/DataRepeater~DataRepeater}. + * + * @returns {module:enyo/Collection~Collection} The {@link module:enyo/Collection~Collection} that comprises the data. + * @public + */ + data: function () { + return this.collection; + }, + + /** + * Consolidates selection logic and allows for deselection of a [model]{@link module:enyo/Model~Model} + * that has already been removed from the [collection]{@link module:enyo/Collection~Collection}. + * + * @private + */ + _select: function (idx, model, select) { + if (!this.selection) { + return; + } + + var c = this.getChildForIndex(idx), + s = this._selection, + i = utils.indexOf(model, s), + dd = this.get('data'), + p = this.selectionProperty; + + if (select) { + if(i == -1) { + if(this.selectionType != 'multi') { + while (s.length) { + i = dd.indexOf(s.pop()); + this.deselect(i); + } + } + + s.push(model); + } + } else { + if(i >= 0) { + s.splice(i, 1); + } + } + + if (c) { + c.set('selected', select); + } + if (p && model) { + if (typeof model.set === 'function') { + model.set(p, select); + } + else { + model[p] = select; + if(c) c.syncBindings({force: true, all: true}); + } + } + this.notifyObservers('selected'); + }, + + /** + * Selects the item at the given index. + * + * @param {Number} idx - The index of the item to select. + * @public + */ + select: function (idx) { + var dd = this.get('data'); + + this._select(idx, dd.at(idx), true); + }, + + /** + * Deselects the item at the given index. + * + * @param {Number} idx - The index of the item to deselect. + * @public + */ + deselect: function (idx) { + var dd = this.get('data'); + + this._select(idx, dd.at(idx), false); + }, + + /** + * Determines whether a [model]{@link module:enyo/Model~Model} is currently selected. + * + * @param {module:enyo/Model~Model} model - The [model]{@link module:enyo/Model~Model} whose selection status + * is to be determined. + * @returns {Boolean} `true` if the given model is selected; otherwise, `false`. + * @public + */ + isSelected: function (model) { + return !!~utils.indexOf(model, this._selection); + }, + + /** + * Selects all items (if [selectionType]{@link module:enyo/DataRepeater~DataRepeater#selectionType} is `multi`). + * + * @public + */ + selectAll: function () { + var dd = this.get('data'); + + if (this.selectionType == 'multi') { + this.stopNotifications(); + var s = this._selection + , len = dd ? dd.length: 0; + s.length = 0; + for (var i=0; i= 0) { + c = o[ci]; + o.splice(ci, 1); + om.splice(ci, 1); + o2.push(c); + this.assignChild(m, idx, c); + } + else { + needed.push(i); + } + om2.push(m); + } + else { + break; + } + } + nNeeded = needed.length; + for (j = 0; j < nNeeded; ++j) { + i = needed[j]; + idx = f + i; + m = om2[i]; + c = om.pop() && o.pop() || b.pop(); + if (c) { + this.assignChild(m, idx, c); + } + else { + c = this.createComponent({model: m}); + this.assignChild(m, idx, c); + // TODO: Rethink child lifecycle hooks (currently deploy / update / retire) + if (typeof this.deployChild === 'function') this.deployChild(c); + c.render(); + } + o2.splice(i, 0, c); + } + needed.length = 0; + while (o.length) { + c = om.pop() && o.pop(); + c.set('model', null); + c.hide(); + b.push(c); + } + this.orderedChildren = o2; + this.orderedChildren_alt = o; + this.orderedModels = om2; + this.orderedModels_alt = om; + ln = o2.length; + if (ln) { + o2[0].addClass('enyo-vdr-first'); + o2[ln - 1].addClass('enyo-vdr-last'); + if (typeof this.positionChildren === 'function') this.positionChildren(); + } + }, + + fwd: function() { + this.set('first', this.first + 1); + }, + + bak: function() { + this.set('first', this.first - 1); + }, + + set: kind.inherit(function (sup) { + return function (prop, val) { + if (prop === 'first') { + this.setExtent(val); + return this; + } + if (prop === 'numItems') { + this.setExtent(null, val); + return this; + } + return sup.apply(this, arguments); + }; + }), + + stabilizeExtent: function() { + var f = this.first, + n = this.numItems, + c = this.collection, + l; + + if (c) { + l = c.length; + f = Math.min(f, l - n); + f = this.first = Math.max(f, 0); + this._last = Math.min(f + n, l) - 1; + } + }, + + dataChanged: function() { + if (this.get('data') && this.hasRendered) { + this.reset(); + } + } +}); + +},{'./kind':'enyo/kind','./DataRepeater':'enyo/DataRepeater'}],'enyo/Button':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Button~Button} kind. +* @module enyo/Button +*/ + +var + kind = require('../kind'); +var + ToolDecorator = require('../ToolDecorator'); + +/** +* {@link module:enyo/Button~Button} implements an HTML [button]{@glossary button}, with support +* for grouping using {@link module:enyo/Group~Group}. +* +* For more information, see the documentation on +* [Buttons]{@linkplain $dev-guide/building-apps/controls/buttons.html} in the +* Enyo Developer Guide. +* +* @class Button +* @extends module:enyo/ToolDecorator~ToolDecorator +* @ui +* @public +*/ +module.exports = kind( + /** @lends module:enyo/Button~Button.prototype */ { + + /** + * @private + */ + name: 'enyo.Button', + + /** + * @private + */ + kind: ToolDecorator, + + /** + * @private + */ + tag: 'button', + + /** + * @private + */ + attributes: { + /** + * Set to `'button'`; otherwise, the default value would be `'submit'`, which + * can cause unexpected problems when [controls]{@link module:enyo/Control~Control} are used + * inside of a [form]{@glossary form}. + * + * @type {String} + * @private + */ + type: 'button' + }, + + /** + * @private + */ + published: + /** @lends module:enyo/Button~Button.prototype */ { + + /** + * When `true`, the [button]{@glossary button} is shown as disabled and does not + * generate tap [events]{@glossary event}. + * + * @type {Boolean} + * @default false + * @public + */ + disabled: false + }, + + /** + * @method + * @private + */ + create: kind.inherit(function (sup) { + return function() { + sup.apply(this, arguments); + this.disabledChanged(); + }; + }), + + /** + * @private + */ + disabledChanged: function () { + this.setAttribute('disabled', this.disabled); + }, + + /** + * @private + */ + tap: function () { + if (this.disabled) { + // work around for platforms like Chrome on Android or Opera that send + // mouseup to disabled form controls + return true; + } else { + this.setActive(true); + } + }, + + // Accessibility + + /** + * @default button + * @type {String} + * @see enyo/AccessibilitySupport~AccessibilitySupport#accessibilityRole + * @public + */ + accessibilityRole: 'button', + + /** + * When `true`, `aria-pressed` will reflect the state of + * {@link module:enyo/GroupItem~GroupItem#active} + * + * @type {Boolean} + * @default false + * @public + */ + accessibilityPressed: false, + + /** + * @private + */ + ariaObservers: [ + {from: 'disabled', to: 'aria-disabled'}, + {path: ['active', 'accessibilityPressed'], method: function () { + this.setAriaAttribute('aria-pressed', this.accessibilityPressed ? String(this.active) : null); + }}, + {from: 'accessibilityRole', to: 'role'} + ] +}); + +},{'../kind':'enyo/kind','../ToolDecorator':'enyo/ToolDecorator'}],'enyo/NewDataList':[function (module,exports,global,require,request){ +require('enyo'); + +var + kind = require('./kind'), + dom = require('./dom'); + +var + Scrollable = require('./Scrollable'), + VirtualDataRepeater = require('./VirtualDataRepeater'); + +module.exports = kind({ + name: 'enyo.NewDataList', + kind: VirtualDataRepeater, + direction: 'vertical', + itemHeight: 100, + itemWidth: 100, + spacing: 0, + rows: 'auto', + columns: 'auto', + overhang: 3, + // Experimental + scrollToBoundaries: false, + mixins: [Scrollable], + observers: [ + {method: 'reset', path: [ + 'direction', 'columns', 'rows', + 'itemHeight', 'itemWidth', 'columns' + ]} + ], + /** + * Returns an array of list items that are currently visible (whether partially + * or fully). + * + * Experimental API -- subject to change. + * + * @public + */ + getVisibleItems: function() { + return this.orderedChildren.slice(this.firstVisibleI, this.lastVisibleI + 1); + }, + + /** + * Returns an array of list items that are currently fully visible. + * + * Experimental API -- subject to change. + * + * @public + */ + getFullyVisibleItems: function() { + return this.orderedChildren.slice(this.firstFullyVisibleI, this.lastFullyVisibleI + 1); + }, + + /** + * Scrolls to a list item (specified by index). + * + * @param {number} index - The (zero-based) index of the item to scroll to + * @param {Object} opts - Scrolling options (see enyo/Scrollable#scrollTo) + * @public + */ + scrollToItem: function (index, opts) { + var b = this.getItemBounds(index); + + // If the item is near the horizontal or vertical + // origin, scroll all the way there + if (b.x <= this.spacing) { + b.x = 0; + } + if (b.y <= this.spacing) { + b.y = 0; + } + + this.scrollTo(b.x, b.y, opts); + }, + + /** + * @private + */ + calculateMetrics: function(opts) { + var sp = this.spacing, + n = this.hasNode(), + oT = this.topOffset, + oR = this.rightOffset, + oB = this.bottomOffset, + oL = this.leftOffset, + cw = (opts && opts.width !== undefined) ? + opts.width : + n.clientWidth - oR - oL, + ch = (opts && opts.height !== undefined) ? + opts.height : + n.clientHeight - oT - oB, + s1, s2, md1, md2, d2x, si, is1, is2, d1, d2, minMax, num; + + if (this.direction == 'vertical') { + s1 = ch; + s2 = cw; + md1 = this.minItemHeight; + md2 = this.minItemWidth; + is1 = this.itemHeight; + is2 = this.itemWidth; + d2x = this.columns; + si = 'verticalSnapIncrement'; + } + else { + s1 = cw; + s2 = ch; + md1 = this.minItemWidth; + md2 = this.minItemHeight; + is1 = this.itemWidth; + is2 = this.itemHeight; + d2x = this.rows; + si = 'horizontalSnapIncrement'; + } + + this.sizeItems = (md1 && md2); + + if (this.sizeItems) { + // the number of columns is the ratio of the available width minus the spacing + // by the minimum tile width plus the spacing + d2x = Math.max(Math.floor((s2 - (sp * 2)) / (md2 + sp)), 1); + // the actual tile width is a ratio of the remaining width after all columns + // and spacing are accounted for and the number of columns that we know we should have + is2 = Math.round((s2 - (sp * (d2x + 1))) / d2x); + // the actual tile height is related to the tile width + is1 = Math.round(md1 * (is2 / md2)); + } + else if (d2x === 'auto') { + d2x = 1; + } + + d1 = sp + is1; + d2 = sp + is2; + + minMax = d1 * 2; + this.threshold = { min: -Infinity, max: minMax, minMax: minMax }; + + num = d2x * (Math.ceil(s1 / d1) + this.overhang); + + this.dim2extent = d2x; + this.itemSize = is1; + this.itemSize2 = is2; + this.delta = d1; + this.delta2 = d2; + this.size = s1; + this.size2 = s2; + + if (this.scrollToBoundaries) { + this.set(si, d1); + } + // We don't call the setter here, because doing so would trigger a + // redundant and expensive call to doIt(). This approach should be fine + // as long as calculateMetrics() is called only by reset(). + this.numItems = num; + }, + /** + * @private + */ + scroll: function() { + var tt = this.threshold, + v = (this.direction === 'vertical'), + val = v ? this.scrollTop : this.scrollLeft, + dir = v ? this.yDir : this.xDir, + delta = this.delta, + cb = this.cachedBounds ? this.cachedBounds : this._getScrollBounds(), + maxVal = v ? cb.maxTop : cb.maxLeft, + minMax = this.threshold.minMax, + maxMin = maxVal - minMax, + d2x = this.dim2extent, + d, st, j; + if (dir == 1 && val > tt.max) { + d = val - tt.max; + st = Math.ceil(d / delta); + j = st * delta; + tt.max = Math.min(maxVal, tt.max + j); + tt.min = Math.min(maxMin, tt.max - delta); + this.set('first', (d2x * Math.ceil(this.first / d2x)) + (st * d2x)); + } + else if (dir == -1 && val < tt.min) { + d = tt.min - val; + st = Math.ceil(d / delta); + j = st * delta; + tt.max = Math.max(minMax, tt.min - (j - delta)); + tt.min = (tt.max > minMax) ? tt.max - delta : -Infinity; + this.set('first', (d2x * Math.ceil(this.first / d2x)) - (st * d2x)); + } + if (tt.max > maxVal) { + if (maxVal < minMax) { + tt.max = minMax; + tt.min = -Infinity; + } + else { + tt.max = maxVal; + tt.min = maxMin; + } + } + this.positionChildren(); + }, + /** + * @private + */ + positionChildren: function() { + var oc = this.orderedChildren, + e = this.dim2extent, + v = (this.direction == 'vertical'), + sd = v ? 'scrollTop' : 'scrollLeft', + sv = Math.round(this[sd]), + sp = this.spacing, + is = this.itemSize, + is2 = this.itemSize2, + i, c, idx, g, p, g2, p2, a, b, w, h, fvi, ffvi, lvi, lfvi; + + if (oc) { + for (i = 0; i < oc.length; i++) { + c = oc[i]; + idx = c.index; + g = Math.floor(idx / e); + g2 = idx % e; + p = sp + (g * this.delta) - sv; + p2 = sp + (g2 * this.delta2); + if (v) { + a = p2; + b = p; + w = is2; + h = is; + } + else { + a = p; + b = p2; + w = is; + h = is2; + } + if (this.rtl) { + a = -a; + } + if (this.sizeItems) { + c.applyStyle('width', w + 'px'); + c.applyStyle('height', h + 'px'); + } + if (fvi === undefined && (p + is) > 0) { + fvi = i; + } + if (ffvi === undefined && p >= 0) { + ffvi = i; + } + if ((p + is) <= this.size) { + lfvi = i; + } + if (p < this.size) { + lvi = i; + } + dom.transform(c, {translate3d: a + 'px, ' + b + 'px, 0'}); + } + this.firstVisibleI = fvi; + this.lastVisibleI = lvi; + this.firstFullyVisibleI = ffvi; + this.lastFullyVisibleI = lfvi; + } + }, + + // Choosing perf over DRY, so duplicating some positioning + // logic also implemented in positionChildren() + /** + * @private + */ + getItemBounds: function (index) { + var d2x = this.dim2extent, + sp = this.spacing, + is = this.itemSize, + is2 = this.itemSize2, + g, g2, p, p2; + + g = Math.floor(index / d2x); + g2 = index % d2x; + p = sp + (g * this.delta); + p2 = sp + (g2 * this.delta2); + + return (this.direction == 'vertical') + ? { x: p2, y: p, w: is2, h: is } + : { x: p, y: p2, w: is, h: is2 } + ; + }, + + /** + * @private + */ + getScrollHeight: function () { + return (this.direction === 'vertical' ? this.getVirtualScrollDimension() : null); + }, + /** + * @private + */ + getScrollWidth: function () { + return (this.direction === 'horizontal' ? this.getVirtualScrollDimension() : null); + }, + /** + * @private + */ + getVirtualScrollDimension: function() { + var len = this.collection ? this.collection.length : 0; + + return (Math.ceil(len / this.dim2extent) * this.delta) + this.spacing; + }, + + /** + * @private + */ + modelsAdded: kind.inherit(function (sup) { + return function() { + this.calcBoundaries(); + sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + modelsRemoved: kind.inherit(function (sup) { + return function() { + this.calcBoundaries(); + sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + showingChangedHandler: kind.inherit(function (sup) { + return function () { + if (this.needsReset) { + this.reset(); + } + else if (this.needsRefresh) { + this.refresh(); + } + return sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + refresh: kind.inherit(function (sup) { + return function () { + if (this.getAbsoluteShowing()) { + if (arguments[1] === 'reset') { + this.calcBoundaries(); + } + this.needsRefresh = false; + sup.apply(this, arguments); + } + else { + this.needsRefresh = true; + } + }; + }), + + /** + * @private + */ + reset: kind.inherit(function (sup) { + return function () { + var v; + if (this.getAbsoluteShowing()) { + v = (this.direction === 'vertical'); + this.set('scrollTop', 0); + this.set('scrollLeft', 0); + this.set('vertical', v ? 'auto' : 'hidden'); + this.set('horizontal', v ? 'hidden' : 'auto'); + this.calculateMetrics(); + this.calcBoundaries(); + this.first = 0; + this.needsReset = false; + sup.apply(this, arguments); + } + else { + this.needsReset = true; + } + }; + }) +}); + +},{'./kind':'enyo/kind','./dom':'enyo/dom','./Scrollable':'enyo/Scrollable','./VirtualDataRepeater':'enyo/VirtualDataRepeater'}] + }; + + + // below is where the generated entries list will be assigned if there is one + entries = null; + + + // if a different require exists then we can't actually work because we don't know the call- + // pattern of the existing function to be able to consume it + // @todo it could be possible to use an alternate name internally when another one exists but + // this would lead to inconsistency of expectations to end-devs in the promise that we expose + // our module's via the require function + if (require && !require.hasOwnProperty('enyo')) { + throw new Error('Incompatible require function found in scope'); + } + if (!require || typeof require != 'function') { + require = scope.require = function (target) { + var entry, exports, ctx, map, mod, value, lreq; + // it may have already been resolved + if (exported.hasOwnProperty(target)) return exported[target]; + entry = manifest[target]; + if (!entry) throw new Error( + 'Could not find the required module: "' + target + '"' + ); + if (!(entry instanceof Array)) { + if (typeof entry == 'object' && (entry.source || entry.style)) { + throw new Error( + 'Attempt to require a requested module: "' + target + '"' + ); + } else if (typeof entry == 'string') { + throw new Error( + 'Attempt to require a bundle entry: "' + target + '"' + ); + } + throw new Error( + 'The shared manifest has been corrupted, the module is invalid: "' + target + '"' + ); + } + mod = entry[0]; + map = entry[1]; + if (typeof mod != 'function') throw new Error( + 'The shared manifest has been corrupted, the module is invalid: "' + target + '"' + ); + ctx = {exports: {}}; + if (scope.request) { + if (map) { + lreq = function (name) { + return scope.request(map.hasOwnProperty(name) ? map[name] : name); + }; + lreq.isRequest = scope.request.isRequest; + } else lreq = scope.request; + } + mod( + ctx, + ctx.exports, + scope, + // primarily for sanity/debugging will give a little bit more context when errors + // are encountered + !map ? scope.require : function (name) { + return require(map.hasOwnProperty(name) ? map[name] : name); + }, + lreq + ); + exports = exported[target] = ctx.exports; + return exports; + }; + if (Object.defineProperty) Object.defineProperty(require, 'enyo', { + value: true, + enumerable: false, + configurable: false, + writable: false + }); + else require.enyo = true; + } + + // in occassions where requests api are being used, below this comment that implementation will + // be injected + + + // if another bundle has already established the shared manifest we need to update it with + // our modules + if (require.manifest) { + Object.keys(manifest).forEach(function (key) { + var value = manifest[key], existing; + if ((existing = require.manifest[key])) { + // if it is an array, we automatically accept it because it is a module definition + // and one definition should never overwrite another, the potential fail cases are + // if two requested bundles reference another external bundle and the second + // removes the first one + if (!(value instanceof Array)) return; + } + require.manifest[key] = value; + }); + manifest = require.manifest; + exported = require.exported; + } + // otherwise we need to set it to our manifest + else { + if (Object.defineProperties) { + Object.defineProperties(require, { + manifest: { + value: manifest, + enumerable: false, + configurable: false, + writable: false + }, + exported: { + value: (exported = {}), + enumerable: false, + configurable: false, + writable: false + } + }); + } else { + require.manifest = manifest; + require.exported = (exported = {}); + } + } + + // if this bundle contains any entries that need to be executed it will do that now + if (entries && entries instanceof Array) entries.forEach(function (name) { require(name); }); +})(this); +//# sourceMappingURL=enyo.js.map \ No newline at end of file diff --git a/tests/index.html b/tests/index.html new file mode 100644 index 000000000..e3fa0bab5 --- /dev/null +++ b/tests/index.html @@ -0,0 +1,24 @@ + + + + + Tween module tests + + + + + +
+
+ + + + + + + + \ No newline at end of file diff --git a/tests/node_modules/chai.js b/tests/node_modules/chai.js new file mode 100644 index 000000000..7c9017149 --- /dev/null +++ b/tests/node_modules/chai.js @@ -0,0 +1,5332 @@ + +;(function(){ + +/** + * Require the module at `name`. + * + * @param {String} name + * @return {Object} exports + * @api public + */ + +function require(name) { + var module = require.modules[name]; + if (!module) throw new Error('failed to require "' + name + '"'); + + if (!('exports' in module) && typeof module.definition === 'function') { + module.client = module.component = true; + module.definition.call(this, module.exports = {}, module); + delete module.definition; + } + + return module.exports; +} + +/** + * Meta info, accessible in the global scope unless you use AMD option. + */ + +require.loader = 'component'; + +/** + * Internal helper object, contains a sorting function for semantiv versioning + */ +require.helper = {}; +require.helper.semVerSort = function(a, b) { + var aArray = a.version.split('.'); + var bArray = b.version.split('.'); + for (var i=0; i bLex ? 1 : -1; + continue; + } else if (aInt > bInt) { + return 1; + } else { + return -1; + } + } + return 0; +} + +/** + * Find and require a module which name starts with the provided name. + * If multiple modules exists, the highest semver is used. + * This function can only be used for remote dependencies. + + * @param {String} name - module name: `user~repo` + * @param {Boolean} returnPath - returns the canonical require path if true, + * otherwise it returns the epxorted module + */ +require.latest = function (name, returnPath) { + function showError(name) { + throw new Error('failed to find latest module of "' + name + '"'); + } + // only remotes with semvers, ignore local files conataining a '/' + var versionRegexp = /(.*)~(.*)@v?(\d+\.\d+\.\d+[^\/]*)$/; + var remoteRegexp = /(.*)~(.*)/; + if (!remoteRegexp.test(name)) showError(name); + var moduleNames = Object.keys(require.modules); + var semVerCandidates = []; + var otherCandidates = []; // for instance: name of the git branch + for (var i=0; i 0) { + var module = semVerCandidates.sort(require.helper.semVerSort).pop().name; + if (returnPath === true) { + return module; + } + return require(module); + } + // if the build contains more than one branch of the same module + // you should not use this funciton + var module = otherCandidates.sort(function(a, b) {return a.name > b.name})[0].name; + if (returnPath === true) { + return module; + } + return require(module); +} + +/** + * Registered modules. + */ + +require.modules = {}; + +/** + * Register module at `name` with callback `definition`. + * + * @param {String} name + * @param {Function} definition + * @api private + */ + +require.register = function (name, definition) { + require.modules[name] = { + definition: definition + }; +}; + +/** + * Define a module's exports immediately with `exports`. + * + * @param {String} name + * @param {Generic} exports + * @api private + */ + +require.define = function (name, exports) { + require.modules[name] = { + exports: exports + }; +}; +require.register("chaijs~assertion-error@1.0.0", function (exports, module) { +/*! + * assertion-error + * Copyright(c) 2013 Jake Luer + * MIT Licensed + */ + +/*! + * Return a function that will copy properties from + * one object to another excluding any originally + * listed. Returned function will create a new `{}`. + * + * @param {String} excluded properties ... + * @return {Function} + */ + +function exclude () { + var excludes = [].slice.call(arguments); + + function excludeProps (res, obj) { + Object.keys(obj).forEach(function (key) { + if (!~excludes.indexOf(key)) res[key] = obj[key]; + }); + } + + return function extendExclude () { + var args = [].slice.call(arguments) + , i = 0 + , res = {}; + + for (; i < args.length; i++) { + excludeProps(res, args[i]); + } + + return res; + }; +}; + +/*! + * Primary Exports + */ + +module.exports = AssertionError; + +/** + * ### AssertionError + * + * An extension of the JavaScript `Error` constructor for + * assertion and validation scenarios. + * + * @param {String} message + * @param {Object} properties to include (optional) + * @param {callee} start stack function (optional) + */ + +function AssertionError (message, _props, ssf) { + var extend = exclude('name', 'message', 'stack', 'constructor', 'toJSON') + , props = extend(_props || {}); + + // default values + this.message = message || 'Unspecified AssertionError'; + this.showDiff = false; + + // copy from properties + for (var key in props) { + this[key] = props[key]; + } + + // capture stack trace + ssf = ssf || arguments.callee; + if (ssf && Error.captureStackTrace) { + Error.captureStackTrace(this, ssf); + } +} + +/*! + * Inherit from Error.prototype + */ + +AssertionError.prototype = Object.create(Error.prototype); + +/*! + * Statically set name + */ + +AssertionError.prototype.name = 'AssertionError'; + +/*! + * Ensure correct constructor + */ + +AssertionError.prototype.constructor = AssertionError; + +/** + * Allow errors to be converted to JSON for static transfer. + * + * @param {Boolean} include stack (default: `true`) + * @return {Object} object that can be `JSON.stringify` + */ + +AssertionError.prototype.toJSON = function (stack) { + var extend = exclude('constructor', 'toJSON', 'stack') + , props = extend({ name: this.name }, this); + + // include stack if exists and not turned off + if (false !== stack && this.stack) { + props.stack = this.stack; + } + + return props; +}; + +}); + +require.register("chaijs~type-detect@0.1.1", function (exports, module) { +/*! + * type-detect + * Copyright(c) 2013 jake luer + * MIT Licensed + */ + +/*! + * Primary Exports + */ + +var exports = module.exports = getType; + +/*! + * Detectable javascript natives + */ + +var natives = { + '[object Array]': 'array' + , '[object RegExp]': 'regexp' + , '[object Function]': 'function' + , '[object Arguments]': 'arguments' + , '[object Date]': 'date' +}; + +/** + * ### typeOf (obj) + * + * Use several different techniques to determine + * the type of object being tested. + * + * + * @param {Mixed} object + * @return {String} object type + * @api public + */ + +function getType (obj) { + var str = Object.prototype.toString.call(obj); + if (natives[str]) return natives[str]; + if (obj === null) return 'null'; + if (obj === undefined) return 'undefined'; + if (obj === Object(obj)) return 'object'; + return typeof obj; +} + +exports.Library = Library; + +/** + * ### Library + * + * Create a repository for custom type detection. + * + * ```js + * var lib = new type.Library; + * ``` + * + */ + +function Library () { + this.tests = {}; +} + +/** + * #### .of (obj) + * + * Expose replacement `typeof` detection to the library. + * + * ```js + * if ('string' === lib.of('hello world')) { + * // ... + * } + * ``` + * + * @param {Mixed} object to test + * @return {String} type + */ + +Library.prototype.of = getType; + +/** + * #### .define (type, test) + * + * Add a test to for the `.test()` assertion. + * + * Can be defined as a regular expression: + * + * ```js + * lib.define('int', /^[0-9]+$/); + * ``` + * + * ... or as a function: + * + * ```js + * lib.define('bln', function (obj) { + * if ('boolean' === lib.of(obj)) return true; + * var blns = [ 'yes', 'no', 'true', 'false', 1, 0 ]; + * if ('string' === lib.of(obj)) obj = obj.toLowerCase(); + * return !! ~blns.indexOf(obj); + * }); + * ``` + * + * @param {String} type + * @param {RegExp|Function} test + * @api public + */ + +Library.prototype.define = function (type, test) { + if (arguments.length === 1) return this.tests[type]; + this.tests[type] = test; + return this; +}; + +/** + * #### .test (obj, test) + * + * Assert that an object is of type. Will first + * check natives, and if that does not pass it will + * use the user defined custom tests. + * + * ```js + * assert(lib.test('1', 'int')); + * assert(lib.test('yes', 'bln')); + * ``` + * + * @param {Mixed} object + * @param {String} type + * @return {Boolean} result + * @api public + */ + +Library.prototype.test = function (obj, type) { + if (type === getType(obj)) return true; + var test = this.tests[type]; + + if (test && 'regexp' === getType(test)) { + return test.test(obj); + } else if (test && 'function' === getType(test)) { + return test(obj); + } else { + throw new ReferenceError('Type test "' + type + '" not defined or invalid.'); + } +}; + +}); + +require.register("chaijs~deep-eql@0.1.3", function (exports, module) { +/*! + * deep-eql + * Copyright(c) 2013 Jake Luer + * MIT Licensed + */ + +/*! + * Module dependencies + */ + +var type = require('chaijs~type-detect@0.1.1'); + +/*! + * Buffer.isBuffer browser shim + */ + +var Buffer; +try { Buffer = require('buffer').Buffer; } +catch(ex) { + Buffer = {}; + Buffer.isBuffer = function() { return false; } +} + +/*! + * Primary Export + */ + +module.exports = deepEqual; + +/** + * Assert super-strict (egal) equality between + * two objects of any type. + * + * @param {Mixed} a + * @param {Mixed} b + * @param {Array} memoised (optional) + * @return {Boolean} equal match + */ + +function deepEqual(a, b, m) { + if (sameValue(a, b)) { + return true; + } else if ('date' === type(a)) { + return dateEqual(a, b); + } else if ('regexp' === type(a)) { + return regexpEqual(a, b); + } else if (Buffer.isBuffer(a)) { + return bufferEqual(a, b); + } else if ('arguments' === type(a)) { + return argumentsEqual(a, b, m); + } else if (!typeEqual(a, b)) { + return false; + } else if (('object' !== type(a) && 'object' !== type(b)) + && ('array' !== type(a) && 'array' !== type(b))) { + return sameValue(a, b); + } else { + return objectEqual(a, b, m); + } +} + +/*! + * Strict (egal) equality test. Ensures that NaN always + * equals NaN and `-0` does not equal `+0`. + * + * @param {Mixed} a + * @param {Mixed} b + * @return {Boolean} equal match + */ + +function sameValue(a, b) { + if (a === b) return a !== 0 || 1 / a === 1 / b; + return a !== a && b !== b; +} + +/*! + * Compare the types of two given objects and + * return if they are equal. Note that an Array + * has a type of `array` (not `object`) and arguments + * have a type of `arguments` (not `array`/`object`). + * + * @param {Mixed} a + * @param {Mixed} b + * @return {Boolean} result + */ + +function typeEqual(a, b) { + return type(a) === type(b); +} + +/*! + * Compare two Date objects by asserting that + * the time values are equal using `saveValue`. + * + * @param {Date} a + * @param {Date} b + * @return {Boolean} result + */ + +function dateEqual(a, b) { + if ('date' !== type(b)) return false; + return sameValue(a.getTime(), b.getTime()); +} + +/*! + * Compare two regular expressions by converting them + * to string and checking for `sameValue`. + * + * @param {RegExp} a + * @param {RegExp} b + * @return {Boolean} result + */ + +function regexpEqual(a, b) { + if ('regexp' !== type(b)) return false; + return sameValue(a.toString(), b.toString()); +} + +/*! + * Assert deep equality of two `arguments` objects. + * Unfortunately, these must be sliced to arrays + * prior to test to ensure no bad behavior. + * + * @param {Arguments} a + * @param {Arguments} b + * @param {Array} memoize (optional) + * @return {Boolean} result + */ + +function argumentsEqual(a, b, m) { + if ('arguments' !== type(b)) return false; + a = [].slice.call(a); + b = [].slice.call(b); + return deepEqual(a, b, m); +} + +/*! + * Get enumerable properties of a given object. + * + * @param {Object} a + * @return {Array} property names + */ + +function enumerable(a) { + var res = []; + for (var key in a) res.push(key); + return res; +} + +/*! + * Simple equality for flat iterable objects + * such as Arrays or Node.js buffers. + * + * @param {Iterable} a + * @param {Iterable} b + * @return {Boolean} result + */ + +function iterableEqual(a, b) { + if (a.length !== b.length) return false; + + var i = 0; + var match = true; + + for (; i < a.length; i++) { + if (a[i] !== b[i]) { + match = false; + break; + } + } + + return match; +} + +/*! + * Extension to `iterableEqual` specifically + * for Node.js Buffers. + * + * @param {Buffer} a + * @param {Mixed} b + * @return {Boolean} result + */ + +function bufferEqual(a, b) { + if (!Buffer.isBuffer(b)) return false; + return iterableEqual(a, b); +} + +/*! + * Block for `objectEqual` ensuring non-existing + * values don't get in. + * + * @param {Mixed} object + * @return {Boolean} result + */ + +function isValue(a) { + return a !== null && a !== undefined; +} + +/*! + * Recursively check the equality of two objects. + * Once basic sameness has been established it will + * defer to `deepEqual` for each enumerable key + * in the object. + * + * @param {Mixed} a + * @param {Mixed} b + * @return {Boolean} result + */ + +function objectEqual(a, b, m) { + if (!isValue(a) || !isValue(b)) { + return false; + } + + if (a.prototype !== b.prototype) { + return false; + } + + var i; + if (m) { + for (i = 0; i < m.length; i++) { + if ((m[i][0] === a && m[i][1] === b) + || (m[i][0] === b && m[i][1] === a)) { + return true; + } + } + } else { + m = []; + } + + try { + var ka = enumerable(a); + var kb = enumerable(b); + } catch (ex) { + return false; + } + + ka.sort(); + kb.sort(); + + if (!iterableEqual(ka, kb)) { + return false; + } + + m.push([ a, b ]); + + var key; + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!deepEqual(a[key], b[key], m)) { + return false; + } + } + + return true; +} + +}); + +require.register("chai", function (exports, module) { +module.exports = require('chai/lib/chai.js'); + +}); + +require.register("chai/lib/chai.js", function (exports, module) { +/*! + * chai + * Copyright(c) 2011-2014 Jake Luer + * MIT Licensed + */ + +var used = [] + , exports = module.exports = {}; + +/*! + * Chai version + */ + +exports.version = '2.1.0'; + +/*! + * Assertion Error + */ + +exports.AssertionError = require('chaijs~assertion-error@1.0.0'); + +/*! + * Utils for plugins (not exported) + */ + +var util = require('chai/lib/chai/utils/index.js'); + +/** + * # .use(function) + * + * Provides a way to extend the internals of Chai + * + * @param {Function} + * @returns {this} for chaining + * @api public + */ + +exports.use = function (fn) { + if (!~used.indexOf(fn)) { + fn(this, util); + used.push(fn); + } + + return this; +}; + +/*! + * Utility Functions + */ + +exports.util = util; + +/*! + * Configuration + */ + +var config = require('chai/lib/chai/config.js'); +exports.config = config; + +/*! + * Primary `Assertion` prototype + */ + +var assertion = require('chai/lib/chai/assertion.js'); +exports.use(assertion); + +/*! + * Core Assertions + */ + +var core = require('chai/lib/chai/core/assertions.js'); +exports.use(core); + +/*! + * Expect interface + */ + +var expect = require('chai/lib/chai/interface/expect.js'); +exports.use(expect); + +/*! + * Should interface + */ + +var should = require('chai/lib/chai/interface/should.js'); +exports.use(should); + +/*! + * Assert interface + */ + +var assert = require('chai/lib/chai/interface/assert.js'); +exports.use(assert); + +}); + +require.register("chai/lib/chai/assertion.js", function (exports, module) { +/*! + * chai + * http://chaijs.com + * Copyright(c) 2011-2014 Jake Luer + * MIT Licensed + */ + +var config = require('chai/lib/chai/config.js'); + +module.exports = function (_chai, util) { + /*! + * Module dependencies. + */ + + var AssertionError = _chai.AssertionError + , flag = util.flag; + + /*! + * Module export. + */ + + _chai.Assertion = Assertion; + + /*! + * Assertion Constructor + * + * Creates object for chaining. + * + * @api private + */ + + function Assertion (obj, msg, stack) { + flag(this, 'ssfi', stack || arguments.callee); + flag(this, 'object', obj); + flag(this, 'message', msg); + } + + Object.defineProperty(Assertion, 'includeStack', { + get: function() { + console.warn('Assertion.includeStack is deprecated, use chai.config.includeStack instead.'); + return config.includeStack; + }, + set: function(value) { + console.warn('Assertion.includeStack is deprecated, use chai.config.includeStack instead.'); + config.includeStack = value; + } + }); + + Object.defineProperty(Assertion, 'showDiff', { + get: function() { + console.warn('Assertion.showDiff is deprecated, use chai.config.showDiff instead.'); + return config.showDiff; + }, + set: function(value) { + console.warn('Assertion.showDiff is deprecated, use chai.config.showDiff instead.'); + config.showDiff = value; + } + }); + + Assertion.addProperty = function (name, fn) { + util.addProperty(this.prototype, name, fn); + }; + + Assertion.addMethod = function (name, fn) { + util.addMethod(this.prototype, name, fn); + }; + + Assertion.addChainableMethod = function (name, fn, chainingBehavior) { + util.addChainableMethod(this.prototype, name, fn, chainingBehavior); + }; + + Assertion.overwriteProperty = function (name, fn) { + util.overwriteProperty(this.prototype, name, fn); + }; + + Assertion.overwriteMethod = function (name, fn) { + util.overwriteMethod(this.prototype, name, fn); + }; + + Assertion.overwriteChainableMethod = function (name, fn, chainingBehavior) { + util.overwriteChainableMethod(this.prototype, name, fn, chainingBehavior); + }; + + /*! + * ### .assert(expression, message, negateMessage, expected, actual) + * + * Executes an expression and check expectations. Throws AssertionError for reporting if test doesn't pass. + * + * @name assert + * @param {Philosophical} expression to be tested + * @param {String or Function} message or function that returns message to display if fails + * @param {String or Function} negatedMessage or function that returns negatedMessage to display if negated expression fails + * @param {Mixed} expected value (remember to check for negation) + * @param {Mixed} actual (optional) will default to `this.obj` + * @api private + */ + + Assertion.prototype.assert = function (expr, msg, negateMsg, expected, _actual, showDiff) { + var ok = util.test(this, arguments); + if (true !== showDiff) showDiff = false; + if (true !== config.showDiff) showDiff = false; + + if (!ok) { + var msg = util.getMessage(this, arguments) + , actual = util.getActual(this, arguments); + throw new AssertionError(msg, { + actual: actual + , expected: expected + , showDiff: showDiff + }, (config.includeStack) ? this.assert : flag(this, 'ssfi')); + } + }; + + /*! + * ### ._obj + * + * Quick reference to stored `actual` value for plugin developers. + * + * @api private + */ + + Object.defineProperty(Assertion.prototype, '_obj', + { get: function () { + return flag(this, 'object'); + } + , set: function (val) { + flag(this, 'object', val); + } + }); +}; + +}); + +require.register("chai/lib/chai/config.js", function (exports, module) { +module.exports = { + + /** + * ### config.includeStack + * + * User configurable property, influences whether stack trace + * is included in Assertion error message. Default of false + * suppresses stack trace in the error message. + * + * chai.config.includeStack = true; // enable stack on error + * + * @param {Boolean} + * @api public + */ + + includeStack: false, + + /** + * ### config.showDiff + * + * User configurable property, influences whether or not + * the `showDiff` flag should be included in the thrown + * AssertionErrors. `false` will always be `false`; `true` + * will be true when the assertion has requested a diff + * be shown. + * + * @param {Boolean} + * @api public + */ + + showDiff: true, + + /** + * ### config.truncateThreshold + * + * User configurable property, sets length threshold for actual and + * expected values in assertion errors. If this threshold is exceeded, + * the value is truncated. + * + * Set it to zero if you want to disable truncating altogether. + * + * chai.config.truncateThreshold = 0; // disable truncating + * + * @param {Number} + * @api public + */ + + truncateThreshold: 40 + +}; + +}); + +require.register("chai/lib/chai/core/assertions.js", function (exports, module) { +/*! + * chai + * http://chaijs.com + * Copyright(c) 2011-2014 Jake Luer + * MIT Licensed + */ + +module.exports = function (chai, _) { + var Assertion = chai.Assertion + , toString = Object.prototype.toString + , flag = _.flag; + + /** + * ### Language Chains + * + * The following are provided as chainable getters to + * improve the readability of your assertions. They + * do not provide testing capabilities unless they + * have been overwritten by a plugin. + * + * **Chains** + * + * - to + * - be + * - been + * - is + * - that + * - which + * - and + * - has + * - have + * - with + * - at + * - of + * - same + * + * @name language chains + * @api public + */ + + [ 'to', 'be', 'been' + , 'is', 'and', 'has', 'have' + , 'with', 'that', 'which', 'at' + , 'of', 'same' ].forEach(function (chain) { + Assertion.addProperty(chain, function () { + return this; + }); + }); + + /** + * ### .not + * + * Negates any of assertions following in the chain. + * + * expect(foo).to.not.equal('bar'); + * expect(goodFn).to.not.throw(Error); + * expect({ foo: 'baz' }).to.have.property('foo') + * .and.not.equal('bar'); + * + * @name not + * @api public + */ + + Assertion.addProperty('not', function () { + flag(this, 'negate', true); + }); + + /** + * ### .deep + * + * Sets the `deep` flag, later used by the `equal` and + * `property` assertions. + * + * expect(foo).to.deep.equal({ bar: 'baz' }); + * expect({ foo: { bar: { baz: 'quux' } } }) + * .to.have.deep.property('foo.bar.baz', 'quux'); + * + * @name deep + * @api public + */ + + Assertion.addProperty('deep', function () { + flag(this, 'deep', true); + }); + + /** + * ### .any + * + * Sets the `any` flag, (opposite of the `all` flag) + * later used in the `keys` assertion. + * + * expect(foo).to.have.any.keys('bar', 'baz'); + * + * @name any + * @api public + */ + + Assertion.addProperty('any', function () { + flag(this, 'any', true); + flag(this, 'all', false) + }); + + + /** + * ### .all + * + * Sets the `all` flag (opposite of the `any` flag) + * later used by the `keys` assertion. + * + * expect(foo).to.have.all.keys('bar', 'baz'); + * + * @name all + * @api public + */ + + Assertion.addProperty('all', function () { + flag(this, 'all', true); + flag(this, 'any', false); + }); + + /** + * ### .a(type) + * + * The `a` and `an` assertions are aliases that can be + * used either as language chains or to assert a value's + * type. + * + * // typeof + * expect('test').to.be.a('string'); + * expect({ foo: 'bar' }).to.be.an('object'); + * expect(null).to.be.a('null'); + * expect(undefined).to.be.an('undefined'); + * + * // language chain + * expect(foo).to.be.an.instanceof(Foo); + * + * @name a + * @alias an + * @param {String} type + * @param {String} message _optional_ + * @api public + */ + + function an (type, msg) { + if (msg) flag(this, 'message', msg); + type = type.toLowerCase(); + var obj = flag(this, 'object') + , article = ~[ 'a', 'e', 'i', 'o', 'u' ].indexOf(type.charAt(0)) ? 'an ' : 'a '; + + this.assert( + type === _.type(obj) + , 'expected #{this} to be ' + article + type + , 'expected #{this} not to be ' + article + type + ); + } + + Assertion.addChainableMethod('an', an); + Assertion.addChainableMethod('a', an); + + /** + * ### .include(value) + * + * The `include` and `contain` assertions can be used as either property + * based language chains or as methods to assert the inclusion of an object + * in an array or a substring in a string. When used as language chains, + * they toggle the `contains` flag for the `keys` assertion. + * + * expect([1,2,3]).to.include(2); + * expect('foobar').to.contain('foo'); + * expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo'); + * + * @name include + * @alias contain + * @alias includes + * @alias contains + * @param {Object|String|Number} obj + * @param {String} message _optional_ + * @api public + */ + + function includeChainingBehavior () { + flag(this, 'contains', true); + } + + function include (val, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + var expected = false; + if (_.type(obj) === 'array' && _.type(val) === 'object') { + for (var i in obj) { + if (_.eql(obj[i], val)) { + expected = true; + break; + } + } + } else if (_.type(val) === 'object') { + if (!flag(this, 'negate')) { + for (var k in val) new Assertion(obj).property(k, val[k]); + return; + } + var subset = {}; + for (var k in val) subset[k] = obj[k]; + expected = _.eql(subset, val); + } else { + expected = obj && ~obj.indexOf(val); + } + this.assert( + expected + , 'expected #{this} to include ' + _.inspect(val) + , 'expected #{this} to not include ' + _.inspect(val)); + } + + Assertion.addChainableMethod('include', include, includeChainingBehavior); + Assertion.addChainableMethod('contain', include, includeChainingBehavior); + Assertion.addChainableMethod('contains', include, includeChainingBehavior); + Assertion.addChainableMethod('includes', include, includeChainingBehavior); + + /** + * ### .ok + * + * Asserts that the target is truthy. + * + * expect('everthing').to.be.ok; + * expect(1).to.be.ok; + * expect(false).to.not.be.ok; + * expect(undefined).to.not.be.ok; + * expect(null).to.not.be.ok; + * + * @name ok + * @api public + */ + + Assertion.addProperty('ok', function () { + this.assert( + flag(this, 'object') + , 'expected #{this} to be truthy' + , 'expected #{this} to be falsy'); + }); + + /** + * ### .true + * + * Asserts that the target is `true`. + * + * expect(true).to.be.true; + * expect(1).to.not.be.true; + * + * @name true + * @api public + */ + + Assertion.addProperty('true', function () { + this.assert( + true === flag(this, 'object') + , 'expected #{this} to be true' + , 'expected #{this} to be false' + , this.negate ? false : true + ); + }); + + /** + * ### .false + * + * Asserts that the target is `false`. + * + * expect(false).to.be.false; + * expect(0).to.not.be.false; + * + * @name false + * @api public + */ + + Assertion.addProperty('false', function () { + this.assert( + false === flag(this, 'object') + , 'expected #{this} to be false' + , 'expected #{this} to be true' + , this.negate ? true : false + ); + }); + + /** + * ### .null + * + * Asserts that the target is `null`. + * + * expect(null).to.be.null; + * expect(undefined).not.to.be.null; + * + * @name null + * @api public + */ + + Assertion.addProperty('null', function () { + this.assert( + null === flag(this, 'object') + , 'expected #{this} to be null' + , 'expected #{this} not to be null' + ); + }); + + /** + * ### .undefined + * + * Asserts that the target is `undefined`. + * + * expect(undefined).to.be.undefined; + * expect(null).to.not.be.undefined; + * + * @name undefined + * @api public + */ + + Assertion.addProperty('undefined', function () { + this.assert( + undefined === flag(this, 'object') + , 'expected #{this} to be undefined' + , 'expected #{this} not to be undefined' + ); + }); + + /** + * ### .exist + * + * Asserts that the target is neither `null` nor `undefined`. + * + * var foo = 'hi' + * , bar = null + * , baz; + * + * expect(foo).to.exist; + * expect(bar).to.not.exist; + * expect(baz).to.not.exist; + * + * @name exist + * @api public + */ + + Assertion.addProperty('exist', function () { + this.assert( + null != flag(this, 'object') + , 'expected #{this} to exist' + , 'expected #{this} to not exist' + ); + }); + + + /** + * ### .empty + * + * Asserts that the target's length is `0`. For arrays, it checks + * the `length` property. For objects, it gets the count of + * enumerable keys. + * + * expect([]).to.be.empty; + * expect('').to.be.empty; + * expect({}).to.be.empty; + * + * @name empty + * @api public + */ + + Assertion.addProperty('empty', function () { + var obj = flag(this, 'object') + , expected = obj; + + if (Array.isArray(obj) || 'string' === typeof object) { + expected = obj.length; + } else if (typeof obj === 'object') { + expected = Object.keys(obj).length; + } + + this.assert( + !expected + , 'expected #{this} to be empty' + , 'expected #{this} not to be empty' + ); + }); + + /** + * ### .arguments + * + * Asserts that the target is an arguments object. + * + * function test () { + * expect(arguments).to.be.arguments; + * } + * + * @name arguments + * @alias Arguments + * @api public + */ + + function checkArguments () { + var obj = flag(this, 'object') + , type = Object.prototype.toString.call(obj); + this.assert( + '[object Arguments]' === type + , 'expected #{this} to be arguments but got ' + type + , 'expected #{this} to not be arguments' + ); + } + + Assertion.addProperty('arguments', checkArguments); + Assertion.addProperty('Arguments', checkArguments); + + /** + * ### .equal(value) + * + * Asserts that the target is strictly equal (`===`) to `value`. + * Alternately, if the `deep` flag is set, asserts that + * the target is deeply equal to `value`. + * + * expect('hello').to.equal('hello'); + * expect(42).to.equal(42); + * expect(1).to.not.equal(true); + * expect({ foo: 'bar' }).to.not.equal({ foo: 'bar' }); + * expect({ foo: 'bar' }).to.deep.equal({ foo: 'bar' }); + * + * @name equal + * @alias equals + * @alias eq + * @alias deep.equal + * @param {Mixed} value + * @param {String} message _optional_ + * @api public + */ + + function assertEqual (val, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + if (flag(this, 'deep')) { + return this.eql(val); + } else { + this.assert( + val === obj + , 'expected #{this} to equal #{exp}' + , 'expected #{this} to not equal #{exp}' + , val + , this._obj + , true + ); + } + } + + Assertion.addMethod('equal', assertEqual); + Assertion.addMethod('equals', assertEqual); + Assertion.addMethod('eq', assertEqual); + + /** + * ### .eql(value) + * + * Asserts that the target is deeply equal to `value`. + * + * expect({ foo: 'bar' }).to.eql({ foo: 'bar' }); + * expect([ 1, 2, 3 ]).to.eql([ 1, 2, 3 ]); + * + * @name eql + * @alias eqls + * @param {Mixed} value + * @param {String} message _optional_ + * @api public + */ + + function assertEql(obj, msg) { + if (msg) flag(this, 'message', msg); + this.assert( + _.eql(obj, flag(this, 'object')) + , 'expected #{this} to deeply equal #{exp}' + , 'expected #{this} to not deeply equal #{exp}' + , obj + , this._obj + , true + ); + } + + Assertion.addMethod('eql', assertEql); + Assertion.addMethod('eqls', assertEql); + + /** + * ### .above(value) + * + * Asserts that the target is greater than `value`. + * + * expect(10).to.be.above(5); + * + * Can also be used in conjunction with `length` to + * assert a minimum length. The benefit being a + * more informative error message than if the length + * was supplied directly. + * + * expect('foo').to.have.length.above(2); + * expect([ 1, 2, 3 ]).to.have.length.above(2); + * + * @name above + * @alias gt + * @alias greaterThan + * @param {Number} value + * @param {String} message _optional_ + * @api public + */ + + function assertAbove (n, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + if (flag(this, 'doLength')) { + new Assertion(obj, msg).to.have.property('length'); + var len = obj.length; + this.assert( + len > n + , 'expected #{this} to have a length above #{exp} but got #{act}' + , 'expected #{this} to not have a length above #{exp}' + , n + , len + ); + } else { + this.assert( + obj > n + , 'expected #{this} to be above ' + n + , 'expected #{this} to be at most ' + n + ); + } + } + + Assertion.addMethod('above', assertAbove); + Assertion.addMethod('gt', assertAbove); + Assertion.addMethod('greaterThan', assertAbove); + + /** + * ### .least(value) + * + * Asserts that the target is greater than or equal to `value`. + * + * expect(10).to.be.at.least(10); + * + * Can also be used in conjunction with `length` to + * assert a minimum length. The benefit being a + * more informative error message than if the length + * was supplied directly. + * + * expect('foo').to.have.length.of.at.least(2); + * expect([ 1, 2, 3 ]).to.have.length.of.at.least(3); + * + * @name least + * @alias gte + * @param {Number} value + * @param {String} message _optional_ + * @api public + */ + + function assertLeast (n, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + if (flag(this, 'doLength')) { + new Assertion(obj, msg).to.have.property('length'); + var len = obj.length; + this.assert( + len >= n + , 'expected #{this} to have a length at least #{exp} but got #{act}' + , 'expected #{this} to have a length below #{exp}' + , n + , len + ); + } else { + this.assert( + obj >= n + , 'expected #{this} to be at least ' + n + , 'expected #{this} to be below ' + n + ); + } + } + + Assertion.addMethod('least', assertLeast); + Assertion.addMethod('gte', assertLeast); + + /** + * ### .below(value) + * + * Asserts that the target is less than `value`. + * + * expect(5).to.be.below(10); + * + * Can also be used in conjunction with `length` to + * assert a maximum length. The benefit being a + * more informative error message than if the length + * was supplied directly. + * + * expect('foo').to.have.length.below(4); + * expect([ 1, 2, 3 ]).to.have.length.below(4); + * + * @name below + * @alias lt + * @alias lessThan + * @param {Number} value + * @param {String} message _optional_ + * @api public + */ + + function assertBelow (n, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + if (flag(this, 'doLength')) { + new Assertion(obj, msg).to.have.property('length'); + var len = obj.length; + this.assert( + len < n + , 'expected #{this} to have a length below #{exp} but got #{act}' + , 'expected #{this} to not have a length below #{exp}' + , n + , len + ); + } else { + this.assert( + obj < n + , 'expected #{this} to be below ' + n + , 'expected #{this} to be at least ' + n + ); + } + } + + Assertion.addMethod('below', assertBelow); + Assertion.addMethod('lt', assertBelow); + Assertion.addMethod('lessThan', assertBelow); + + /** + * ### .most(value) + * + * Asserts that the target is less than or equal to `value`. + * + * expect(5).to.be.at.most(5); + * + * Can also be used in conjunction with `length` to + * assert a maximum length. The benefit being a + * more informative error message than if the length + * was supplied directly. + * + * expect('foo').to.have.length.of.at.most(4); + * expect([ 1, 2, 3 ]).to.have.length.of.at.most(3); + * + * @name most + * @alias lte + * @param {Number} value + * @param {String} message _optional_ + * @api public + */ + + function assertMost (n, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + if (flag(this, 'doLength')) { + new Assertion(obj, msg).to.have.property('length'); + var len = obj.length; + this.assert( + len <= n + , 'expected #{this} to have a length at most #{exp} but got #{act}' + , 'expected #{this} to have a length above #{exp}' + , n + , len + ); + } else { + this.assert( + obj <= n + , 'expected #{this} to be at most ' + n + , 'expected #{this} to be above ' + n + ); + } + } + + Assertion.addMethod('most', assertMost); + Assertion.addMethod('lte', assertMost); + + /** + * ### .within(start, finish) + * + * Asserts that the target is within a range. + * + * expect(7).to.be.within(5,10); + * + * Can also be used in conjunction with `length` to + * assert a length range. The benefit being a + * more informative error message than if the length + * was supplied directly. + * + * expect('foo').to.have.length.within(2,4); + * expect([ 1, 2, 3 ]).to.have.length.within(2,4); + * + * @name within + * @param {Number} start lowerbound inclusive + * @param {Number} finish upperbound inclusive + * @param {String} message _optional_ + * @api public + */ + + Assertion.addMethod('within', function (start, finish, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object') + , range = start + '..' + finish; + if (flag(this, 'doLength')) { + new Assertion(obj, msg).to.have.property('length'); + var len = obj.length; + this.assert( + len >= start && len <= finish + , 'expected #{this} to have a length within ' + range + , 'expected #{this} to not have a length within ' + range + ); + } else { + this.assert( + obj >= start && obj <= finish + , 'expected #{this} to be within ' + range + , 'expected #{this} to not be within ' + range + ); + } + }); + + /** + * ### .instanceof(constructor) + * + * Asserts that the target is an instance of `constructor`. + * + * var Tea = function (name) { this.name = name; } + * , Chai = new Tea('chai'); + * + * expect(Chai).to.be.an.instanceof(Tea); + * expect([ 1, 2, 3 ]).to.be.instanceof(Array); + * + * @name instanceof + * @param {Constructor} constructor + * @param {String} message _optional_ + * @alias instanceOf + * @api public + */ + + function assertInstanceOf (constructor, msg) { + if (msg) flag(this, 'message', msg); + var name = _.getName(constructor); + this.assert( + flag(this, 'object') instanceof constructor + , 'expected #{this} to be an instance of ' + name + , 'expected #{this} to not be an instance of ' + name + ); + }; + + Assertion.addMethod('instanceof', assertInstanceOf); + Assertion.addMethod('instanceOf', assertInstanceOf); + + /** + * ### .property(name, [value]) + * + * Asserts that the target has a property `name`, optionally asserting that + * the value of that property is strictly equal to `value`. + * If the `deep` flag is set, you can use dot- and bracket-notation for deep + * references into objects and arrays. + * + * // simple referencing + * var obj = { foo: 'bar' }; + * expect(obj).to.have.property('foo'); + * expect(obj).to.have.property('foo', 'bar'); + * + * // deep referencing + * var deepObj = { + * green: { tea: 'matcha' } + * , teas: [ 'chai', 'matcha', { tea: 'konacha' } ] + * }; + + * expect(deepObj).to.have.deep.property('green.tea', 'matcha'); + * expect(deepObj).to.have.deep.property('teas[1]', 'matcha'); + * expect(deepObj).to.have.deep.property('teas[2].tea', 'konacha'); + * + * You can also use an array as the starting point of a `deep.property` + * assertion, or traverse nested arrays. + * + * var arr = [ + * [ 'chai', 'matcha', 'konacha' ] + * , [ { tea: 'chai' } + * , { tea: 'matcha' } + * , { tea: 'konacha' } ] + * ]; + * + * expect(arr).to.have.deep.property('[0][1]', 'matcha'); + * expect(arr).to.have.deep.property('[1][2].tea', 'konacha'); + * + * Furthermore, `property` changes the subject of the assertion + * to be the value of that property from the original object. This + * permits for further chainable assertions on that property. + * + * expect(obj).to.have.property('foo') + * .that.is.a('string'); + * expect(deepObj).to.have.property('green') + * .that.is.an('object') + * .that.deep.equals({ tea: 'matcha' }); + * expect(deepObj).to.have.property('teas') + * .that.is.an('array') + * .with.deep.property('[2]') + * .that.deep.equals({ tea: 'konacha' }); + * + * @name property + * @alias deep.property + * @param {String} name + * @param {Mixed} value (optional) + * @param {String} message _optional_ + * @returns value of property for chaining + * @api public + */ + + Assertion.addMethod('property', function (name, val, msg) { + if (msg) flag(this, 'message', msg); + + var isDeep = !!flag(this, 'deep') + , descriptor = isDeep ? 'deep property ' : 'property ' + , negate = flag(this, 'negate') + , obj = flag(this, 'object') + , pathInfo = isDeep ? _.getPathInfo(name, obj) : null + , hasProperty = isDeep + ? pathInfo.exists + : _.hasProperty(name, obj) + , value = isDeep + ? pathInfo.value + : obj[name]; + + if (negate && undefined !== val) { + if (undefined === value) { + msg = (msg != null) ? msg + ': ' : ''; + throw new Error(msg + _.inspect(obj) + ' has no ' + descriptor + _.inspect(name)); + } + } else { + this.assert( + hasProperty + , 'expected #{this} to have a ' + descriptor + _.inspect(name) + , 'expected #{this} to not have ' + descriptor + _.inspect(name)); + } + + if (undefined !== val) { + this.assert( + val === value + , 'expected #{this} to have a ' + descriptor + _.inspect(name) + ' of #{exp}, but got #{act}' + , 'expected #{this} to not have a ' + descriptor + _.inspect(name) + ' of #{act}' + , val + , value + ); + } + + flag(this, 'object', value); + }); + + + /** + * ### .ownProperty(name) + * + * Asserts that the target has an own property `name`. + * + * expect('test').to.have.ownProperty('length'); + * + * @name ownProperty + * @alias haveOwnProperty + * @param {String} name + * @param {String} message _optional_ + * @api public + */ + + function assertOwnProperty (name, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + this.assert( + obj.hasOwnProperty(name) + , 'expected #{this} to have own property ' + _.inspect(name) + , 'expected #{this} to not have own property ' + _.inspect(name) + ); + } + + Assertion.addMethod('ownProperty', assertOwnProperty); + Assertion.addMethod('haveOwnProperty', assertOwnProperty); + + /** + * ### .length(value) + * + * Asserts that the target's `length` property has + * the expected value. + * + * expect([ 1, 2, 3]).to.have.length(3); + * expect('foobar').to.have.length(6); + * + * Can also be used as a chain precursor to a value + * comparison for the length property. + * + * expect('foo').to.have.length.above(2); + * expect([ 1, 2, 3 ]).to.have.length.above(2); + * expect('foo').to.have.length.below(4); + * expect([ 1, 2, 3 ]).to.have.length.below(4); + * expect('foo').to.have.length.within(2,4); + * expect([ 1, 2, 3 ]).to.have.length.within(2,4); + * + * @name length + * @alias lengthOf + * @param {Number} length + * @param {String} message _optional_ + * @api public + */ + + function assertLengthChain () { + flag(this, 'doLength', true); + } + + function assertLength (n, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + new Assertion(obj, msg).to.have.property('length'); + var len = obj.length; + + this.assert( + len == n + , 'expected #{this} to have a length of #{exp} but got #{act}' + , 'expected #{this} to not have a length of #{act}' + , n + , len + ); + } + + Assertion.addChainableMethod('length', assertLength, assertLengthChain); + Assertion.addMethod('lengthOf', assertLength); + + /** + * ### .match(regexp) + * + * Asserts that the target matches a regular expression. + * + * expect('foobar').to.match(/^foo/); + * + * @name match + * @param {RegExp} RegularExpression + * @param {String} message _optional_ + * @api public + */ + + Assertion.addMethod('match', function (re, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + this.assert( + re.exec(obj) + , 'expected #{this} to match ' + re + , 'expected #{this} not to match ' + re + ); + }); + + /** + * ### .string(string) + * + * Asserts that the string target contains another string. + * + * expect('foobar').to.have.string('bar'); + * + * @name string + * @param {String} string + * @param {String} message _optional_ + * @api public + */ + + Assertion.addMethod('string', function (str, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + new Assertion(obj, msg).is.a('string'); + + this.assert( + ~obj.indexOf(str) + , 'expected #{this} to contain ' + _.inspect(str) + , 'expected #{this} to not contain ' + _.inspect(str) + ); + }); + + + /** + * ### .keys(key1, [key2], [...]) + * + * Asserts that the target contains any or all of the passed-in keys. + * Use in combination with `any`, `all`, `contains`, or `have` will affect + * what will pass. + * + * When used in conjunction with `any`, at least one key that is passed + * in must exist in the target object. This is regardless whether or not + * the `have` or `contain` qualifiers are used. Note, either `any` or `all` + * should be used in the assertion. If neither are used, the assertion is + * defaulted to `all`. + * + * When both `all` and `contain` are used, the target object must have at + * least all of the passed-in keys but may have more keys not listed. + * + * When both `all` and `have` are used, the target object must both contain + * all of the passed-in keys AND the number of keys in the target object must + * match the number of keys passed in (in other words, a target object must + * have all and only all of the passed-in keys). + * + * expect({ foo: 1, bar: 2 }).to.have.any.keys('foo', 'baz'); + * expect({ foo: 1, bar: 2 }).to.have.any.keys('foo'); + * expect({ foo: 1, bar: 2 }).to.contain.any.keys('bar', 'baz'); + * expect({ foo: 1, bar: 2 }).to.contain.any.keys(['foo']); + * expect({ foo: 1, bar: 2 }).to.contain.any.keys({'foo': 6}); + * expect({ foo: 1, bar: 2 }).to.have.all.keys(['bar', 'foo']); + * expect({ foo: 1, bar: 2 }).to.have.all.keys({'bar': 6, 'foo', 7}); + * expect({ foo: 1, bar: 2, baz: 3 }).to.contain.all.keys(['bar', 'foo']); + * expect({ foo: 1, bar: 2, baz: 3 }).to.contain.all.keys([{'bar': 6}}]); + * + * + * @name keys + * @alias key + * @param {String...|Array|Object} keys + * @api public + */ + + function assertKeys (keys) { + var obj = flag(this, 'object') + , str + , ok = true + , mixedArgsMsg = 'keys must be given single argument of Array|Object|String, or multiple String arguments'; + + switch (_.type(keys)) { + case "array": + if (arguments.length > 1) throw (new Error(mixedArgsMsg)); + break; + case "object": + if (arguments.length > 1) throw (new Error(mixedArgsMsg)); + keys = Object.keys(keys); + break; + default: + keys = Array.prototype.slice.call(arguments); + } + + if (!keys.length) throw new Error('keys required'); + + var actual = Object.keys(obj) + , expected = keys + , len = keys.length + , any = flag(this, 'any') + , all = flag(this, 'all'); + + if (!any && !all) { + all = true; + } + + // Has any + if (any) { + var intersection = expected.filter(function(key) { + return ~actual.indexOf(key); + }); + ok = intersection.length > 0; + } + + // Has all + if (all) { + ok = keys.every(function(key){ + return ~actual.indexOf(key); + }); + if (!flag(this, 'negate') && !flag(this, 'contains')) { + ok = ok && keys.length == actual.length; + } + } + + // Key string + if (len > 1) { + keys = keys.map(function(key){ + return _.inspect(key); + }); + var last = keys.pop(); + if (all) { + str = keys.join(', ') + ', and ' + last; + } + if (any) { + str = keys.join(', ') + ', or ' + last; + } + } else { + str = _.inspect(keys[0]); + } + + // Form + str = (len > 1 ? 'keys ' : 'key ') + str; + + // Have / include + str = (flag(this, 'contains') ? 'contain ' : 'have ') + str; + + // Assertion + this.assert( + ok + , 'expected #{this} to ' + str + , 'expected #{this} to not ' + str + , expected.slice(0).sort() + , actual.sort() + , true + ); + } + + Assertion.addMethod('keys', assertKeys); + Assertion.addMethod('key', assertKeys); + + /** + * ### .throw(constructor) + * + * Asserts that the function target will throw a specific error, or specific type of error + * (as determined using `instanceof`), optionally with a RegExp or string inclusion test + * for the error's message. + * + * var err = new ReferenceError('This is a bad function.'); + * var fn = function () { throw err; } + * expect(fn).to.throw(ReferenceError); + * expect(fn).to.throw(Error); + * expect(fn).to.throw(/bad function/); + * expect(fn).to.not.throw('good function'); + * expect(fn).to.throw(ReferenceError, /bad function/); + * expect(fn).to.throw(err); + * expect(fn).to.not.throw(new RangeError('Out of range.')); + * + * Please note that when a throw expectation is negated, it will check each + * parameter independently, starting with error constructor type. The appropriate way + * to check for the existence of a type of error but for a message that does not match + * is to use `and`. + * + * expect(fn).to.throw(ReferenceError) + * .and.not.throw(/good function/); + * + * @name throw + * @alias throws + * @alias Throw + * @param {ErrorConstructor} constructor + * @param {String|RegExp} expected error message + * @param {String} message _optional_ + * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types + * @returns error for chaining (null if no error) + * @api public + */ + + function assertThrows (constructor, errMsg, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + new Assertion(obj, msg).is.a('function'); + + var thrown = false + , desiredError = null + , name = null + , thrownError = null; + + if (arguments.length === 0) { + errMsg = null; + constructor = null; + } else if (constructor && (constructor instanceof RegExp || 'string' === typeof constructor)) { + errMsg = constructor; + constructor = null; + } else if (constructor && constructor instanceof Error) { + desiredError = constructor; + constructor = null; + errMsg = null; + } else if (typeof constructor === 'function') { + name = constructor.prototype.name || constructor.name; + if (name === 'Error' && constructor !== Error) { + name = (new constructor()).name; + } + } else { + constructor = null; + } + + try { + obj(); + } catch (err) { + // first, check desired error + if (desiredError) { + this.assert( + err === desiredError + , 'expected #{this} to throw #{exp} but #{act} was thrown' + , 'expected #{this} to not throw #{exp}' + , (desiredError instanceof Error ? desiredError.toString() : desiredError) + , (err instanceof Error ? err.toString() : err) + ); + + flag(this, 'object', err); + return this; + } + + // next, check constructor + if (constructor) { + this.assert( + err instanceof constructor + , 'expected #{this} to throw #{exp} but #{act} was thrown' + , 'expected #{this} to not throw #{exp} but #{act} was thrown' + , name + , (err instanceof Error ? err.toString() : err) + ); + + if (!errMsg) { + flag(this, 'object', err); + return this; + } + } + + // next, check message + var message = 'object' === _.type(err) && "message" in err + ? err.message + : '' + err; + + if ((message != null) && errMsg && errMsg instanceof RegExp) { + this.assert( + errMsg.exec(message) + , 'expected #{this} to throw error matching #{exp} but got #{act}' + , 'expected #{this} to throw error not matching #{exp}' + , errMsg + , message + ); + + flag(this, 'object', err); + return this; + } else if ((message != null) && errMsg && 'string' === typeof errMsg) { + this.assert( + ~message.indexOf(errMsg) + , 'expected #{this} to throw error including #{exp} but got #{act}' + , 'expected #{this} to throw error not including #{act}' + , errMsg + , message + ); + + flag(this, 'object', err); + return this; + } else { + thrown = true; + thrownError = err; + } + } + + var actuallyGot = '' + , expectedThrown = name !== null + ? name + : desiredError + ? '#{exp}' //_.inspect(desiredError) + : 'an error'; + + if (thrown) { + actuallyGot = ' but #{act} was thrown' + } + + this.assert( + thrown === true + , 'expected #{this} to throw ' + expectedThrown + actuallyGot + , 'expected #{this} to not throw ' + expectedThrown + actuallyGot + , (desiredError instanceof Error ? desiredError.toString() : desiredError) + , (thrownError instanceof Error ? thrownError.toString() : thrownError) + ); + + flag(this, 'object', thrownError); + }; + + Assertion.addMethod('throw', assertThrows); + Assertion.addMethod('throws', assertThrows); + Assertion.addMethod('Throw', assertThrows); + + /** + * ### .respondTo(method) + * + * Asserts that the object or class target will respond to a method. + * + * Klass.prototype.bar = function(){}; + * expect(Klass).to.respondTo('bar'); + * expect(obj).to.respondTo('bar'); + * + * To check if a constructor will respond to a static function, + * set the `itself` flag. + * + * Klass.baz = function(){}; + * expect(Klass).itself.to.respondTo('baz'); + * + * @name respondTo + * @param {String} method + * @param {String} message _optional_ + * @api public + */ + + Assertion.addMethod('respondTo', function (method, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object') + , itself = flag(this, 'itself') + , context = ('function' === _.type(obj) && !itself) + ? obj.prototype[method] + : obj[method]; + + this.assert( + 'function' === typeof context + , 'expected #{this} to respond to ' + _.inspect(method) + , 'expected #{this} to not respond to ' + _.inspect(method) + ); + }); + + /** + * ### .itself + * + * Sets the `itself` flag, later used by the `respondTo` assertion. + * + * function Foo() {} + * Foo.bar = function() {} + * Foo.prototype.baz = function() {} + * + * expect(Foo).itself.to.respondTo('bar'); + * expect(Foo).itself.not.to.respondTo('baz'); + * + * @name itself + * @api public + */ + + Assertion.addProperty('itself', function () { + flag(this, 'itself', true); + }); + + /** + * ### .satisfy(method) + * + * Asserts that the target passes a given truth test. + * + * expect(1).to.satisfy(function(num) { return num > 0; }); + * + * @name satisfy + * @param {Function} matcher + * @param {String} message _optional_ + * @api public + */ + + Assertion.addMethod('satisfy', function (matcher, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + var result = matcher(obj); + this.assert( + result + , 'expected #{this} to satisfy ' + _.objDisplay(matcher) + , 'expected #{this} to not satisfy' + _.objDisplay(matcher) + , this.negate ? false : true + , result + ); + }); + + /** + * ### .closeTo(expected, delta) + * + * Asserts that the target is equal `expected`, to within a +/- `delta` range. + * + * expect(1.5).to.be.closeTo(1, 0.5); + * + * @name closeTo + * @param {Number} expected + * @param {Number} delta + * @param {String} message _optional_ + * @api public + */ + + Assertion.addMethod('closeTo', function (expected, delta, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + + new Assertion(obj, msg).is.a('number'); + if (_.type(expected) !== 'number' || _.type(delta) !== 'number') { + throw new Error('the arguments to closeTo must be numbers'); + } + + this.assert( + Math.abs(obj - expected) <= delta + , 'expected #{this} to be close to ' + expected + ' +/- ' + delta + , 'expected #{this} not to be close to ' + expected + ' +/- ' + delta + ); + }); + + function isSubsetOf(subset, superset, cmp) { + return subset.every(function(elem) { + if (!cmp) return superset.indexOf(elem) !== -1; + + return superset.some(function(elem2) { + return cmp(elem, elem2); + }); + }) + } + + /** + * ### .members(set) + * + * Asserts that the target is a superset of `set`, + * or that the target and `set` have the same strictly-equal (===) members. + * Alternately, if the `deep` flag is set, set members are compared for deep + * equality. + * + * expect([1, 2, 3]).to.include.members([3, 2]); + * expect([1, 2, 3]).to.not.include.members([3, 2, 8]); + * + * expect([4, 2]).to.have.members([2, 4]); + * expect([5, 2]).to.not.have.members([5, 2, 1]); + * + * expect([{ id: 1 }]).to.deep.include.members([{ id: 1 }]); + * + * @name members + * @param {Array} set + * @param {String} message _optional_ + * @api public + */ + + Assertion.addMethod('members', function (subset, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + + new Assertion(obj).to.be.an('array'); + new Assertion(subset).to.be.an('array'); + + var cmp = flag(this, 'deep') ? _.eql : undefined; + + if (flag(this, 'contains')) { + return this.assert( + isSubsetOf(subset, obj, cmp) + , 'expected #{this} to be a superset of #{act}' + , 'expected #{this} to not be a superset of #{act}' + , obj + , subset + ); + } + + this.assert( + isSubsetOf(obj, subset, cmp) && isSubsetOf(subset, obj, cmp) + , 'expected #{this} to have the same members as #{act}' + , 'expected #{this} to not have the same members as #{act}' + , obj + , subset + ); + }); + + /** + * ### .change(function) + * + * Asserts that a function changes an object property + * + * var obj = { val: 10 }; + * var fn = function() { obj.val += 3 }; + * var noChangeFn = function() { return 'foo' + 'bar'; } + * expect(fn).to.change(obj, 'val'); + * expect(noChangFn).to.not.change(obj, 'val') + * + * @name change + * @alias changes + * @alias Change + * @param {String} object + * @param {String} property name + * @param {String} message _optional_ + * @api public + */ + + function assertChanges (object, prop, msg) { + if (msg) flag(this, 'message', msg); + var fn = flag(this, 'object'); + new Assertion(object, msg).to.have.property(prop); + new Assertion(fn).is.a('function'); + + var initial = object[prop]; + fn(); + + this.assert( + initial !== object[prop] + , 'expected .' + prop + ' to change' + , 'expected .' + prop + ' to not change' + ); + } + + Assertion.addChainableMethod('change', assertChanges); + Assertion.addChainableMethod('changes', assertChanges); + + /** + * ### .increase(function) + * + * Asserts that a function increases an object property + * + * var obj = { val: 10 }; + * var fn = function() { obj.val = 15 }; + * expect(fn).to.increase(obj, 'val'); + * + * @name increase + * @alias increases + * @alias Increase + * @param {String} object + * @param {String} property name + * @param {String} message _optional_ + * @api public + */ + + function assertIncreases (object, prop, msg) { + if (msg) flag(this, 'message', msg); + var fn = flag(this, 'object'); + new Assertion(object, msg).to.have.property(prop); + new Assertion(fn).is.a('function'); + + var initial = object[prop]; + fn(); + + this.assert( + object[prop] - initial > 0 + , 'expected .' + prop + ' to increase' + , 'expected .' + prop + ' to not increase' + ); + } + + Assertion.addChainableMethod('increase', assertIncreases); + Assertion.addChainableMethod('increases', assertIncreases); + + /** + * ### .decrease(function) + * + * Asserts that a function decreases an object property + * + * var obj = { val: 10 }; + * var fn = function() { obj.val = 5 }; + * expect(fn).to.decrease(obj, 'val'); + * + * @name decrease + * @alias decreases + * @alias Decrease + * @param {String} object + * @param {String} property name + * @param {String} message _optional_ + * @api public + */ + + function assertDecreases (object, prop, msg) { + if (msg) flag(this, 'message', msg); + var fn = flag(this, 'object'); + new Assertion(object, msg).to.have.property(prop); + new Assertion(fn).is.a('function'); + + var initial = object[prop]; + fn(); + + this.assert( + object[prop] - initial < 0 + , 'expected .' + prop + ' to decrease' + , 'expected .' + prop + ' to not decrease' + ); + } + + Assertion.addChainableMethod('decrease', assertDecreases); + Assertion.addChainableMethod('decreases', assertDecreases); + +}; + +}); + +require.register("chai/lib/chai/interface/assert.js", function (exports, module) { +/*! + * chai + * Copyright(c) 2011-2014 Jake Luer + * MIT Licensed + */ + + +module.exports = function (chai, util) { + + /*! + * Chai dependencies. + */ + + var Assertion = chai.Assertion + , flag = util.flag; + + /*! + * Module export. + */ + + /** + * ### assert(expression, message) + * + * Write your own test expressions. + * + * assert('foo' !== 'bar', 'foo is not bar'); + * assert(Array.isArray([]), 'empty arrays are arrays'); + * + * @param {Mixed} expression to test for truthiness + * @param {String} message to display on error + * @name assert + * @api public + */ + + var assert = chai.assert = function (express, errmsg) { + var test = new Assertion(null, null, chai.assert); + test.assert( + express + , errmsg + , '[ negation message unavailable ]' + ); + }; + + /** + * ### .fail(actual, expected, [message], [operator]) + * + * Throw a failure. Node.js `assert` module-compatible. + * + * @name fail + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @param {String} operator + * @api public + */ + + assert.fail = function (actual, expected, message, operator) { + message = message || 'assert.fail()'; + throw new chai.AssertionError(message, { + actual: actual + , expected: expected + , operator: operator + }, assert.fail); + }; + + /** + * ### .ok(object, [message]) + * + * Asserts that `object` is truthy. + * + * assert.ok('everything', 'everything is ok'); + * assert.ok(false, 'this will fail'); + * + * @name ok + * @param {Mixed} object to test + * @param {String} message + * @api public + */ + + assert.ok = function (val, msg) { + new Assertion(val, msg).is.ok; + }; + + /** + * ### .notOk(object, [message]) + * + * Asserts that `object` is falsy. + * + * assert.notOk('everything', 'this will fail'); + * assert.notOk(false, 'this will pass'); + * + * @name notOk + * @param {Mixed} object to test + * @param {String} message + * @api public + */ + + assert.notOk = function (val, msg) { + new Assertion(val, msg).is.not.ok; + }; + + /** + * ### .equal(actual, expected, [message]) + * + * Asserts non-strict equality (`==`) of `actual` and `expected`. + * + * assert.equal(3, '3', '== coerces values to strings'); + * + * @name equal + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @api public + */ + + assert.equal = function (act, exp, msg) { + var test = new Assertion(act, msg, assert.equal); + + test.assert( + exp == flag(test, 'object') + , 'expected #{this} to equal #{exp}' + , 'expected #{this} to not equal #{act}' + , exp + , act + ); + }; + + /** + * ### .notEqual(actual, expected, [message]) + * + * Asserts non-strict inequality (`!=`) of `actual` and `expected`. + * + * assert.notEqual(3, 4, 'these numbers are not equal'); + * + * @name notEqual + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @api public + */ + + assert.notEqual = function (act, exp, msg) { + var test = new Assertion(act, msg, assert.notEqual); + + test.assert( + exp != flag(test, 'object') + , 'expected #{this} to not equal #{exp}' + , 'expected #{this} to equal #{act}' + , exp + , act + ); + }; + + /** + * ### .strictEqual(actual, expected, [message]) + * + * Asserts strict equality (`===`) of `actual` and `expected`. + * + * assert.strictEqual(true, true, 'these booleans are strictly equal'); + * + * @name strictEqual + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @api public + */ + + assert.strictEqual = function (act, exp, msg) { + new Assertion(act, msg).to.equal(exp); + }; + + /** + * ### .notStrictEqual(actual, expected, [message]) + * + * Asserts strict inequality (`!==`) of `actual` and `expected`. + * + * assert.notStrictEqual(3, '3', 'no coercion for strict equality'); + * + * @name notStrictEqual + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @api public + */ + + assert.notStrictEqual = function (act, exp, msg) { + new Assertion(act, msg).to.not.equal(exp); + }; + + /** + * ### .deepEqual(actual, expected, [message]) + * + * Asserts that `actual` is deeply equal to `expected`. + * + * assert.deepEqual({ tea: 'green' }, { tea: 'green' }); + * + * @name deepEqual + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @api public + */ + + assert.deepEqual = function (act, exp, msg) { + new Assertion(act, msg).to.eql(exp); + }; + + /** + * ### .notDeepEqual(actual, expected, [message]) + * + * Assert that `actual` is not deeply equal to `expected`. + * + * assert.notDeepEqual({ tea: 'green' }, { tea: 'jasmine' }); + * + * @name notDeepEqual + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @api public + */ + + assert.notDeepEqual = function (act, exp, msg) { + new Assertion(act, msg).to.not.eql(exp); + }; + + /** + * ### .isTrue(value, [message]) + * + * Asserts that `value` is true. + * + * var teaServed = true; + * assert.isTrue(teaServed, 'the tea has been served'); + * + * @name isTrue + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isAbove = function (val, abv, msg) { + new Assertion(val, msg).to.be.above(abv); + }; + + /** + * ### .isAbove(valueToCheck, valueToBeAbove, [message]) + * + * Asserts `valueToCheck` is strictly greater than (>) `valueToBeAbove` + * + * assert.isAbove(5, 2, '5 is strictly greater than 2'); + * + * @name isAbove + * @param {Mixed} valueToCheck + * @param {Mixed} valueToBeAbove + * @param {String} message + * @api public + */ + + assert.isBelow = function (val, blw, msg) { + new Assertion(val, msg).to.be.below(blw); + }; + + /** + * ### .isBelow(valueToCheck, valueToBeBelow, [message]) + * + * Asserts `valueToCheck` is strictly less than (<) `valueToBeBelow` + * + * assert.isBelow(3, 6, '3 is strictly less than 6'); + * + * @name isBelow + * @param {Mixed} valueToCheck + * @param {Mixed} valueToBeBelow + * @param {String} message + * @api public + */ + + assert.isTrue = function (val, msg) { + new Assertion(val, msg).is['true']; + }; + + /** + * ### .isFalse(value, [message]) + * + * Asserts that `value` is false. + * + * var teaServed = false; + * assert.isFalse(teaServed, 'no tea yet? hmm...'); + * + * @name isFalse + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isFalse = function (val, msg) { + new Assertion(val, msg).is['false']; + }; + + /** + * ### .isNull(value, [message]) + * + * Asserts that `value` is null. + * + * assert.isNull(err, 'there was no error'); + * + * @name isNull + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isNull = function (val, msg) { + new Assertion(val, msg).to.equal(null); + }; + + /** + * ### .isNotNull(value, [message]) + * + * Asserts that `value` is not null. + * + * var tea = 'tasty chai'; + * assert.isNotNull(tea, 'great, time for tea!'); + * + * @name isNotNull + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isNotNull = function (val, msg) { + new Assertion(val, msg).to.not.equal(null); + }; + + /** + * ### .isUndefined(value, [message]) + * + * Asserts that `value` is `undefined`. + * + * var tea; + * assert.isUndefined(tea, 'no tea defined'); + * + * @name isUndefined + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isUndefined = function (val, msg) { + new Assertion(val, msg).to.equal(undefined); + }; + + /** + * ### .isDefined(value, [message]) + * + * Asserts that `value` is not `undefined`. + * + * var tea = 'cup of chai'; + * assert.isDefined(tea, 'tea has been defined'); + * + * @name isDefined + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isDefined = function (val, msg) { + new Assertion(val, msg).to.not.equal(undefined); + }; + + /** + * ### .isFunction(value, [message]) + * + * Asserts that `value` is a function. + * + * function serveTea() { return 'cup of tea'; }; + * assert.isFunction(serveTea, 'great, we can have tea now'); + * + * @name isFunction + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isFunction = function (val, msg) { + new Assertion(val, msg).to.be.a('function'); + }; + + /** + * ### .isNotFunction(value, [message]) + * + * Asserts that `value` is _not_ a function. + * + * var serveTea = [ 'heat', 'pour', 'sip' ]; + * assert.isNotFunction(serveTea, 'great, we have listed the steps'); + * + * @name isNotFunction + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isNotFunction = function (val, msg) { + new Assertion(val, msg).to.not.be.a('function'); + }; + + /** + * ### .isObject(value, [message]) + * + * Asserts that `value` is an object (as revealed by + * `Object.prototype.toString`). + * + * var selection = { name: 'Chai', serve: 'with spices' }; + * assert.isObject(selection, 'tea selection is an object'); + * + * @name isObject + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isObject = function (val, msg) { + new Assertion(val, msg).to.be.a('object'); + }; + + /** + * ### .isNotObject(value, [message]) + * + * Asserts that `value` is _not_ an object. + * + * var selection = 'chai' + * assert.isNotObject(selection, 'tea selection is not an object'); + * assert.isNotObject(null, 'null is not an object'); + * + * @name isNotObject + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isNotObject = function (val, msg) { + new Assertion(val, msg).to.not.be.a('object'); + }; + + /** + * ### .isArray(value, [message]) + * + * Asserts that `value` is an array. + * + * var menu = [ 'green', 'chai', 'oolong' ]; + * assert.isArray(menu, 'what kind of tea do we want?'); + * + * @name isArray + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isArray = function (val, msg) { + new Assertion(val, msg).to.be.an('array'); + }; + + /** + * ### .isNotArray(value, [message]) + * + * Asserts that `value` is _not_ an array. + * + * var menu = 'green|chai|oolong'; + * assert.isNotArray(menu, 'what kind of tea do we want?'); + * + * @name isNotArray + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isNotArray = function (val, msg) { + new Assertion(val, msg).to.not.be.an('array'); + }; + + /** + * ### .isString(value, [message]) + * + * Asserts that `value` is a string. + * + * var teaOrder = 'chai'; + * assert.isString(teaOrder, 'order placed'); + * + * @name isString + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isString = function (val, msg) { + new Assertion(val, msg).to.be.a('string'); + }; + + /** + * ### .isNotString(value, [message]) + * + * Asserts that `value` is _not_ a string. + * + * var teaOrder = 4; + * assert.isNotString(teaOrder, 'order placed'); + * + * @name isNotString + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isNotString = function (val, msg) { + new Assertion(val, msg).to.not.be.a('string'); + }; + + /** + * ### .isNumber(value, [message]) + * + * Asserts that `value` is a number. + * + * var cups = 2; + * assert.isNumber(cups, 'how many cups'); + * + * @name isNumber + * @param {Number} value + * @param {String} message + * @api public + */ + + assert.isNumber = function (val, msg) { + new Assertion(val, msg).to.be.a('number'); + }; + + /** + * ### .isNotNumber(value, [message]) + * + * Asserts that `value` is _not_ a number. + * + * var cups = '2 cups please'; + * assert.isNotNumber(cups, 'how many cups'); + * + * @name isNotNumber + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isNotNumber = function (val, msg) { + new Assertion(val, msg).to.not.be.a('number'); + }; + + /** + * ### .isBoolean(value, [message]) + * + * Asserts that `value` is a boolean. + * + * var teaReady = true + * , teaServed = false; + * + * assert.isBoolean(teaReady, 'is the tea ready'); + * assert.isBoolean(teaServed, 'has tea been served'); + * + * @name isBoolean + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isBoolean = function (val, msg) { + new Assertion(val, msg).to.be.a('boolean'); + }; + + /** + * ### .isNotBoolean(value, [message]) + * + * Asserts that `value` is _not_ a boolean. + * + * var teaReady = 'yep' + * , teaServed = 'nope'; + * + * assert.isNotBoolean(teaReady, 'is the tea ready'); + * assert.isNotBoolean(teaServed, 'has tea been served'); + * + * @name isNotBoolean + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isNotBoolean = function (val, msg) { + new Assertion(val, msg).to.not.be.a('boolean'); + }; + + /** + * ### .typeOf(value, name, [message]) + * + * Asserts that `value`'s type is `name`, as determined by + * `Object.prototype.toString`. + * + * assert.typeOf({ tea: 'chai' }, 'object', 'we have an object'); + * assert.typeOf(['chai', 'jasmine'], 'array', 'we have an array'); + * assert.typeOf('tea', 'string', 'we have a string'); + * assert.typeOf(/tea/, 'regexp', 'we have a regular expression'); + * assert.typeOf(null, 'null', 'we have a null'); + * assert.typeOf(undefined, 'undefined', 'we have an undefined'); + * + * @name typeOf + * @param {Mixed} value + * @param {String} name + * @param {String} message + * @api public + */ + + assert.typeOf = function (val, type, msg) { + new Assertion(val, msg).to.be.a(type); + }; + + /** + * ### .notTypeOf(value, name, [message]) + * + * Asserts that `value`'s type is _not_ `name`, as determined by + * `Object.prototype.toString`. + * + * assert.notTypeOf('tea', 'number', 'strings are not numbers'); + * + * @name notTypeOf + * @param {Mixed} value + * @param {String} typeof name + * @param {String} message + * @api public + */ + + assert.notTypeOf = function (val, type, msg) { + new Assertion(val, msg).to.not.be.a(type); + }; + + /** + * ### .instanceOf(object, constructor, [message]) + * + * Asserts that `value` is an instance of `constructor`. + * + * var Tea = function (name) { this.name = name; } + * , chai = new Tea('chai'); + * + * assert.instanceOf(chai, Tea, 'chai is an instance of tea'); + * + * @name instanceOf + * @param {Object} object + * @param {Constructor} constructor + * @param {String} message + * @api public + */ + + assert.instanceOf = function (val, type, msg) { + new Assertion(val, msg).to.be.instanceOf(type); + }; + + /** + * ### .notInstanceOf(object, constructor, [message]) + * + * Asserts `value` is not an instance of `constructor`. + * + * var Tea = function (name) { this.name = name; } + * , chai = new String('chai'); + * + * assert.notInstanceOf(chai, Tea, 'chai is not an instance of tea'); + * + * @name notInstanceOf + * @param {Object} object + * @param {Constructor} constructor + * @param {String} message + * @api public + */ + + assert.notInstanceOf = function (val, type, msg) { + new Assertion(val, msg).to.not.be.instanceOf(type); + }; + + /** + * ### .include(haystack, needle, [message]) + * + * Asserts that `haystack` includes `needle`. Works + * for strings and arrays. + * + * assert.include('foobar', 'bar', 'foobar contains string "bar"'); + * assert.include([ 1, 2, 3 ], 3, 'array contains value'); + * + * @name include + * @param {Array|String} haystack + * @param {Mixed} needle + * @param {String} message + * @api public + */ + + assert.include = function (exp, inc, msg) { + new Assertion(exp, msg, assert.include).include(inc); + }; + + /** + * ### .notInclude(haystack, needle, [message]) + * + * Asserts that `haystack` does not include `needle`. Works + * for strings and arrays. + *i + * assert.notInclude('foobar', 'baz', 'string not include substring'); + * assert.notInclude([ 1, 2, 3 ], 4, 'array not include contain value'); + * + * @name notInclude + * @param {Array|String} haystack + * @param {Mixed} needle + * @param {String} message + * @api public + */ + + assert.notInclude = function (exp, inc, msg) { + new Assertion(exp, msg, assert.notInclude).not.include(inc); + }; + + /** + * ### .match(value, regexp, [message]) + * + * Asserts that `value` matches the regular expression `regexp`. + * + * assert.match('foobar', /^foo/, 'regexp matches'); + * + * @name match + * @param {Mixed} value + * @param {RegExp} regexp + * @param {String} message + * @api public + */ + + assert.match = function (exp, re, msg) { + new Assertion(exp, msg).to.match(re); + }; + + /** + * ### .notMatch(value, regexp, [message]) + * + * Asserts that `value` does not match the regular expression `regexp`. + * + * assert.notMatch('foobar', /^foo/, 'regexp does not match'); + * + * @name notMatch + * @param {Mixed} value + * @param {RegExp} regexp + * @param {String} message + * @api public + */ + + assert.notMatch = function (exp, re, msg) { + new Assertion(exp, msg).to.not.match(re); + }; + + /** + * ### .property(object, property, [message]) + * + * Asserts that `object` has a property named by `property`. + * + * assert.property({ tea: { green: 'matcha' }}, 'tea'); + * + * @name property + * @param {Object} object + * @param {String} property + * @param {String} message + * @api public + */ + + assert.property = function (obj, prop, msg) { + new Assertion(obj, msg).to.have.property(prop); + }; + + /** + * ### .notProperty(object, property, [message]) + * + * Asserts that `object` does _not_ have a property named by `property`. + * + * assert.notProperty({ tea: { green: 'matcha' }}, 'coffee'); + * + * @name notProperty + * @param {Object} object + * @param {String} property + * @param {String} message + * @api public + */ + + assert.notProperty = function (obj, prop, msg) { + new Assertion(obj, msg).to.not.have.property(prop); + }; + + /** + * ### .deepProperty(object, property, [message]) + * + * Asserts that `object` has a property named by `property`, which can be a + * string using dot- and bracket-notation for deep reference. + * + * assert.deepProperty({ tea: { green: 'matcha' }}, 'tea.green'); + * + * @name deepProperty + * @param {Object} object + * @param {String} property + * @param {String} message + * @api public + */ + + assert.deepProperty = function (obj, prop, msg) { + new Assertion(obj, msg).to.have.deep.property(prop); + }; + + /** + * ### .notDeepProperty(object, property, [message]) + * + * Asserts that `object` does _not_ have a property named by `property`, which + * can be a string using dot- and bracket-notation for deep reference. + * + * assert.notDeepProperty({ tea: { green: 'matcha' }}, 'tea.oolong'); + * + * @name notDeepProperty + * @param {Object} object + * @param {String} property + * @param {String} message + * @api public + */ + + assert.notDeepProperty = function (obj, prop, msg) { + new Assertion(obj, msg).to.not.have.deep.property(prop); + }; + + /** + * ### .propertyVal(object, property, value, [message]) + * + * Asserts that `object` has a property named by `property` with value given + * by `value`. + * + * assert.propertyVal({ tea: 'is good' }, 'tea', 'is good'); + * + * @name propertyVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.propertyVal = function (obj, prop, val, msg) { + new Assertion(obj, msg).to.have.property(prop, val); + }; + + /** + * ### .propertyNotVal(object, property, value, [message]) + * + * Asserts that `object` has a property named by `property`, but with a value + * different from that given by `value`. + * + * assert.propertyNotVal({ tea: 'is good' }, 'tea', 'is bad'); + * + * @name propertyNotVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.propertyNotVal = function (obj, prop, val, msg) { + new Assertion(obj, msg).to.not.have.property(prop, val); + }; + + /** + * ### .deepPropertyVal(object, property, value, [message]) + * + * Asserts that `object` has a property named by `property` with value given + * by `value`. `property` can use dot- and bracket-notation for deep + * reference. + * + * assert.deepPropertyVal({ tea: { green: 'matcha' }}, 'tea.green', 'matcha'); + * + * @name deepPropertyVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.deepPropertyVal = function (obj, prop, val, msg) { + new Assertion(obj, msg).to.have.deep.property(prop, val); + }; + + /** + * ### .deepPropertyNotVal(object, property, value, [message]) + * + * Asserts that `object` has a property named by `property`, but with a value + * different from that given by `value`. `property` can use dot- and + * bracket-notation for deep reference. + * + * assert.deepPropertyNotVal({ tea: { green: 'matcha' }}, 'tea.green', 'konacha'); + * + * @name deepPropertyNotVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.deepPropertyNotVal = function (obj, prop, val, msg) { + new Assertion(obj, msg).to.not.have.deep.property(prop, val); + }; + + /** + * ### .lengthOf(object, length, [message]) + * + * Asserts that `object` has a `length` property with the expected value. + * + * assert.lengthOf([1,2,3], 3, 'array has length of 3'); + * assert.lengthOf('foobar', 5, 'string has length of 6'); + * + * @name lengthOf + * @param {Mixed} object + * @param {Number} length + * @param {String} message + * @api public + */ + + assert.lengthOf = function (exp, len, msg) { + new Assertion(exp, msg).to.have.length(len); + }; + + /** + * ### .throws(function, [constructor/string/regexp], [string/regexp], [message]) + * + * Asserts that `function` will throw an error that is an instance of + * `constructor`, or alternately that it will throw an error with message + * matching `regexp`. + * + * assert.throw(fn, 'function throws a reference error'); + * assert.throw(fn, /function throws a reference error/); + * assert.throw(fn, ReferenceError); + * assert.throw(fn, ReferenceError, 'function throws a reference error'); + * assert.throw(fn, ReferenceError, /function throws a reference error/); + * + * @name throws + * @alias throw + * @alias Throw + * @param {Function} function + * @param {ErrorConstructor} constructor + * @param {RegExp} regexp + * @param {String} message + * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types + * @api public + */ + + assert.Throw = function (fn, errt, errs, msg) { + if ('string' === typeof errt || errt instanceof RegExp) { + errs = errt; + errt = null; + } + + var assertErr = new Assertion(fn, msg).to.Throw(errt, errs); + return flag(assertErr, 'object'); + }; + + /** + * ### .doesNotThrow(function, [constructor/regexp], [message]) + * + * Asserts that `function` will _not_ throw an error that is an instance of + * `constructor`, or alternately that it will not throw an error with message + * matching `regexp`. + * + * assert.doesNotThrow(fn, Error, 'function does not throw'); + * + * @name doesNotThrow + * @param {Function} function + * @param {ErrorConstructor} constructor + * @param {RegExp} regexp + * @param {String} message + * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types + * @api public + */ + + assert.doesNotThrow = function (fn, type, msg) { + if ('string' === typeof type) { + msg = type; + type = null; + } + + new Assertion(fn, msg).to.not.Throw(type); + }; + + /** + * ### .operator(val1, operator, val2, [message]) + * + * Compares two values using `operator`. + * + * assert.operator(1, '<', 2, 'everything is ok'); + * assert.operator(1, '>', 2, 'this will fail'); + * + * @name operator + * @param {Mixed} val1 + * @param {String} operator + * @param {Mixed} val2 + * @param {String} message + * @api public + */ + + assert.operator = function (val, operator, val2, msg) { + if (!~['==', '===', '>', '>=', '<', '<=', '!=', '!=='].indexOf(operator)) { + throw new Error('Invalid operator "' + operator + '"'); + } + var test = new Assertion(eval(val + operator + val2), msg); + test.assert( + true === flag(test, 'object') + , 'expected ' + util.inspect(val) + ' to be ' + operator + ' ' + util.inspect(val2) + , 'expected ' + util.inspect(val) + ' to not be ' + operator + ' ' + util.inspect(val2) ); + }; + + /** + * ### .closeTo(actual, expected, delta, [message]) + * + * Asserts that the target is equal `expected`, to within a +/- `delta` range. + * + * assert.closeTo(1.5, 1, 0.5, 'numbers are close'); + * + * @name closeTo + * @param {Number} actual + * @param {Number} expected + * @param {Number} delta + * @param {String} message + * @api public + */ + + assert.closeTo = function (act, exp, delta, msg) { + new Assertion(act, msg).to.be.closeTo(exp, delta); + }; + + /** + * ### .sameMembers(set1, set2, [message]) + * + * Asserts that `set1` and `set2` have the same members. + * Order is not taken into account. + * + * assert.sameMembers([ 1, 2, 3 ], [ 2, 1, 3 ], 'same members'); + * + * @name sameMembers + * @param {Array} set1 + * @param {Array} set2 + * @param {String} message + * @api public + */ + + assert.sameMembers = function (set1, set2, msg) { + new Assertion(set1, msg).to.have.same.members(set2); + } + + /** + * ### .sameDeepMembers(set1, set2, [message]) + * + * Asserts that `set1` and `set2` have the same members - using a deep equality checking. + * Order is not taken into account. + * + * assert.sameDeepMembers([ {b: 3}, {a: 2}, {c: 5} ], [ {c: 5}, {b: 3}, {a: 2} ], 'same deep members'); + * + * @name sameDeepMembers + * @param {Array} set1 + * @param {Array} set2 + * @param {String} message + * @api public + */ + + assert.sameDeepMembers = function (set1, set2, msg) { + new Assertion(set1, msg).to.have.same.deep.members(set2); + } + + /** + * ### .includeMembers(superset, subset, [message]) + * + * Asserts that `subset` is included in `superset`. + * Order is not taken into account. + * + * assert.includeMembers([ 1, 2, 3 ], [ 2, 1 ], 'include members'); + * + * @name includeMembers + * @param {Array} superset + * @param {Array} subset + * @param {String} message + * @api public + */ + + assert.includeMembers = function (superset, subset, msg) { + new Assertion(superset, msg).to.include.members(subset); + } + + /** + * ### .changes(function, object, property) + * + * Asserts that a function changes the value of a property + * + * var obj = { val: 10 }; + * var fn = function() { obj.val = 22 }; + * assert.changes(fn, obj, 'val'); + * + * @name changes + * @param {Function} modifier function + * @param {Object} object + * @param {String} property name + * @param {String} message _optional_ + * @api public + */ + + assert.changes = function (fn, obj, prop) { + new Assertion(fn).to.change(obj, prop); + } + + /** + * ### .doesNotChange(function, object, property) + * + * Asserts that a function does not changes the value of a property + * + * var obj = { val: 10 }; + * var fn = function() { console.log('foo'); }; + * assert.doesNotChange(fn, obj, 'val'); + * + * @name doesNotChange + * @param {Function} modifier function + * @param {Object} object + * @param {String} property name + * @param {String} message _optional_ + * @api public + */ + + assert.doesNotChange = function (fn, obj, prop) { + new Assertion(fn).to.not.change(obj, prop); + } + + /** + * ### .increases(function, object, property) + * + * Asserts that a function increases an object property + * + * var obj = { val: 10 }; + * var fn = function() { obj.val = 13 }; + * assert.increases(fn, obj, 'val'); + * + * @name increases + * @param {Function} modifier function + * @param {Object} object + * @param {String} property name + * @param {String} message _optional_ + * @api public + */ + + assert.increases = function (fn, obj, prop) { + new Assertion(fn).to.increase(obj, prop); + } + + /** + * ### .doesNotIncrease(function, object, property) + * + * Asserts that a function does not increase object property + * + * var obj = { val: 10 }; + * var fn = function() { obj.val = 8 }; + * assert.doesNotIncrease(fn, obj, 'val'); + * + * @name doesNotIncrease + * @param {Function} modifier function + * @param {Object} object + * @param {String} property name + * @param {String} message _optional_ + * @api public + */ + + assert.doesNotIncrease = function (fn, obj, prop) { + new Assertion(fn).to.not.increase(obj, prop); + } + + /** + * ### .decreases(function, object, property) + * + * Asserts that a function decreases an object property + * + * var obj = { val: 10 }; + * var fn = function() { obj.val = 5 }; + * assert.decreases(fn, obj, 'val'); + * + * @name decreases + * @param {Function} modifier function + * @param {Object} object + * @param {String} property name + * @param {String} message _optional_ + * @api public + */ + + assert.decreases = function (fn, obj, prop) { + new Assertion(fn).to.decrease(obj, prop); + } + + /** + * ### .doesNotDecrease(function, object, property) + * + * Asserts that a function does not decreases an object property + * + * var obj = { val: 10 }; + * var fn = function() { obj.val = 15 }; + * assert.doesNotDecrease(fn, obj, 'val'); + * + * @name doesNotDecrease + * @param {Function} modifier function + * @param {Object} object + * @param {String} property name + * @param {String} message _optional_ + * @api public + */ + + assert.doesNotDecrease = function (fn, obj, prop) { + new Assertion(fn).to.not.decrease(obj, prop); + } + + /*! + * Undocumented / untested + */ + + assert.ifError = function (val, msg) { + new Assertion(val, msg).to.not.be.ok; + }; + + /*! + * Aliases. + */ + + (function alias(name, as){ + assert[as] = assert[name]; + return alias; + }) + ('Throw', 'throw') + ('Throw', 'throws'); +}; + +}); + +require.register("chai/lib/chai/interface/expect.js", function (exports, module) { +/*! + * chai + * Copyright(c) 2011-2014 Jake Luer + * MIT Licensed + */ + +module.exports = function (chai, util) { + chai.expect = function (val, message) { + return new chai.Assertion(val, message); + }; + + /** + * ### .fail(actual, expected, [message], [operator]) + * + * Throw a failure. + * + * @name fail + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @param {String} operator + * @api public + */ + + chai.expect.fail = function (actual, expected, message, operator) { + message = message || 'expect.fail()'; + throw new chai.AssertionError(message, { + actual: actual + , expected: expected + , operator: operator + }, chai.expect.fail); + }; +}; + +}); + +require.register("chai/lib/chai/interface/should.js", function (exports, module) { +/*! + * chai + * Copyright(c) 2011-2014 Jake Luer + * MIT Licensed + */ + +module.exports = function (chai, util) { + var Assertion = chai.Assertion; + + function loadShould () { + // explicitly define this method as function as to have it's name to include as `ssfi` + function shouldGetter() { + if (this instanceof String || this instanceof Number) { + return new Assertion(this.constructor(this), null, shouldGetter); + } else if (this instanceof Boolean) { + return new Assertion(this == true, null, shouldGetter); + } + return new Assertion(this, null, shouldGetter); + } + function shouldSetter(value) { + // See https://github.com/chaijs/chai/issues/86: this makes + // `whatever.should = someValue` actually set `someValue`, which is + // especially useful for `global.should = require('chai').should()`. + // + // Note that we have to use [[DefineProperty]] instead of [[Put]] + // since otherwise we would trigger this very setter! + Object.defineProperty(this, 'should', { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } + // modify Object.prototype to have `should` + Object.defineProperty(Object.prototype, 'should', { + set: shouldSetter + , get: shouldGetter + , configurable: true + }); + + var should = {}; + + /** + * ### .fail(actual, expected, [message], [operator]) + * + * Throw a failure. + * + * @name fail + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @param {String} operator + * @api public + */ + + should.fail = function (actual, expected, message, operator) { + message = message || 'should.fail()'; + throw new chai.AssertionError(message, { + actual: actual + , expected: expected + , operator: operator + }, should.fail); + }; + + should.equal = function (val1, val2, msg) { + new Assertion(val1, msg).to.equal(val2); + }; + + should.Throw = function (fn, errt, errs, msg) { + new Assertion(fn, msg).to.Throw(errt, errs); + }; + + should.exist = function (val, msg) { + new Assertion(val, msg).to.exist; + } + + // negation + should.not = {} + + should.not.equal = function (val1, val2, msg) { + new Assertion(val1, msg).to.not.equal(val2); + }; + + should.not.Throw = function (fn, errt, errs, msg) { + new Assertion(fn, msg).to.not.Throw(errt, errs); + }; + + should.not.exist = function (val, msg) { + new Assertion(val, msg).to.not.exist; + } + + should['throw'] = should['Throw']; + should.not['throw'] = should.not['Throw']; + + return should; + }; + + chai.should = loadShould; + chai.Should = loadShould; +}; + +}); + +require.register("chai/lib/chai/utils/addChainableMethod.js", function (exports, module) { +/*! + * Chai - addChainingMethod utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Module dependencies + */ + +var transferFlags = require('chai/lib/chai/utils/transferFlags.js'); +var flag = require('chai/lib/chai/utils/flag.js'); +var config = require('chai/lib/chai/config.js'); + +/*! + * Module variables + */ + +// Check whether `__proto__` is supported +var hasProtoSupport = '__proto__' in Object; + +// Without `__proto__` support, this module will need to add properties to a function. +// However, some Function.prototype methods cannot be overwritten, +// and there seems no easy cross-platform way to detect them (@see chaijs/chai/issues/69). +var excludeNames = /^(?:length|name|arguments|caller)$/; + +// Cache `Function` properties +var call = Function.prototype.call, + apply = Function.prototype.apply; + +/** + * ### addChainableMethod (ctx, name, method, chainingBehavior) + * + * Adds a method to an object, such that the method can also be chained. + * + * utils.addChainableMethod(chai.Assertion.prototype, 'foo', function (str) { + * var obj = utils.flag(this, 'object'); + * new chai.Assertion(obj).to.be.equal(str); + * }); + * + * Can also be accessed directly from `chai.Assertion`. + * + * chai.Assertion.addChainableMethod('foo', fn, chainingBehavior); + * + * The result can then be used as both a method assertion, executing both `method` and + * `chainingBehavior`, or as a language chain, which only executes `chainingBehavior`. + * + * expect(fooStr).to.be.foo('bar'); + * expect(fooStr).to.be.foo.equal('foo'); + * + * @param {Object} ctx object to which the method is added + * @param {String} name of method to add + * @param {Function} method function to be used for `name`, when called + * @param {Function} chainingBehavior function to be called every time the property is accessed + * @name addChainableMethod + * @api public + */ + +module.exports = function (ctx, name, method, chainingBehavior) { + if (typeof chainingBehavior !== 'function') { + chainingBehavior = function () { }; + } + + var chainableBehavior = { + method: method + , chainingBehavior: chainingBehavior + }; + + // save the methods so we can overwrite them later, if we need to. + if (!ctx.__methods) { + ctx.__methods = {}; + } + ctx.__methods[name] = chainableBehavior; + + Object.defineProperty(ctx, name, + { get: function () { + chainableBehavior.chainingBehavior.call(this); + + var assert = function assert() { + var old_ssfi = flag(this, 'ssfi'); + if (old_ssfi && config.includeStack === false) + flag(this, 'ssfi', assert); + var result = chainableBehavior.method.apply(this, arguments); + return result === undefined ? this : result; + }; + + // Use `__proto__` if available + if (hasProtoSupport) { + // Inherit all properties from the object by replacing the `Function` prototype + var prototype = assert.__proto__ = Object.create(this); + // Restore the `call` and `apply` methods from `Function` + prototype.call = call; + prototype.apply = apply; + } + // Otherwise, redefine all properties (slow!) + else { + var asserterNames = Object.getOwnPropertyNames(ctx); + asserterNames.forEach(function (asserterName) { + if (!excludeNames.test(asserterName)) { + var pd = Object.getOwnPropertyDescriptor(ctx, asserterName); + Object.defineProperty(assert, asserterName, pd); + } + }); + } + + transferFlags(this, assert); + return assert; + } + , configurable: true + }); +}; + +}); + +require.register("chai/lib/chai/utils/addMethod.js", function (exports, module) { +/*! + * Chai - addMethod utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +var config = require('chai/lib/chai/config.js'); + +/** + * ### .addMethod (ctx, name, method) + * + * Adds a method to the prototype of an object. + * + * utils.addMethod(chai.Assertion.prototype, 'foo', function (str) { + * var obj = utils.flag(this, 'object'); + * new chai.Assertion(obj).to.be.equal(str); + * }); + * + * Can also be accessed directly from `chai.Assertion`. + * + * chai.Assertion.addMethod('foo', fn); + * + * Then can be used as any other assertion. + * + * expect(fooStr).to.be.foo('bar'); + * + * @param {Object} ctx object to which the method is added + * @param {String} name of method to add + * @param {Function} method function to be used for name + * @name addMethod + * @api public + */ +var flag = require('chai/lib/chai/utils/flag.js'); + +module.exports = function (ctx, name, method) { + ctx[name] = function () { + var old_ssfi = flag(this, 'ssfi'); + if (old_ssfi && config.includeStack === false) + flag(this, 'ssfi', ctx[name]); + var result = method.apply(this, arguments); + return result === undefined ? this : result; + }; +}; + +}); + +require.register("chai/lib/chai/utils/addProperty.js", function (exports, module) { +/*! + * Chai - addProperty utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### addProperty (ctx, name, getter) + * + * Adds a property to the prototype of an object. + * + * utils.addProperty(chai.Assertion.prototype, 'foo', function () { + * var obj = utils.flag(this, 'object'); + * new chai.Assertion(obj).to.be.instanceof(Foo); + * }); + * + * Can also be accessed directly from `chai.Assertion`. + * + * chai.Assertion.addProperty('foo', fn); + * + * Then can be used as any other assertion. + * + * expect(myFoo).to.be.foo; + * + * @param {Object} ctx object to which the property is added + * @param {String} name of property to add + * @param {Function} getter function to be used for name + * @name addProperty + * @api public + */ + +module.exports = function (ctx, name, getter) { + Object.defineProperty(ctx, name, + { get: function () { + var result = getter.call(this); + return result === undefined ? this : result; + } + , configurable: true + }); +}; + +}); + +require.register("chai/lib/chai/utils/flag.js", function (exports, module) { +/*! + * Chai - flag utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### flag(object, key, [value]) + * + * Get or set a flag value on an object. If a + * value is provided it will be set, else it will + * return the currently set value or `undefined` if + * the value is not set. + * + * utils.flag(this, 'foo', 'bar'); // setter + * utils.flag(this, 'foo'); // getter, returns `bar` + * + * @param {Object} object constructed Assertion + * @param {String} key + * @param {Mixed} value (optional) + * @name flag + * @api private + */ + +module.exports = function (obj, key, value) { + var flags = obj.__flags || (obj.__flags = Object.create(null)); + if (arguments.length === 3) { + flags[key] = value; + } else { + return flags[key]; + } +}; + +}); + +require.register("chai/lib/chai/utils/getActual.js", function (exports, module) { +/*! + * Chai - getActual utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * # getActual(object, [actual]) + * + * Returns the `actual` value for an Assertion + * + * @param {Object} object (constructed Assertion) + * @param {Arguments} chai.Assertion.prototype.assert arguments + */ + +module.exports = function (obj, args) { + return args.length > 4 ? args[4] : obj._obj; +}; + +}); + +require.register("chai/lib/chai/utils/getEnumerableProperties.js", function (exports, module) { +/*! + * Chai - getEnumerableProperties utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### .getEnumerableProperties(object) + * + * This allows the retrieval of enumerable property names of an object, + * inherited or not. + * + * @param {Object} object + * @returns {Array} + * @name getEnumerableProperties + * @api public + */ + +module.exports = function getEnumerableProperties(object) { + var result = []; + for (var name in object) { + result.push(name); + } + return result; +}; + +}); + +require.register("chai/lib/chai/utils/getMessage.js", function (exports, module) { +/*! + * Chai - message composition utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Module dependancies + */ + +var flag = require('chai/lib/chai/utils/flag.js') + , getActual = require('chai/lib/chai/utils/getActual.js') + , inspect = require('chai/lib/chai/utils/inspect.js') + , objDisplay = require('chai/lib/chai/utils/objDisplay.js'); + +/** + * ### .getMessage(object, message, negateMessage) + * + * Construct the error message based on flags + * and template tags. Template tags will return + * a stringified inspection of the object referenced. + * + * Message template tags: + * - `#{this}` current asserted object + * - `#{act}` actual value + * - `#{exp}` expected value + * + * @param {Object} object (constructed Assertion) + * @param {Arguments} chai.Assertion.prototype.assert arguments + * @name getMessage + * @api public + */ + +module.exports = function (obj, args) { + var negate = flag(obj, 'negate') + , val = flag(obj, 'object') + , expected = args[3] + , actual = getActual(obj, args) + , msg = negate ? args[2] : args[1] + , flagMsg = flag(obj, 'message'); + + if(typeof msg === "function") msg = msg(); + msg = msg || ''; + msg = msg + .replace(/#{this}/g, objDisplay(val)) + .replace(/#{act}/g, objDisplay(actual)) + .replace(/#{exp}/g, objDisplay(expected)); + + return flagMsg ? flagMsg + ': ' + msg : msg; +}; + +}); + +require.register("chai/lib/chai/utils/getName.js", function (exports, module) { +/*! + * Chai - getName utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * # getName(func) + * + * Gets the name of a function, in a cross-browser way. + * + * @param {Function} a function (usually a constructor) + */ + +module.exports = function (func) { + if (func.name) return func.name; + + var match = /^\s?function ([^(]*)\(/.exec(func); + return match && match[1] ? match[1] : ""; +}; + +}); + +require.register("chai/lib/chai/utils/getPathValue.js", function (exports, module) { +/*! + * Chai - getPathValue utility + * Copyright(c) 2012-2014 Jake Luer + * @see https://github.com/logicalparadox/filtr + * MIT Licensed + */ + +var getPathInfo = require('chai/lib/chai/utils/getPathInfo.js'); + +/** + * ### .getPathValue(path, object) + * + * This allows the retrieval of values in an + * object given a string path. + * + * var obj = { + * prop1: { + * arr: ['a', 'b', 'c'] + * , str: 'Hello' + * } + * , prop2: { + * arr: [ { nested: 'Universe' } ] + * , str: 'Hello again!' + * } + * } + * + * The following would be the results. + * + * getPathValue('prop1.str', obj); // Hello + * getPathValue('prop1.att[2]', obj); // b + * getPathValue('prop2.arr[0].nested', obj); // Universe + * + * @param {String} path + * @param {Object} object + * @returns {Object} value or `undefined` + * @name getPathValue + * @api public + */ +module.exports = function(path, obj) { + var info = getPathInfo(path, obj); + return info.value; +}; + +}); + +require.register("chai/lib/chai/utils/getPathInfo.js", function (exports, module) { +/*! + * Chai - getPathInfo utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +var hasProperty = require('chai/lib/chai/utils/hasProperty.js'); + +/** + * ### .getPathInfo(path, object) + * + * This allows the retrieval of property info in an + * object given a string path. + * + * The path info consists of an object with the + * following properties: + * + * * parent - The parent object of the property referenced by `path` + * * name - The name of the final property, a number if it was an array indexer + * * value - The value of the property, if it exists, otherwise `undefined` + * * exists - Whether the property exists or not + * + * @param {String} path + * @param {Object} object + * @returns {Object} info + * @name getPathInfo + * @api public + */ + +module.exports = function getPathInfo(path, obj) { + var parsed = parsePath(path), + last = parsed[parsed.length - 1]; + + var info = { + parent: _getPathValue(parsed, obj, parsed.length - 1), + name: last.p || last.i, + value: _getPathValue(parsed, obj), + }; + info.exists = hasProperty(info.name, info.parent); + + return info; +}; + + +/*! + * ## parsePath(path) + * + * Helper function used to parse string object + * paths. Use in conjunction with `_getPathValue`. + * + * var parsed = parsePath('myobject.property.subprop'); + * + * ### Paths: + * + * * Can be as near infinitely deep and nested + * * Arrays are also valid using the formal `myobject.document[3].property`. + * + * @param {String} path + * @returns {Object} parsed + * @api private + */ + +function parsePath (path) { + var str = path.replace(/\[/g, '.[') + , parts = str.match(/(\\\.|[^.]+?)+/g); + return parts.map(function (value) { + var re = /\[(\d+)\]$/ + , mArr = re.exec(value); + if (mArr) return { i: parseFloat(mArr[1]) }; + else return { p: value }; + }); +} + + +/*! + * ## _getPathValue(parsed, obj) + * + * Helper companion function for `.parsePath` that returns + * the value located at the parsed address. + * + * var value = getPathValue(parsed, obj); + * + * @param {Object} parsed definition from `parsePath`. + * @param {Object} object to search against + * @param {Number} object to search against + * @returns {Object|Undefined} value + * @api private + */ + +function _getPathValue (parsed, obj, index) { + var tmp = obj + , res; + + index = (index === undefined ? parsed.length : index); + + for (var i = 0, l = index; i < l; i++) { + var part = parsed[i]; + if (tmp) { + if ('undefined' !== typeof part.p) + tmp = tmp[part.p]; + else if ('undefined' !== typeof part.i) + tmp = tmp[part.i]; + if (i == (l - 1)) res = tmp; + } else { + res = undefined; + } + } + return res; +} + +}); + +require.register("chai/lib/chai/utils/hasProperty.js", function (exports, module) { +/*! + * Chai - hasProperty utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +var type = require('chai/lib/chai/utils/type.js'); + +/** + * ### .hasProperty(object, name) + * + * This allows checking whether an object has + * named property or numeric array index. + * + * Basically does the same thing as the `in` + * operator but works properly with natives + * and null/undefined values. + * + * var obj = { + * arr: ['a', 'b', 'c'] + * , str: 'Hello' + * } + * + * The following would be the results. + * + * hasProperty('str', obj); // true + * hasProperty('constructor', obj); // true + * hasProperty('bar', obj); // false + * + * hasProperty('length', obj.str); // true + * hasProperty(1, obj.str); // true + * hasProperty(5, obj.str); // false + * + * hasProperty('length', obj.arr); // true + * hasProperty(2, obj.arr); // true + * hasProperty(3, obj.arr); // false + * + * @param {Objuect} object + * @param {String|Number} name + * @returns {Boolean} whether it exists + * @name getPathInfo + * @api public + */ + +var literals = { + 'number': Number + , 'string': String +}; + +module.exports = function hasProperty(name, obj) { + var ot = type(obj); + + // Bad Object, obviously no props at all + if(ot === 'null' || ot === 'undefined') + return false; + + // The `in` operator does not work with certain literals + // box these before the check + if(literals[ot] && typeof obj !== 'object') + obj = new literals[ot](obj); + + return name in obj; +}; + +}); + +require.register("chai/lib/chai/utils/getProperties.js", function (exports, module) { +/*! + * Chai - getProperties utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### .getProperties(object) + * + * This allows the retrieval of property names of an object, enumerable or not, + * inherited or not. + * + * @param {Object} object + * @returns {Array} + * @name getProperties + * @api public + */ + +module.exports = function getProperties(object) { + var result = Object.getOwnPropertyNames(subject); + + function addProperty(property) { + if (result.indexOf(property) === -1) { + result.push(property); + } + } + + var proto = Object.getPrototypeOf(subject); + while (proto !== null) { + Object.getOwnPropertyNames(proto).forEach(addProperty); + proto = Object.getPrototypeOf(proto); + } + + return result; +}; + +}); + +require.register("chai/lib/chai/utils/index.js", function (exports, module) { +/*! + * chai + * Copyright(c) 2011 Jake Luer + * MIT Licensed + */ + +/*! + * Main exports + */ + +var exports = module.exports = {}; + +/*! + * test utility + */ + +exports.test = require('chai/lib/chai/utils/test.js'); + +/*! + * type utility + */ + +exports.type = require('chai/lib/chai/utils/type.js'); + +/*! + * message utility + */ + +exports.getMessage = require('chai/lib/chai/utils/getMessage.js'); + +/*! + * actual utility + */ + +exports.getActual = require('chai/lib/chai/utils/getActual.js'); + +/*! + * Inspect util + */ + +exports.inspect = require('chai/lib/chai/utils/inspect.js'); + +/*! + * Object Display util + */ + +exports.objDisplay = require('chai/lib/chai/utils/objDisplay.js'); + +/*! + * Flag utility + */ + +exports.flag = require('chai/lib/chai/utils/flag.js'); + +/*! + * Flag transferring utility + */ + +exports.transferFlags = require('chai/lib/chai/utils/transferFlags.js'); + +/*! + * Deep equal utility + */ + +exports.eql = require('chaijs~deep-eql@0.1.3'); + +/*! + * Deep path value + */ + +exports.getPathValue = require('chai/lib/chai/utils/getPathValue.js'); + +/*! + * Deep path info + */ + +exports.getPathInfo = require('chai/lib/chai/utils/getPathInfo.js'); + +/*! + * Check if a property exists + */ + +exports.hasProperty = require('chai/lib/chai/utils/hasProperty.js'); + +/*! + * Function name + */ + +exports.getName = require('chai/lib/chai/utils/getName.js'); + +/*! + * add Property + */ + +exports.addProperty = require('chai/lib/chai/utils/addProperty.js'); + +/*! + * add Method + */ + +exports.addMethod = require('chai/lib/chai/utils/addMethod.js'); + +/*! + * overwrite Property + */ + +exports.overwriteProperty = require('chai/lib/chai/utils/overwriteProperty.js'); + +/*! + * overwrite Method + */ + +exports.overwriteMethod = require('chai/lib/chai/utils/overwriteMethod.js'); + +/*! + * Add a chainable method + */ + +exports.addChainableMethod = require('chai/lib/chai/utils/addChainableMethod.js'); + +/*! + * Overwrite chainable method + */ + +exports.overwriteChainableMethod = require('chai/lib/chai/utils/overwriteChainableMethod.js'); + + +}); + +require.register("chai/lib/chai/utils/inspect.js", function (exports, module) { +// This is (almost) directly from Node.js utils +// https://github.com/joyent/node/blob/f8c335d0caf47f16d31413f89aa28eda3878e3aa/lib/util.js + +var getName = require('chai/lib/chai/utils/getName.js'); +var getProperties = require('chai/lib/chai/utils/getProperties.js'); +var getEnumerableProperties = require('chai/lib/chai/utils/getEnumerableProperties.js'); + +module.exports = inspect; + +/** + * Echos the value of a value. Trys to print the value out + * in the best way possible given the different types. + * + * @param {Object} obj The object to print out. + * @param {Boolean} showHidden Flag that shows hidden (not enumerable) + * properties of objects. + * @param {Number} depth Depth in which to descend in object. Default is 2. + * @param {Boolean} colors Flag to turn on ANSI escape codes to color the + * output. Default is false (no coloring). + */ +function inspect(obj, showHidden, depth, colors) { + var ctx = { + showHidden: showHidden, + seen: [], + stylize: function (str) { return str; } + }; + return formatValue(ctx, obj, (typeof depth === 'undefined' ? 2 : depth)); +} + +// Returns true if object is a DOM element. +var isDOMElement = function (object) { + if (typeof HTMLElement === 'object') { + return object instanceof HTMLElement; + } else { + return object && + typeof object === 'object' && + object.nodeType === 1 && + typeof object.nodeName === 'string'; + } +}; + +function formatValue(ctx, value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if (value && typeof value.inspect === 'function' && + // Filter out the util module, it's inspect function is special + value.inspect !== exports.inspect && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + var ret = value.inspect(recurseTimes); + if (typeof ret !== 'string') { + ret = formatValue(ctx, ret, recurseTimes); + } + return ret; + } + + // Primitive types cannot have properties + var primitive = formatPrimitive(ctx, value); + if (primitive) { + return primitive; + } + + // If this is a DOM element, try to get the outer HTML. + if (isDOMElement(value)) { + if ('outerHTML' in value) { + return value.outerHTML; + // This value does not have an outerHTML attribute, + // it could still be an XML element + } else { + // Attempt to serialize it + try { + if (document.xmlVersion) { + var xmlSerializer = new XMLSerializer(); + return xmlSerializer.serializeToString(value); + } else { + // Firefox 11- do not support outerHTML + // It does, however, support innerHTML + // Use the following to render the element + var ns = "http://www.w3.org/1999/xhtml"; + var container = document.createElementNS(ns, '_'); + + container.appendChild(value.cloneNode(false)); + html = container.innerHTML + .replace('><', '>' + value.innerHTML + '<'); + container.innerHTML = ''; + return html; + } + } catch (err) { + // This could be a non-native DOM implementation, + // continue with the normal flow: + // printing the element as if it is an object. + } + } + } + + // Look up the keys of the object. + var visibleKeys = getEnumerableProperties(value); + var keys = ctx.showHidden ? getProperties(value) : visibleKeys; + + // Some type of object without properties can be shortcutted. + // In IE, errors have a single `stack` property, or if they are vanilla `Error`, + // a `stack` plus `description` property; ignore those for consistency. + if (keys.length === 0 || (isError(value) && ( + (keys.length === 1 && keys[0] === 'stack') || + (keys.length === 2 && keys[0] === 'description' && keys[1] === 'stack') + ))) { + if (typeof value === 'function') { + var name = getName(value); + var nameSuffix = name ? ': ' + name : ''; + return ctx.stylize('[Function' + nameSuffix + ']', 'special'); + } + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } + if (isDate(value)) { + return ctx.stylize(Date.prototype.toUTCString.call(value), 'date'); + } + if (isError(value)) { + return formatError(value); + } + } + + var base = '', array = false, braces = ['{', '}']; + + // Make Array say that they are Array + if (isArray(value)) { + array = true; + braces = ['[', ']']; + } + + // Make functions say that they are functions + if (typeof value === 'function') { + var name = getName(value); + var nameSuffix = name ? ': ' + name : ''; + base = ' [Function' + nameSuffix + ']'; + } + + // Make RegExps say that they are RegExps + if (isRegExp(value)) { + base = ' ' + RegExp.prototype.toString.call(value); + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + Date.prototype.toUTCString.call(value); + } + + // Make error with message first say the error + if (isError(value)) { + return formatError(value); + } + + if (keys.length === 0 && (!array || value.length == 0)) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } else { + return ctx.stylize('[Object]', 'special'); + } + } + + ctx.seen.push(value); + + var output; + if (array) { + output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); + } else { + output = keys.map(function(key) { + return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); + }); + } + + ctx.seen.pop(); + + return reduceToSingleString(output, base, braces); +} + + +function formatPrimitive(ctx, value) { + switch (typeof value) { + case 'undefined': + return ctx.stylize('undefined', 'undefined'); + + case 'string': + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return ctx.stylize(simple, 'string'); + + case 'number': + if (value === 0 && (1/value) === -Infinity) { + return ctx.stylize('-0', 'number'); + } + return ctx.stylize('' + value, 'number'); + + case 'boolean': + return ctx.stylize('' + value, 'boolean'); + } + // For some reason typeof null is "object", so special case here. + if (value === null) { + return ctx.stylize('null', 'null'); + } +} + + +function formatError(value) { + return '[' + Error.prototype.toString.call(value) + ']'; +} + + +function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { + var output = []; + for (var i = 0, l = value.length; i < l; ++i) { + if (Object.prototype.hasOwnProperty.call(value, String(i))) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + String(i), true)); + } else { + output.push(''); + } + } + keys.forEach(function(key) { + if (!key.match(/^\d+$/)) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + key, true)); + } + }); + return output; +} + + +function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { + var name, str; + if (value.__lookupGetter__) { + if (value.__lookupGetter__(key)) { + if (value.__lookupSetter__(key)) { + str = ctx.stylize('[Getter/Setter]', 'special'); + } else { + str = ctx.stylize('[Getter]', 'special'); + } + } else { + if (value.__lookupSetter__(key)) { + str = ctx.stylize('[Setter]', 'special'); + } + } + } + if (visibleKeys.indexOf(key) < 0) { + name = '[' + key + ']'; + } + if (!str) { + if (ctx.seen.indexOf(value[key]) < 0) { + if (recurseTimes === null) { + str = formatValue(ctx, value[key], null); + } else { + str = formatValue(ctx, value[key], recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (array) { + str = str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n').substr(2); + } else { + str = '\n' + str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n'); + } + } + } else { + str = ctx.stylize('[Circular]', 'special'); + } + } + if (typeof name === 'undefined') { + if (array && key.match(/^\d+$/)) { + return str; + } + name = JSON.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = ctx.stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = ctx.stylize(name, 'string'); + } + } + + return name + ': ' + str; +} + + +function reduceToSingleString(output, base, braces) { + var numLinesEst = 0; + var length = output.reduce(function(prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.length + 1; + }, 0); + + if (length > 60) { + return braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; + } + + return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; +} + +function isArray(ar) { + return Array.isArray(ar) || + (typeof ar === 'object' && objectToString(ar) === '[object Array]'); +} + +function isRegExp(re) { + return typeof re === 'object' && objectToString(re) === '[object RegExp]'; +} + +function isDate(d) { + return typeof d === 'object' && objectToString(d) === '[object Date]'; +} + +function isError(e) { + return typeof e === 'object' && objectToString(e) === '[object Error]'; +} + +function objectToString(o) { + return Object.prototype.toString.call(o); +} + +}); + +require.register("chai/lib/chai/utils/objDisplay.js", function (exports, module) { +/*! + * Chai - flag utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Module dependancies + */ + +var inspect = require('chai/lib/chai/utils/inspect.js'); +var config = require('chai/lib/chai/config.js'); + +/** + * ### .objDisplay (object) + * + * Determines if an object or an array matches + * criteria to be inspected in-line for error + * messages or should be truncated. + * + * @param {Mixed} javascript object to inspect + * @name objDisplay + * @api public + */ + +module.exports = function (obj) { + var str = inspect(obj) + , type = Object.prototype.toString.call(obj); + + if (config.truncateThreshold && str.length >= config.truncateThreshold) { + if (type === '[object Function]') { + return !obj.name || obj.name === '' + ? '[Function]' + : '[Function: ' + obj.name + ']'; + } else if (type === '[object Array]') { + return '[ Array(' + obj.length + ') ]'; + } else if (type === '[object Object]') { + var keys = Object.keys(obj) + , kstr = keys.length > 2 + ? keys.splice(0, 2).join(', ') + ', ...' + : keys.join(', '); + return '{ Object (' + kstr + ') }'; + } else { + return str; + } + } else { + return str; + } +}; + +}); + +require.register("chai/lib/chai/utils/overwriteMethod.js", function (exports, module) { +/*! + * Chai - overwriteMethod utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### overwriteMethod (ctx, name, fn) + * + * Overwites an already existing method and provides + * access to previous function. Must return function + * to be used for name. + * + * utils.overwriteMethod(chai.Assertion.prototype, 'equal', function (_super) { + * return function (str) { + * var obj = utils.flag(this, 'object'); + * if (obj instanceof Foo) { + * new chai.Assertion(obj.value).to.equal(str); + * } else { + * _super.apply(this, arguments); + * } + * } + * }); + * + * Can also be accessed directly from `chai.Assertion`. + * + * chai.Assertion.overwriteMethod('foo', fn); + * + * Then can be used as any other assertion. + * + * expect(myFoo).to.equal('bar'); + * + * @param {Object} ctx object whose method is to be overwritten + * @param {String} name of method to overwrite + * @param {Function} method function that returns a function to be used for name + * @name overwriteMethod + * @api public + */ + +module.exports = function (ctx, name, method) { + var _method = ctx[name] + , _super = function () { return this; }; + + if (_method && 'function' === typeof _method) + _super = _method; + + ctx[name] = function () { + var result = method(_super).apply(this, arguments); + return result === undefined ? this : result; + } +}; + +}); + +require.register("chai/lib/chai/utils/overwriteProperty.js", function (exports, module) { +/*! + * Chai - overwriteProperty utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### overwriteProperty (ctx, name, fn) + * + * Overwites an already existing property getter and provides + * access to previous value. Must return function to use as getter. + * + * utils.overwriteProperty(chai.Assertion.prototype, 'ok', function (_super) { + * return function () { + * var obj = utils.flag(this, 'object'); + * if (obj instanceof Foo) { + * new chai.Assertion(obj.name).to.equal('bar'); + * } else { + * _super.call(this); + * } + * } + * }); + * + * + * Can also be accessed directly from `chai.Assertion`. + * + * chai.Assertion.overwriteProperty('foo', fn); + * + * Then can be used as any other assertion. + * + * expect(myFoo).to.be.ok; + * + * @param {Object} ctx object whose property is to be overwritten + * @param {String} name of property to overwrite + * @param {Function} getter function that returns a getter function to be used for name + * @name overwriteProperty + * @api public + */ + +module.exports = function (ctx, name, getter) { + var _get = Object.getOwnPropertyDescriptor(ctx, name) + , _super = function () {}; + + if (_get && 'function' === typeof _get.get) + _super = _get.get + + Object.defineProperty(ctx, name, + { get: function () { + var result = getter(_super).call(this); + return result === undefined ? this : result; + } + , configurable: true + }); +}; + +}); + +require.register("chai/lib/chai/utils/overwriteChainableMethod.js", function (exports, module) { +/*! + * Chai - overwriteChainableMethod utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### overwriteChainableMethod (ctx, name, method, chainingBehavior) + * + * Overwites an already existing chainable method + * and provides access to the previous function or + * property. Must return functions to be used for + * name. + * + * utils.overwriteChainableMethod(chai.Assertion.prototype, 'length', + * function (_super) { + * } + * , function (_super) { + * } + * ); + * + * Can also be accessed directly from `chai.Assertion`. + * + * chai.Assertion.overwriteChainableMethod('foo', fn, fn); + * + * Then can be used as any other assertion. + * + * expect(myFoo).to.have.length(3); + * expect(myFoo).to.have.length.above(3); + * + * @param {Object} ctx object whose method / property is to be overwritten + * @param {String} name of method / property to overwrite + * @param {Function} method function that returns a function to be used for name + * @param {Function} chainingBehavior function that returns a function to be used for property + * @name overwriteChainableMethod + * @api public + */ + +module.exports = function (ctx, name, method, chainingBehavior) { + var chainableBehavior = ctx.__methods[name]; + + var _chainingBehavior = chainableBehavior.chainingBehavior; + chainableBehavior.chainingBehavior = function () { + var result = chainingBehavior(_chainingBehavior).call(this); + return result === undefined ? this : result; + }; + + var _method = chainableBehavior.method; + chainableBehavior.method = function () { + var result = method(_method).apply(this, arguments); + return result === undefined ? this : result; + }; +}; + +}); + +require.register("chai/lib/chai/utils/test.js", function (exports, module) { +/*! + * Chai - test utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Module dependancies + */ + +var flag = require('chai/lib/chai/utils/flag.js'); + +/** + * # test(object, expression) + * + * Test and object for expression. + * + * @param {Object} object (constructed Assertion) + * @param {Arguments} chai.Assertion.prototype.assert arguments + */ + +module.exports = function (obj, args) { + var negate = flag(obj, 'negate') + , expr = args[0]; + return negate ? !expr : expr; +}; + +}); + +require.register("chai/lib/chai/utils/transferFlags.js", function (exports, module) { +/*! + * Chai - transferFlags utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### transferFlags(assertion, object, includeAll = true) + * + * Transfer all the flags for `assertion` to `object`. If + * `includeAll` is set to `false`, then the base Chai + * assertion flags (namely `object`, `ssfi`, and `message`) + * will not be transferred. + * + * + * var newAssertion = new Assertion(); + * utils.transferFlags(assertion, newAssertion); + * + * var anotherAsseriton = new Assertion(myObj); + * utils.transferFlags(assertion, anotherAssertion, false); + * + * @param {Assertion} assertion the assertion to transfer the flags from + * @param {Object} object the object to transfer the flags to; usually a new assertion + * @param {Boolean} includeAll + * @name transferFlags + * @api private + */ + +module.exports = function (assertion, object, includeAll) { + var flags = assertion.__flags || (assertion.__flags = Object.create(null)); + + if (!object.__flags) { + object.__flags = Object.create(null); + } + + includeAll = arguments.length === 3 ? includeAll : true; + + for (var flag in flags) { + if (includeAll || + (flag !== 'object' && flag !== 'ssfi' && flag != 'message')) { + object.__flags[flag] = flags[flag]; + } + } +}; + +}); + +require.register("chai/lib/chai/utils/type.js", function (exports, module) { +/*! + * Chai - type utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Detectable javascript natives + */ + +var natives = { + '[object Arguments]': 'arguments' + , '[object Array]': 'array' + , '[object Date]': 'date' + , '[object Function]': 'function' + , '[object Number]': 'number' + , '[object RegExp]': 'regexp' + , '[object String]': 'string' +}; + +/** + * ### type(object) + * + * Better implementation of `typeof` detection that can + * be used cross-browser. Handles the inconsistencies of + * Array, `null`, and `undefined` detection. + * + * utils.type({}) // 'object' + * utils.type(null) // `null' + * utils.type(undefined) // `undefined` + * utils.type([]) // `array` + * + * @param {Mixed} object to detect type of + * @name type + * @api private + */ + +module.exports = function (obj) { + var str = Object.prototype.toString.call(obj); + if (natives[str]) return natives[str]; + if (obj === null) return 'null'; + if (obj === undefined) return 'undefined'; + if (obj === Object(obj)) return 'object'; + return typeof obj; +}; + +}); + +if (typeof exports == "object") { + module.exports = require("chai"); +} else if (typeof define == "function" && define.amd) { + define("chai", [], function(){ return require("chai"); }); +} else { + (this || window)["chai"] = require("chai"); +} +})() \ No newline at end of file diff --git a/tests/node_modules/mocha.css b/tests/node_modules/mocha.css new file mode 100644 index 000000000..9758515e3 --- /dev/null +++ b/tests/node_modules/mocha.css @@ -0,0 +1,305 @@ +@charset "utf-8"; + +body { + margin:0; +} + +#mocha { + font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; + margin: 60px 50px; +} + +#mocha ul, +#mocha li { + margin: 0; + padding: 0; +} + +#mocha ul { + list-style: none; +} + +#mocha h1, +#mocha h2 { + margin: 0; +} + +#mocha h1 { + margin-top: 15px; + font-size: 1em; + font-weight: 200; +} + +#mocha h1 a { + text-decoration: none; + color: inherit; +} + +#mocha h1 a:hover { + text-decoration: underline; +} + +#mocha .suite .suite h1 { + margin-top: 0; + font-size: .8em; +} + +#mocha .hidden { + display: none; +} + +#mocha h2 { + font-size: 12px; + font-weight: normal; + cursor: pointer; +} + +#mocha .suite { + margin-left: 15px; +} + +#mocha .test { + margin-left: 15px; + overflow: hidden; +} + +#mocha .test.pending:hover h2::after { + content: '(pending)'; + font-family: arial, sans-serif; +} + +#mocha .test.pass.medium .duration { + background: #c09853; +} + +#mocha .test.pass.slow .duration { + background: #b94a48; +} + +#mocha .test.pass::before { + content: '✓'; + font-size: 12px; + display: block; + float: left; + margin-right: 5px; + color: #00d6b2; +} + +#mocha .test.pass .duration { + font-size: 9px; + margin-left: 5px; + padding: 2px 5px; + color: #fff; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); + -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); + box-shadow: inset 0 1px 1px rgba(0,0,0,.2); + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + -ms-border-radius: 5px; + -o-border-radius: 5px; + border-radius: 5px; +} + +#mocha .test.pass.fast .duration { + display: none; +} + +#mocha .test.pending { + color: #0b97c4; +} + +#mocha .test.pending::before { + content: '◦'; + color: #0b97c4; +} + +#mocha .test.fail { + color: #c00; +} + +#mocha .test.fail pre { + color: black; +} + +#mocha .test.fail::before { + content: '✖'; + font-size: 12px; + display: block; + float: left; + margin-right: 5px; + color: #c00; +} + +#mocha .test pre.error { + color: #c00; + max-height: 300px; + overflow: auto; +} + +#mocha .test .html-error { + overflow: auto; + color: black; + line-height: 1.5; + display: block; + float: left; + clear: left; + font: 12px/1.5 monaco, monospace; + margin: 5px; + padding: 15px; + border: 1px solid #eee; + max-width: 85%; /*(1)*/ + max-width: calc(100% - 42px); /*(2)*/ + max-height: 300px; + word-wrap: break-word; + border-bottom-color: #ddd; + -webkit-border-radius: 3px; + -webkit-box-shadow: 0 1px 3px #eee; + -moz-border-radius: 3px; + -moz-box-shadow: 0 1px 3px #eee; + border-radius: 3px; +} + +#mocha .test .html-error pre.error { + border: none; + -webkit-border-radius: none; + -webkit-box-shadow: none; + -moz-border-radius: none; + -moz-box-shadow: none; + padding: 0; + margin: 0; + margin-top: 18px; + max-height: none; +} + +/** + * (1): approximate for browsers not supporting calc + * (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border) + * ^^ seriously + */ +#mocha .test pre { + display: block; + float: left; + clear: left; + font: 12px/1.5 monaco, monospace; + margin: 5px; + padding: 15px; + border: 1px solid #eee; + max-width: 85%; /*(1)*/ + max-width: calc(100% - 42px); /*(2)*/ + word-wrap: break-word; + border-bottom-color: #ddd; + -webkit-border-radius: 3px; + -webkit-box-shadow: 0 1px 3px #eee; + -moz-border-radius: 3px; + -moz-box-shadow: 0 1px 3px #eee; + border-radius: 3px; +} + +#mocha .test h2 { + position: relative; +} + +#mocha .test a.replay { + position: absolute; + top: 3px; + right: 0; + text-decoration: none; + vertical-align: middle; + display: block; + width: 15px; + height: 15px; + line-height: 15px; + text-align: center; + background: #eee; + font-size: 15px; + -moz-border-radius: 15px; + border-radius: 15px; + -webkit-transition: opacity 200ms; + -moz-transition: opacity 200ms; + transition: opacity 200ms; + opacity: 0.3; + color: #888; +} + +#mocha .test:hover a.replay { + opacity: 1; +} + +#mocha-report.pass .test.fail { + display: none; +} + +#mocha-report.fail .test.pass { + display: none; +} + +#mocha-report.pending .test.pass, +#mocha-report.pending .test.fail { + display: none; +} +#mocha-report.pending .test.pass.pending { + display: block; +} + +#mocha-error { + color: #c00; + font-size: 1.5em; + font-weight: 100; + letter-spacing: 1px; +} + +#mocha-stats { + position: fixed; + top: 15px; + right: 10px; + font-size: 12px; + margin: 0; + color: #888; + z-index: 1; +} + +#mocha-stats .progress { + float: right; + padding-top: 0; +} + +#mocha-stats em { + color: black; +} + +#mocha-stats a { + text-decoration: none; + color: inherit; +} + +#mocha-stats a:hover { + border-bottom: 1px solid #eee; +} + +#mocha-stats li { + display: inline-block; + margin: 0 5px; + list-style: none; + padding-top: 11px; +} + +#mocha-stats canvas { + width: 40px; + height: 40px; +} + +#mocha code .comment { color: #ddd; } +#mocha code .init { color: #2f6fad; } +#mocha code .string { color: #5890ad; } +#mocha code .keyword { color: #8a6343; } +#mocha code .number { color: #2f6fad; } + +@media screen and (max-device-width: 480px) { + #mocha { + margin: 60px 0px; + } + + #mocha #stats { + position: absolute; + } +} \ No newline at end of file diff --git a/tests/node_modules/mocha.js b/tests/node_modules/mocha.js new file mode 100644 index 000000000..cdc7939b3 --- /dev/null +++ b/tests/node_modules/mocha.js @@ -0,0 +1,13189 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 1) { + suites.shift(); + } + var suite = Suite.create(suites[0], title); + suite.file = file; + suites.unshift(suite); + return suite; + }; + + /** + * Exclusive test-case. + */ + + context.suite.only = function(title, fn) { + var suite = context.suite(title, fn); + mocha.grep(suite.fullTitle()); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.test = function(title, fn) { + var test = new Test(title, fn); + test.file = file; + suites[0].addTest(test); + return test; + }; + + /** + * Exclusive test-case. + */ + + context.test.only = function(title, fn) { + var test = context.test(title, fn); + var reString = '^' + escapeRe(test.fullTitle()) + '$'; + mocha.grep(new RegExp(reString)); + }; + + context.test.skip = common.test.skip; + }); +}; + +},{"../suite":37,"../test":38,"./common":9,"escape-string-regexp":67}],13:[function(require,module,exports){ +/** + * Module dependencies. + */ + +var Suite = require('../suite'); +var Test = require('../test'); +var escapeRe = require('escape-string-regexp'); + +/** + * TDD-style interface: + * + * suite('Array', function() { + * suite('#indexOf()', function() { + * suiteSetup(function() { + * + * }); + * + * test('should return -1 when not present', function() { + * + * }); + * + * test('should return the index when present', function() { + * + * }); + * + * suiteTeardown(function() { + * + * }); + * }); + * }); + * + * @param {Suite} suite Root suite. + */ +module.exports = function(suite) { + var suites = [suite]; + + suite.on('pre-require', function(context, file, mocha) { + var common = require('./common')(suites, context); + + context.setup = common.beforeEach; + context.teardown = common.afterEach; + context.suiteSetup = common.before; + context.suiteTeardown = common.after; + context.run = mocha.options.delay && common.runWithSuite(suite); + + /** + * Describe a "suite" with the given `title` and callback `fn` containing + * nested suites and/or tests. + */ + context.suite = function(title, fn) { + var suite = Suite.create(suites[0], title); + suite.file = file; + suites.unshift(suite); + fn.call(suite); + suites.shift(); + return suite; + }; + + /** + * Pending suite. + */ + context.suite.skip = function(title, fn) { + var suite = Suite.create(suites[0], title); + suite.pending = true; + suites.unshift(suite); + fn.call(suite); + suites.shift(); + }; + + /** + * Exclusive test-case. + */ + context.suite.only = function(title, fn) { + var suite = context.suite(title, fn); + mocha.grep(suite.fullTitle()); + }; + + /** + * Describe a specification or test-case with the given `title` and + * callback `fn` acting as a thunk. + */ + context.test = function(title, fn) { + var suite = suites[0]; + if (suite.pending) { + fn = null; + } + var test = new Test(title, fn); + test.file = file; + suite.addTest(test); + return test; + }; + + /** + * Exclusive test-case. + */ + + context.test.only = function(title, fn) { + var test = context.test(title, fn); + var reString = '^' + escapeRe(test.fullTitle()) + '$'; + mocha.grep(new RegExp(reString)); + }; + + context.test.skip = common.test.skip; + }); +}; + +},{"../suite":37,"../test":38,"./common":9,"escape-string-regexp":67}],14:[function(require,module,exports){ +(function (process,global,__dirname){ +/*! + * mocha + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var escapeRe = require('escape-string-regexp'); +var path = require('path'); +var reporters = require('./reporters'); +var utils = require('./utils'); + +/** + * Expose `Mocha`. + */ + +exports = module.exports = Mocha; + +/** + * To require local UIs and reporters when running in node. + */ + +if (!process.browser) { + var cwd = process.cwd(); + module.paths.push(cwd, path.join(cwd, 'node_modules')); +} + +/** + * Expose internals. + */ + +exports.utils = utils; +exports.interfaces = require('./interfaces'); +exports.reporters = reporters; +exports.Runnable = require('./runnable'); +exports.Context = require('./context'); +exports.Runner = require('./runner'); +exports.Suite = require('./suite'); +exports.Hook = require('./hook'); +exports.Test = require('./test'); + +/** + * Return image `name` path. + * + * @api private + * @param {string} name + * @return {string} + */ +function image(name) { + return path.join(__dirname, '../images', name + '.png'); +} + +/** + * Set up mocha with `options`. + * + * Options: + * + * - `ui` name "bdd", "tdd", "exports" etc + * - `reporter` reporter instance, defaults to `mocha.reporters.spec` + * - `globals` array of accepted globals + * - `timeout` timeout in milliseconds + * - `bail` bail on the first test failure + * - `slow` milliseconds to wait before considering a test slow + * - `ignoreLeaks` ignore global leaks + * - `fullTrace` display the full stack-trace on failing + * - `grep` string or regexp to filter tests with + * + * @api public + * @param {Object} options + */ +function Mocha(options) { + options = options || {}; + this.files = []; + this.options = options; + if (options.grep) { + this.grep(new RegExp(options.grep)); + } + if (options.fgrep) { + this.grep(options.fgrep); + } + this.suite = new exports.Suite('', new exports.Context()); + this.ui(options.ui); + this.bail(options.bail); + this.reporter(options.reporter, options.reporterOptions); + if (options.timeout != null) { + this.timeout(options.timeout); + } + this.useColors(options.useColors); + if (options.enableTimeouts !== null) { + this.enableTimeouts(options.enableTimeouts); + } + if (options.slow) { + this.slow(options.slow); + } + + this.suite.on('pre-require', function(context) { + exports.afterEach = context.afterEach || context.teardown; + exports.after = context.after || context.suiteTeardown; + exports.beforeEach = context.beforeEach || context.setup; + exports.before = context.before || context.suiteSetup; + exports.describe = context.describe || context.suite; + exports.it = context.it || context.test; + exports.setup = context.setup || context.beforeEach; + exports.suiteSetup = context.suiteSetup || context.before; + exports.suiteTeardown = context.suiteTeardown || context.after; + exports.suite = context.suite || context.describe; + exports.teardown = context.teardown || context.afterEach; + exports.test = context.test || context.it; + exports.run = context.run; + }); +} + +/** + * Enable or disable bailing on the first failure. + * + * @api public + * @param {boolean} [bail] + */ +Mocha.prototype.bail = function(bail) { + if (!arguments.length) { + bail = true; + } + this.suite.bail(bail); + return this; +}; + +/** + * Add test `file`. + * + * @api public + * @param {string} file + */ +Mocha.prototype.addFile = function(file) { + this.files.push(file); + return this; +}; + +/** + * Set reporter to `reporter`, defaults to "spec". + * + * @api public + * @param {string|Function} reporter name or constructor + * @param {Object} reporterOptions optional options + */ +Mocha.prototype.reporter = function(reporter, reporterOptions) { + if (typeof reporter === 'function') { + this._reporter = reporter; + } else { + reporter = reporter || 'spec'; + var _reporter; + // Try to load a built-in reporter. + if (reporters[reporter]) { + _reporter = reporters[reporter]; + } + // Try to load reporters from process.cwd() and node_modules + if (!_reporter) { + try { + _reporter = require(reporter); + } catch (err) { + err.message.indexOf('Cannot find module') !== -1 + ? console.warn('"' + reporter + '" reporter not found') + : console.warn('"' + reporter + '" reporter blew up with error:\n' + err.stack); + } + } + if (!_reporter && reporter === 'teamcity') { + console.warn('The Teamcity reporter was moved to a package named ' + + 'mocha-teamcity-reporter ' + + '(https://npmjs.org/package/mocha-teamcity-reporter).'); + } + if (!_reporter) { + throw new Error('invalid reporter "' + reporter + '"'); + } + this._reporter = _reporter; + } + this.options.reporterOptions = reporterOptions; + return this; +}; + +/** + * Set test UI `name`, defaults to "bdd". + * + * @api public + * @param {string} bdd + */ +Mocha.prototype.ui = function(name) { + name = name || 'bdd'; + this._ui = exports.interfaces[name]; + if (!this._ui) { + try { + this._ui = require(name); + } catch (err) { + throw new Error('invalid interface "' + name + '"'); + } + } + this._ui = this._ui(this.suite); + return this; +}; + +/** + * Load registered files. + * + * @api private + */ +Mocha.prototype.loadFiles = function(fn) { + var self = this; + var suite = this.suite; + var pending = this.files.length; + this.files.forEach(function(file) { + file = path.resolve(file); + suite.emit('pre-require', global, file, self); + suite.emit('require', require(file), file, self); + suite.emit('post-require', global, file, self); + --pending || (fn && fn()); + }); +}; + +/** + * Enable growl support. + * + * @api private + */ +Mocha.prototype._growl = function(runner, reporter) { + var notify = require('growl'); + + runner.on('end', function() { + var stats = reporter.stats; + if (stats.failures) { + var msg = stats.failures + ' of ' + runner.total + ' tests failed'; + notify(msg, { name: 'mocha', title: 'Failed', image: image('error') }); + } else { + notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', { + name: 'mocha', + title: 'Passed', + image: image('ok') + }); + } + }); +}; + +/** + * Add regexp to grep, if `re` is a string it is escaped. + * + * @api public + * @param {RegExp|string} re + * @return {Mocha} + */ +Mocha.prototype.grep = function(re) { + this.options.grep = typeof re === 'string' ? new RegExp(escapeRe(re)) : re; + return this; +}; + +/** + * Invert `.grep()` matches. + * + * @api public + * @return {Mocha} + */ +Mocha.prototype.invert = function() { + this.options.invert = true; + return this; +}; + +/** + * Ignore global leaks. + * + * @api public + * @param {boolean} ignore + * @return {Mocha} + */ +Mocha.prototype.ignoreLeaks = function(ignore) { + this.options.ignoreLeaks = Boolean(ignore); + return this; +}; + +/** + * Enable global leak checking. + * + * @api public + * @return {Mocha} + */ +Mocha.prototype.checkLeaks = function() { + this.options.ignoreLeaks = false; + return this; +}; + +/** + * Display long stack-trace on failing + * + * @api public + * @return {Mocha} + */ +Mocha.prototype.fullTrace = function() { + this.options.fullStackTrace = true; + return this; +}; + +/** + * Enable growl support. + * + * @api public + * @return {Mocha} + */ +Mocha.prototype.growl = function() { + this.options.growl = true; + return this; +}; + +/** + * Ignore `globals` array or string. + * + * @api public + * @param {Array|string} globals + * @return {Mocha} + */ +Mocha.prototype.globals = function(globals) { + this.options.globals = (this.options.globals || []).concat(globals); + return this; +}; + +/** + * Emit color output. + * + * @api public + * @param {boolean} colors + * @return {Mocha} + */ +Mocha.prototype.useColors = function(colors) { + if (colors !== undefined) { + this.options.useColors = colors; + } + return this; +}; + +/** + * Use inline diffs rather than +/-. + * + * @api public + * @param {boolean} inlineDiffs + * @return {Mocha} + */ +Mocha.prototype.useInlineDiffs = function(inlineDiffs) { + this.options.useInlineDiffs = inlineDiffs !== undefined && inlineDiffs; + return this; +}; + +/** + * Set the timeout in milliseconds. + * + * @api public + * @param {number} timeout + * @return {Mocha} + */ +Mocha.prototype.timeout = function(timeout) { + this.suite.timeout(timeout); + return this; +}; + +/** + * Set slowness threshold in milliseconds. + * + * @api public + * @param {number} slow + * @return {Mocha} + */ +Mocha.prototype.slow = function(slow) { + this.suite.slow(slow); + return this; +}; + +/** + * Enable timeouts. + * + * @api public + * @param {boolean} enabled + * @return {Mocha} + */ +Mocha.prototype.enableTimeouts = function(enabled) { + this.suite.enableTimeouts(arguments.length && enabled !== undefined ? enabled : true); + return this; +}; + +/** + * Makes all tests async (accepting a callback) + * + * @api public + * @return {Mocha} + */ +Mocha.prototype.asyncOnly = function() { + this.options.asyncOnly = true; + return this; +}; + +/** + * Disable syntax highlighting (in browser). + * + * @api public + * @returns {Mocha} + */ +Mocha.prototype.noHighlighting = function() { + this.options.noHighlighting = true; + return this; +}; + +/** + * Delay root suite execution. + * + * @api public + * @returns {Mocha} + */ +Mocha.prototype.delay = function delay() { + this.options.delay = true; + return this; +}; + +/** + * Run tests and invoke `fn()` when complete. + * + * @api public + * @param {Function} fn + * @return {Runner} + */ +Mocha.prototype.run = function(fn) { + if (this.files.length) { + this.loadFiles(); + } + var suite = this.suite; + var options = this.options; + options.files = this.files; + var runner = new exports.Runner(suite, options.delay); + var reporter = new this._reporter(runner, options); + runner.ignoreLeaks = options.ignoreLeaks !== false; + runner.fullStackTrace = options.fullStackTrace; + runner.asyncOnly = options.asyncOnly; + if (options.grep) { + runner.grep(options.grep, options.invert); + } + if (options.globals) { + runner.globals(options.globals); + } + if (options.growl) { + this._growl(runner, reporter); + } + if (options.useColors !== undefined) { + exports.reporters.Base.useColors = options.useColors; + } + exports.reporters.Base.inlineDiffs = options.useInlineDiffs; + + function done(failures) { + if (reporter.done) { + reporter.done(failures, fn); + } else { + fn && fn(failures); + } + } + + return runner.run(done); +}; + +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},"/lib") +},{"./context":6,"./hook":7,"./interfaces":11,"./reporters":22,"./runnable":35,"./runner":36,"./suite":37,"./test":38,"./utils":39,"_process":50,"escape-string-regexp":67,"growl":68,"path":41}],15:[function(require,module,exports){ +/** + * Helpers. + */ + +var s = 1000; +var m = s * 60; +var h = m * 60; +var d = h * 24; +var y = d * 365.25; + +/** + * Parse or format the given `val`. + * + * Options: + * + * - `long` verbose formatting [false] + * + * @api public + * @param {string|number} val + * @param {Object} options + * @return {string|number} + */ +module.exports = function(val, options) { + options = options || {}; + if (typeof val === 'string') { + return parse(val); + } + // https://github.com/mochajs/mocha/pull/1035 + return options['long'] ? longFormat(val) : shortFormat(val); +}; + +/** + * Parse the given `str` and return milliseconds. + * + * @api private + * @param {string} str + * @return {number} + */ +function parse(str) { + var match = (/^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i).exec(str); + if (!match) { + return; + } + var n = parseFloat(match[1]); + var type = (match[2] || 'ms').toLowerCase(); + switch (type) { + case 'years': + case 'year': + case 'y': + return n * y; + case 'days': + case 'day': + case 'd': + return n * d; + case 'hours': + case 'hour': + case 'h': + return n * h; + case 'minutes': + case 'minute': + case 'm': + return n * m; + case 'seconds': + case 'second': + case 's': + return n * s; + case 'ms': + return n; + default: + // No default case + } +} + +/** + * Short format for `ms`. + * + * @api private + * @param {number} ms + * @return {string} + */ +function shortFormat(ms) { + if (ms >= d) { + return Math.round(ms / d) + 'd'; + } + if (ms >= h) { + return Math.round(ms / h) + 'h'; + } + if (ms >= m) { + return Math.round(ms / m) + 'm'; + } + if (ms >= s) { + return Math.round(ms / s) + 's'; + } + return ms + 'ms'; +} + +/** + * Long format for `ms`. + * + * @api private + * @param {number} ms + * @return {string} + */ +function longFormat(ms) { + return plural(ms, d, 'day') + || plural(ms, h, 'hour') + || plural(ms, m, 'minute') + || plural(ms, s, 'second') + || ms + ' ms'; +} + +/** + * Pluralization helper. + * + * @api private + * @param {number} ms + * @param {number} n + * @param {string} name + */ +function plural(ms, n, name) { + if (ms < n) { + return; + } + if (ms < n * 1.5) { + return Math.floor(ms / n) + ' ' + name; + } + return Math.ceil(ms / n) + ' ' + name + 's'; +} + +},{}],16:[function(require,module,exports){ + +/** + * Expose `Pending`. + */ + +module.exports = Pending; + +/** + * Initialize a new `Pending` error with the given message. + * + * @param {string} message + */ +function Pending(message) { + this.message = message; +} + +},{}],17:[function(require,module,exports){ +(function (process,global){ +/** + * Module dependencies. + */ + +var tty = require('tty'); +var diff = require('diff'); +var ms = require('../ms'); +var utils = require('../utils'); +var supportsColor = process.browser ? null : require('supports-color'); + +/** + * Expose `Base`. + */ + +exports = module.exports = Base; + +/** + * Save timer references to avoid Sinon interfering. + * See: https://github.com/mochajs/mocha/issues/237 + */ + +/* eslint-disable no-unused-vars, no-native-reassign */ +var Date = global.Date; +var setTimeout = global.setTimeout; +var setInterval = global.setInterval; +var clearTimeout = global.clearTimeout; +var clearInterval = global.clearInterval; +/* eslint-enable no-unused-vars, no-native-reassign */ + +/** + * Check if both stdio streams are associated with a tty. + */ + +var isatty = tty.isatty(1) && tty.isatty(2); + +/** + * Enable coloring by default, except in the browser interface. + */ + +exports.useColors = !process.browser && (supportsColor || (process.env.MOCHA_COLORS !== undefined)); + +/** + * Inline diffs instead of +/- + */ + +exports.inlineDiffs = false; + +/** + * Default color map. + */ + +exports.colors = { + pass: 90, + fail: 31, + 'bright pass': 92, + 'bright fail': 91, + 'bright yellow': 93, + pending: 36, + suite: 0, + 'error title': 0, + 'error message': 31, + 'error stack': 90, + checkmark: 32, + fast: 90, + medium: 33, + slow: 31, + green: 32, + light: 90, + 'diff gutter': 90, + 'diff added': 32, + 'diff removed': 31 +}; + +/** + * Default symbol map. + */ + +exports.symbols = { + ok: '✓', + err: '✖', + dot: '․' +}; + +// With node.js on Windows: use symbols available in terminal default fonts +if (process.platform === 'win32') { + exports.symbols.ok = '\u221A'; + exports.symbols.err = '\u00D7'; + exports.symbols.dot = '.'; +} + +/** + * Color `str` with the given `type`, + * allowing colors to be disabled, + * as well as user-defined color + * schemes. + * + * @param {string} type + * @param {string} str + * @return {string} + * @api private + */ +var color = exports.color = function(type, str) { + if (!exports.useColors) { + return String(str); + } + return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m'; +}; + +/** + * Expose term window size, with some defaults for when stderr is not a tty. + */ + +exports.window = { + width: 75 +}; + +if (isatty) { + exports.window.width = tty.getWindowSize()[1]; +} + +/** + * Expose some basic cursor interactions that are common among reporters. + */ + +exports.cursor = { + hide: function() { + isatty && process.stdout.write('\u001b[?25l'); + }, + + show: function() { + isatty && process.stdout.write('\u001b[?25h'); + }, + + deleteLine: function() { + isatty && process.stdout.write('\u001b[2K'); + }, + + beginningOfLine: function() { + isatty && process.stdout.write('\u001b[0G'); + }, + + CR: function() { + if (isatty) { + exports.cursor.deleteLine(); + exports.cursor.beginningOfLine(); + } else { + process.stdout.write('\r'); + } + } +}; + +/** + * Outut the given `failures` as a list. + * + * @param {Array} failures + * @api public + */ + +exports.list = function(failures) { + console.log(); + failures.forEach(function(test, i) { + // format + var fmt = color('error title', ' %s) %s:\n') + + color('error message', ' %s') + + color('error stack', '\n%s\n'); + + // msg + var msg; + var err = test.err; + var message = err.message || ''; + var stack = err.stack || message; + var index = stack.indexOf(message); + var actual = err.actual; + var expected = err.expected; + var escape = true; + + if (index === -1) { + msg = message; + } else { + index += message.length; + msg = stack.slice(0, index); + // remove msg from stack + stack = stack.slice(index + 1); + } + + // uncaught + if (err.uncaught) { + msg = 'Uncaught ' + msg; + } + // explicitly show diff + if (err.showDiff !== false && sameType(actual, expected) && expected !== undefined) { + escape = false; + if (!(utils.isString(actual) && utils.isString(expected))) { + err.actual = actual = utils.stringify(actual); + err.expected = expected = utils.stringify(expected); + } + + fmt = color('error title', ' %s) %s:\n%s') + color('error stack', '\n%s\n'); + var match = message.match(/^([^:]+): expected/); + msg = '\n ' + color('error message', match ? match[1] : msg); + + if (exports.inlineDiffs) { + msg += inlineDiff(err, escape); + } else { + msg += unifiedDiff(err, escape); + } + } + + // indent stack trace + stack = stack.replace(/^/gm, ' '); + + console.log(fmt, (i + 1), test.fullTitle(), msg, stack); + }); +}; + +/** + * Initialize a new `Base` reporter. + * + * All other reporters generally + * inherit from this reporter, providing + * stats such as test duration, number + * of tests passed / failed etc. + * + * @param {Runner} runner + * @api public + */ + +function Base(runner) { + var stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 }; + var failures = this.failures = []; + + if (!runner) { + return; + } + this.runner = runner; + + runner.stats = stats; + + runner.on('start', function() { + stats.start = new Date(); + }); + + runner.on('suite', function(suite) { + stats.suites = stats.suites || 0; + suite.root || stats.suites++; + }); + + runner.on('test end', function() { + stats.tests = stats.tests || 0; + stats.tests++; + }); + + runner.on('pass', function(test) { + stats.passes = stats.passes || 0; + + if (test.duration > test.slow()) { + test.speed = 'slow'; + } else if (test.duration > test.slow() / 2) { + test.speed = 'medium'; + } else { + test.speed = 'fast'; + } + + stats.passes++; + }); + + runner.on('fail', function(test, err) { + stats.failures = stats.failures || 0; + stats.failures++; + test.err = err; + failures.push(test); + }); + + runner.on('end', function() { + stats.end = new Date(); + stats.duration = new Date() - stats.start; + }); + + runner.on('pending', function() { + stats.pending++; + }); +} + +/** + * Output common epilogue used by many of + * the bundled reporters. + * + * @api public + */ +Base.prototype.epilogue = function() { + var stats = this.stats; + var fmt; + + console.log(); + + // passes + fmt = color('bright pass', ' ') + + color('green', ' %d passing') + + color('light', ' (%s)'); + + console.log(fmt, + stats.passes || 0, + ms(stats.duration)); + + // pending + if (stats.pending) { + fmt = color('pending', ' ') + + color('pending', ' %d pending'); + + console.log(fmt, stats.pending); + } + + // failures + if (stats.failures) { + fmt = color('fail', ' %d failing'); + + console.log(fmt, stats.failures); + + Base.list(this.failures); + console.log(); + } + + console.log(); +}; + +/** + * Pad the given `str` to `len`. + * + * @api private + * @param {string} str + * @param {string} len + * @return {string} + */ +function pad(str, len) { + str = String(str); + return Array(len - str.length + 1).join(' ') + str; +} + +/** + * Returns an inline diff between 2 strings with coloured ANSI output + * + * @api private + * @param {Error} err with actual/expected + * @param {boolean} escape + * @return {string} Diff + */ +function inlineDiff(err, escape) { + var msg = errorDiff(err, 'WordsWithSpace', escape); + + // linenos + var lines = msg.split('\n'); + if (lines.length > 4) { + var width = String(lines.length).length; + msg = lines.map(function(str, i) { + return pad(++i, width) + ' |' + ' ' + str; + }).join('\n'); + } + + // legend + msg = '\n' + + color('diff removed', 'actual') + + ' ' + + color('diff added', 'expected') + + '\n\n' + + msg + + '\n'; + + // indent + msg = msg.replace(/^/gm, ' '); + return msg; +} + +/** + * Returns a unified diff between two strings. + * + * @api private + * @param {Error} err with actual/expected + * @param {boolean} escape + * @return {string} The diff. + */ +function unifiedDiff(err, escape) { + var indent = ' '; + function cleanUp(line) { + if (escape) { + line = escapeInvisibles(line); + } + if (line[0] === '+') { + return indent + colorLines('diff added', line); + } + if (line[0] === '-') { + return indent + colorLines('diff removed', line); + } + if (line.match(/\@\@/)) { + return null; + } + if (line.match(/\\ No newline/)) { + return null; + } + return indent + line; + } + function notBlank(line) { + return line != null; + } + var msg = diff.createPatch('string', err.actual, err.expected); + var lines = msg.split('\n').splice(4); + return '\n ' + + colorLines('diff added', '+ expected') + ' ' + + colorLines('diff removed', '- actual') + + '\n\n' + + lines.map(cleanUp).filter(notBlank).join('\n'); +} + +/** + * Return a character diff for `err`. + * + * @api private + * @param {Error} err + * @param {string} type + * @param {boolean} escape + * @return {string} + */ +function errorDiff(err, type, escape) { + var actual = escape ? escapeInvisibles(err.actual) : err.actual; + var expected = escape ? escapeInvisibles(err.expected) : err.expected; + return diff['diff' + type](actual, expected).map(function(str) { + if (str.added) { + return colorLines('diff added', str.value); + } + if (str.removed) { + return colorLines('diff removed', str.value); + } + return str.value; + }).join(''); +} + +/** + * Returns a string with all invisible characters in plain text + * + * @api private + * @param {string} line + * @return {string} + */ +function escapeInvisibles(line) { + return line.replace(/\t/g, '') + .replace(/\r/g, '') + .replace(/\n/g, '\n'); +} + +/** + * Color lines for `str`, using the color `name`. + * + * @api private + * @param {string} name + * @param {string} str + * @return {string} + */ +function colorLines(name, str) { + return str.split('\n').map(function(str) { + return color(name, str); + }).join('\n'); +} + +/** + * Object#toString reference. + */ +var objToString = Object.prototype.toString; + +/** + * Check that a / b have the same type. + * + * @api private + * @param {Object} a + * @param {Object} b + * @return {boolean} + */ +function sameType(a, b) { + return objToString.call(a) === objToString.call(b); +} + +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"../ms":15,"../utils":39,"_process":50,"diff":66,"supports-color":41,"tty":5}],18:[function(require,module,exports){ +/** + * Module dependencies. + */ + +var Base = require('./base'); +var utils = require('../utils'); + +/** + * Expose `Doc`. + */ + +exports = module.exports = Doc; + +/** + * Initialize a new `Doc` reporter. + * + * @param {Runner} runner + * @api public + */ +function Doc(runner) { + Base.call(this, runner); + + var indents = 2; + + function indent() { + return Array(indents).join(' '); + } + + runner.on('suite', function(suite) { + if (suite.root) { + return; + } + ++indents; + console.log('%s
', indent()); + ++indents; + console.log('%s

%s

', indent(), utils.escape(suite.title)); + console.log('%s
', indent()); + }); + + runner.on('suite end', function(suite) { + if (suite.root) { + return; + } + console.log('%s
', indent()); + --indents; + console.log('%s
', indent()); + --indents; + }); + + runner.on('pass', function(test) { + console.log('%s

X<9mq6G3%vtb%piIo89ti&<^ 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 M5c!pqju`3xA7| * { + pointer-events: auto; +} +/* user selection */ +.enyo-unselectable { + cursor: default; + -ms-user-select: none; + -webkit-user-select: none; + -moz-user-select: -moz-none; + user-select: none; +} +.enyo-selectable { + cursor: auto; + -ms-user-select: element; + -webkit-user-select: text; + -moz-user-select: text; + user-select: text; +} +.enyo-selectable::selection, +.enyo-selectable ::selection { + background: #3297FD; + color: #FFF; +} +/* layout */ +body .enyo-fit { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; +} +.enyo-clip { + overflow: hidden; +} +.enyo-border-box { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +/* compositing */ +.enyo-composite { + -webkit-transform: translateZ(0); + -moz-transform: translateZ(0); + -ms-transform: translateZ(0); + -o-transform: translateZ(0); + transform: translateZ(0); +} +.enyo-inline { + display: inline-block; +} +.enyo-positioned { + position: relative; +} +.enyo-fill { + position: relative; + width: 100%; + height: 100%; +} + +/* Accessibility */ +*[tabindex] { + /* remove outline in case dom has tabindex attribute */ + outline: none; + /* remove tap highlight in case dom has tabindex attribute */ + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +.enyo-scrollable { + overflow: hidden; +} + +/* Fullscreen */ +:-webkit-full-screen { + width: 100% !important; + height: 100% !important; +} +:-moz-full-screen { + width: 100% !important; + height: 100% !important; +} +:-ms-fullscreen { + width: 100% !important; + height: 100% !important; +} +:-o-full-screen { + width: 100% !important; + height: 100% !important; +} +:fullscreen { + width: 100% !important; + height: 100% !important; +} +/* Fallback Fullscreen */ +body .enyo-fullscreen { + position: absolute !important; + left: 0 !important; + top: 0 !important; + right: 0 !important; + bottom: 0 !important; + width: 100% !important; + height: 100% !important; + box-sizing: border-box !important; +} + +.enyo-image { + background-position: center; + background-repeat: no-repeat; + background-size: cover; +} +.enyo-image.sized { + display: inline-block; +} +.enyo-image.contain { + background-size: contain; +} +.enyo-image.cover { + background-size: cover; +} + +.enyo-tool-decorator { + display: inline-block; +} +.enyo-tool-decorator > * { + display: inline-block; + vertical-align: middle; +} + +/* reset */ +button { + font-size: inherit; + font-family: inherit; +} +button::-moz-focus-inner { + border: 0; + padding: 0; +} + diff --git a/tests/build/enyo.js b/tests/build/enyo.js new file mode 100644 index 000000000..3e091068a --- /dev/null +++ b/tests/build/enyo.js @@ -0,0 +1,24160 @@ +(function (scope) { + var require, manifest, exported, entries; + + // if there is already a global require method exposed from somewhere we will find it now + require = scope.require; + + // below is where the generated manifest will be placed + manifest = {'enyo/options':[function (module,exports,global,require,request){ +/*jshint node:true */ +'use strict'; + +/** +* Returns the global enyo options hash +* @module enyo/options +*/ + +module.exports = (global.enyo && global.enyo.options) || {}; + +}],'enyo/PathResolverFactory':[function (module,exports,global,require,request){ + + +var PathResolverFactory = module.exports = function() { + this.paths = {}; + this.pathNames = []; +}; + +PathResolverFactory.prototype = { + addPath: function(inName, inPath) { + this.paths[inName] = inPath; + this.pathNames.push(inName); + this.pathNames.sort(function(a, b) { + return b.length - a.length; + }); + return inPath; + }, + addPaths: function(inPaths) { + if (inPaths) { + for (var n in inPaths) { + this.addPath(n, inPaths[n]); + } + } + }, + includeTrailingSlash: function(inPath) { + return (inPath && inPath.slice(-1) !== "/") ? inPath + "/" : inPath; + }, + // replace macros of the form $pathname with the mapped value of paths.pathname + rewrite: function (inPath) { + var working, its = this.includeTrailingSlash, paths = this.paths; + var fn = function(macro, name) { + working = true; + return its(paths[name]) || ''; + }; + var result = inPath; + do { + working = false; + for (var i=0; i= 0; +}; + +/** +* Binds the `this` context of any method to a scope and a variable number of provided initial +* parameters. +* +* @param {Object} scope - The `this` context for the method. +* @param {(Function|String)} method - A Function or the name of a method to bind. +* @param {...*} [args] Any arguments that will be provided as the initial arguments to the +* enclosed method. +* @returns {Function} The bound method/closure. +* @public +*/ +var bind = exports.bind = function (scope, method) { + if (!method) { + method = scope; + scope = null; + } + scope = scope || global; + if (typeof method == 'string') { + if (scope[method]) { + method = scope[method]; + } else { + throw('enyo.bind: scope["' + method + '"] is null (scope="' + scope + '")'); + } + } + if (typeof method == 'function') { + var args = cloneArray(arguments, 2); + if (method.bind) { + return method.bind.apply(method, [scope].concat(args)); + } else { + return function() { + var nargs = cloneArray(arguments); + // invoke with collected args + return method.apply(scope, args.concat(nargs)); + }; + } + } else { + throw('enyo.bind: scope["' + method + '"] is not a function (scope="' + scope + '")'); + } +}; + +/** +* Binds a callback to a scope. If the object has a `destroyed` property that's truthy, then the +* callback will not be run if called. This can be used to implement both +* {@link module:enyo/CoreObject~Object#bindSafely} and {@link module:enyo/CoreObject~Object}-like objects like +* {@link module:enyo/Model~Model} and {@link module:enyo/Collection~Collection}. +* +* @param {Object} scope - The `this` context for the method. +* @param {(Function|String)} method - A Function or the name of a method to bind. +* @param {...*} [args] Any arguments that will be provided as the initial arguments to the +* enclosed method. +* @returns {Function} The bound method/closure. +* @public +*/ +exports.bindSafely = function (scope, method) { + if (typeof method == 'string') { + if (scope[method]) { + method = scope[method]; + } else { + throw('enyo.bindSafely: scope["' + method + '"] is null (this="' + this + '")'); + } + } + if (typeof method == 'function') { + var args = cloneArray(arguments, 2); + return function() { + if (scope.destroyed) { + return; + } + var nargs = cloneArray(arguments); + return method.apply(scope, args.concat(nargs)); + }; + } else { + throw('enyo.bindSafely: scope["' + method + '"] is not a function (this="' + this + '")'); + } +}; + +/** +* Calls the provided `method` on `scope`, asynchronously. +* +* Uses [window.setTimeout()]{@glossary window.setTimeout} with minimum delay, +* usually around 10ms. +* +* Additional arguments are passed to `method` when it is invoked. +* +* If only a single argument is supplied, will just call that function asynchronously without +* doing any additional binding. +* +* @param {Object} scope - The `this` context for the method. +* @param {(Function|String)} method - A Function or the name of a method to bind. +* @param {...*} [args] Any arguments that will be provided as the initial arguments to the +* enclosed method. +* @returns {Number} The `setTimeout` id. +* @public +*/ +exports.asyncMethod = function (scope, method) { + if (!method) { + // passed just a single argument + return setTimeout(scope, 1); + } else { + return setTimeout(bind.apply(scope, arguments), 1); + } +}; + +/** +* Calls the provided `method` ([String]{@glossary String}) on `scope` with optional +* arguments `args` ([Array]{@glossary Array}), if the object and method exist. +* +* @example +* enyo.call(myWorkObject, 'doWork', [3, 'foo']); +* +* @param {Object} scope - The `this` context for the method. +* @param {(Function|String)} method - A Function or the name of a method to bind. +* @param {Array} [args] - An array of arguments to pass to the method. +* @returns {*} The return value of the method. +* @public +*/ +exports.call = function (scope, method, args) { + var context = scope || this; + if (method) { + var fn = context[method] || method; + if (fn && fn.apply) { + return fn.apply(context, args || []); + } + } +}; + +/** +* Returns the current time in milliseconds. On platforms that support it, +* [Date.now()]{@glossary Date.now} will be used; otherwise this will +* be equivalent to [new Date().getTime()]{@glossary Date.getTime}. +* +* @returns {Number} Number of milliseconds representing the current time. +* @method +* @public +*/ + +var now = exports.now = Date.now || function () { + return new Date().getTime(); +}; + +/** +* When [window.performance]{@glossary window.performance} is available, supplies +* a high-precision, high-performance monotonic timestamp, which is independent of +* changes to the system clock and thus safer for use in animation, etc. Falls back to +* {@link module:enyo/utils#now} (based on the JavaScript [Date]{@glossary Date} object), +* which is subject to system time changes. +* +* @returns {Number} Number of milliseconds representing the current time or time since +* start of application execution as reported by the platform. +* @method +* @public +*/ +exports.perfNow = (function () { + // we have to check whether or not the browser has supplied a valid + // method to use + var perf = window.performance || {}; + // test against all known vendor-specific implementations, but use + // a fallback just in case + perf.now = perf.now || perf.mozNow || perf.msNow || perf.oNow || perf.webkitNow || now; + return function () { + return perf.now(); + }; +}()); + +/** +* A fast-path enabled global getter that takes a string path, which may be a full path (from +* context window/Enyo) or a relative path (to the execution context of the method). It knows how +* to check for and call the backwards-compatible generated getters, as well as how to handle +* computed properties. Returns `undefined` if the object at the given path cannot be found. May +* safely be called on non-existent paths. +* +* @param {String} path - The path from which to retrieve a value. +* @returns {*} The value for the given path, or `undefined` if the path could not be +* completely resolved. +* @method enyo.getPath +* @public +*/ +var getPath = exports.getPath = function (path) { + // we're trying to catch only null/undefined not empty string or 0 cases + if (!path && path !== null && path !== undefined) return path; + + var next = this, + parts, + part, + getter, + prev; + + // obviously there is a severe penalty for requesting get with a path lead + // by unnecessary relative notation... + if (path[0] == '.') path = path.replace(/^\.+/, ''); + + // here's where we check to make sure we have a truthy string-ish + if (!path) return; + + parts = path.split('.'); + part = parts.shift(); + + do { + prev = next; + // for constructors we must check to make sure they are undeferred before + // looking for static properties + // for the auto generated or provided published property support we have separate + // routines that must be called to preserve compatibility + if (next._getters && ((getter = next._getters[part])) && !getter.generated) next = next[getter](); + // for all other special cases to ensure we use any overloaded getter methods + else if (next.get && next !== this && next.get !== getPath) next = next.get(part); + // and for regular cases + else next = next[part]; + } while (next && (part = parts.shift())); + + // if necessary we ensure we've undeferred any constructor that we're + // retrieving here as a final property as well + return next; +}; + +/** +* @private +*/ +getPath.fast = function (path) { + // the current context + var b = this, fn, v; + if (b._getters && (fn = b._getters[path])) { + v = b[fn](); + } else { + v = b[path]; + } + + return v; +}; + +/** +* @TODO: Out of date... +* A global setter that takes a string path (relative to the method's execution context) or a +* full path (relative to window). Attempts to automatically retrieve any previously existing +* value to supply to any observers. If the context is an {@link module:enyo/CoreObject~Object} or subkind, the +* {@link module:enyo/ObserverSupport~ObserverSupport.notify} method is used to notify listeners for the path's being +* set. If the previous value is equivalent to the newly set value, observers will not be +* triggered by default. If the third parameter is present and is an explicit boolean true, the +* observers will be triggered regardless. Returns the context from which the method was executed. +* +* @param {String} path - The path for which to set the given value. +* @param {*} is - The value to set. +* @param {Object} [opts] - An options hash. +* @returns {this} Whatever the given context was when executed. +* @method enyo.setPath +* @public +*/ +var setPath = exports.setPath = function (path, is, opts) { + // we're trying to catch only null/undefined not empty string or 0 cases + if (!path || (!path && path !== null && path !== undefined)) return this; + + var next = this, + options = {create: true, silent: false, force: false}, + base = next, + parts, + part, + was, + force, + create, + silent, + comparator; + + if (typeof opts == 'object') opts = mixin({}, [options, opts]); + else { + force = opts; + opts = options; + } + + if (opts.force) force = true; + silent = opts.silent; + create = opts.create; + comparator = opts.comparator; + + + // obviously there is a severe penalty for requesting get with a path lead + // by unnecessary relative notation... + if (path[0] == '.') path = path.replace(/^\.+/, ''); + + // here's where we check to make sure we have a truthy string-ish + if (!path) return next; + + parts = path.split('.'); + part = parts.shift(); + + do { + + if (!parts.length) was = next.get && next.get !== getPath? next.get(part): next[part]; + else { + // this allows us to ensure that if we're setting a static property of a constructor we have the + // correct constructor + // @TODO: It seems ludicrous to have to check this on every single part of a chain; if we didn't have + // deferred constructors this wouldn't be necessary and is expensive - unnecessarily so when speed is so important + if (next !== base && next.set && next.set !== setPath) { + parts.unshift(part); + next.set(parts.join('.'), is, opts); + return base; + } + if (next !== base && next.get) next = (next.get !== getPath? next.get(part): next[part]) || (create && (next[part] = {})); + else next = next[part] || (create && (next[part] = {})); + } + + } while (next && parts.length && (part = parts.shift())); + + if (!next) return base; + + // now update to the new value + if (next !== base && next.set && next.set !== setPath) { + next.set(part, is, opts); + return base; + } else next[part] = is; + + // if possible we notify the changes but this change is notified from the immediate + // parent not the root object (could be the same) + if (next.notify && !silent && (force || was !== is || (comparator && comparator(was, is)))) next.notify(part, was, is, opts); + // we will always return the original root-object of the call + return base; +}; + +/** +* @private +*/ +setPath.fast = function (path, value) { + // the current context + var b = this, + // the previous value and helper variable + rv, fn; + // we have to check and ensure that we're not setting a computed property + // and if we are, do nothing + if (b._computed && b._computed[path] !== undefined) { + return b; + } + if (b._getters && (fn=b._getters[path])) { + rv = b[fn](); + } else { + rv = b[path]; + } + // set the new value now that we can + b[path] = value; + + // this method is only ever called from the context of enyo objects + // as a protected method + if (rv !== value) { b.notifyObservers(path, rv, value); } + // return the context + return b; +}; + +// ---------------------------------- +// String Functions +// ---------------------------------- + +/** +* Uppercases a given string. Will coerce to a [String]{@glossary String} +* if possible/necessary. +* +* @param {String} str - The string to uppercase. +* @returns {String} The uppercased string. +* @public +*/ +exports.toUpperCase = new exports.Extensible(function (str) { + if (str != null) { + return str.toString().toUpperCase(); + } + return str; +}); + +/** +* Lowercases a given string. Will coerce to a [String]{@glossary String} +* if possible/necessary. +* +* @param {String} str - The string to lowercase. +* @returns {String} The lowercased string. +* @public +*/ +exports.toLowerCase = new exports.Extensible(function (str) { + if (str != null) { + return str.toString().toLowerCase(); + } + return str; +}); + +/** +* Capitalizes a given string. +* +* @param {String} str - The string to capitalize. +* @returns {String} The capitalized string. +* @public +*/ +exports.cap = function (str) { + return str.slice(0, 1).toUpperCase() + str.slice(1); +}; + +/** +* Un-capitalizes a given string. +* +* @param {String} str - The string to un-capitalize. +* @returns {String} The un-capitalized string. +* @public +*/ +exports.uncap = function (str) { + return str.slice(0, 1).toLowerCase() + str.slice(1); +}; + +/** +* Injects an arbitrary number of values, in order, into a template string at +* positions marked by `"%."`. +* +* @param {String} template - The string template to inject with values. +* @param {...String} val The values to inject into the template. +* @returns {String} A copy of the template populated with values. +* @public +*/ +exports.format = function (template) { + var pattern = /\%./g, + arg = 0, + tmp = template, + args = arguments, + replacer; + + replacer = function () { + return args[++arg]; + }; + + return tmp.replace(pattern, replacer); +}; + +/** +* @private +*/ +String.prototype.trim = String.prototype.trim || function () { + return this.replace(/^\s+|\s+$/g, ''); +}; + +/** +* Takes a string and trims leading and trailing spaces. Strings with no length, +* non-strings, and falsy values will be returned without modification. +* +* @param {String} str - The string from which to remove whitespace. +* @returns {String} The trimmed string. +* @public +*/ +exports.trim = function (str) { + return (typeof str == 'string' && str.trim()) || str; +}; + +// ---------------------------------- +// Object Functions +// ---------------------------------- + +/** +* A [polyfill]{@glossary polyfill} for platforms that don't support +* [Object.create()]{@glossary Object.create}. +*/ +Object.create = Object.create || (function () { + var Anon = function () {}; + return function (obj) { + // in the polyfill we can't support the additional features so we are ignoring + // the extra parameters + if (!obj || obj === null || typeof obj != 'object') throw 'Object.create: Invalid parameter'; + Anon.prototype = obj; + return new Anon(); + }; +})(); + +/** +* A [polyfill]{@glossary polyfill} for platforms that don't support +* [Object.keys()]{@glossary Object.keys}. +*/ +Object.keys = Object.keys || function (obj) { + var results = []; + var hop = Object.prototype.hasOwnProperty; + for (var prop in obj) { + if (hop.call(obj, prop)) { + results.push(prop); + } + } + // *sigh* IE 8 + if (!({toString: null}).propertyIsEnumerable('toString')) { + var dontEnums = [ + 'toString', + 'toLocaleString', + 'valueOf', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'constructor' + ]; + for (var i = 0, p; (p = dontEnums[i]); i++) { + if (hop.call(obj, p)) { + results.push(p); + } + } + } + return results; +}; + +/** +* Returns an array of all known enumerable properties found on a given object. +* +* @alias Object.keys. +* @method enyo.keys +* @public +*/ +exports.keys = Object.keys; + +/** +* Convenience method that takes an [array]{@glossary Array} of properties +* and an [object]{@glossary Object} as parameters. Returns a new object +* with only those properties named in the array that are found to exist on the +* base object. If the third parameter is `true`, falsy values will be ignored. +* +* @param {String[]} properties The properties to include on the returned object. +* @param {Object} object - The object from which to retrieve values for the requested properties. +* @param {Boolean} [ignore=false] Whether or not to ignore copying falsy values. +* @returns {Object} A new object populated with the requested properties and values from +* the given object. +* @public +*/ +exports.only = function (properties, object, ignore) { + var ret = {}, + prop, + len, + i; + + for (i = 0, len = properties.length >>> 0; i < len; ++i) { + prop = properties[i]; + + if (ignore && (object[prop] === undefined || object[prop] === null)) continue; + ret[prop] = object[prop]; + } + + return ret; +}; + +/** +* Convenience method that takes two [objects]{@glossary Object} as parameters. +* For each key from the first object, if the key also exists in the second object, +* a mapping of the key from the first object to the key from the second object is +* added to a result object, which is eventually returned. In other words, the +* returned object maps the named properties of the first object to the named +* properties of the second object. The optional third parameter is a boolean +* designating whether to pass unknown key/value pairs through to the new object. +* If `true`, those keys will exist on the returned object. +* +* @param {Object} map - The object with key/value pairs. +* @param {Object} obj - The object whose values will be used. +* @param {Boolean} [pass=false] Whether or not to pass unnamed properties through +* from the given object. +* @returns {Object} A new object whose properties have been mapped. +* @public +*/ +exports.remap = function (map, obj, pass) { + var ret = pass ? clone(obj) : {}, + key; + + for (key in map) { + if (key in obj) ret[map[key]] = obj.get ? obj.get(key) : obj[key]; + } + return ret; +}; + +/** +* Helper method that accepts an [array]{@glossary Array} of +* [objects]{@glossary Object} and returns a hash of those objects indexed +* by the specified `property`. If a `filter` is provided, the filter should +* accept four parameters: the key, the value (object), the current mutable map +* reference, and an immutable copy of the original array of objects for +* comparison. +* +* @param {String} property - The property to index the array by. +* @param {Array} array - An array of property objects. +* @param {Function} [filter] - The filter function to use; accepts four arguments. +* @returns {Object} A hash (object) indexed by the `property` argument +* @public +*/ +exports.indexBy = function (property, array, filter) { + // the return value - indexed map from the given array + var map = {}, + value, + len, + idx = 0; + // sanity check for the array with an efficient native array check + if (!exists(array) || !(array instanceof Array)) { + return map; + } + // sanity check the property as a string + if (!exists(property) || 'string' !== typeof property) { + return map; + } + // the immutable copy of the array + var copy = clone(array); + // test to see if filter actually exsits + filter = exists(filter) && 'function' === typeof filter ? filter : undefined; + for (len = array.length; idx < len; ++idx) { + // grab the value from the array + value = array[idx]; + // make sure that it exists and has the requested property at all + if (exists(value) && exists(value[property])) { + if (filter) { + // if there was a filter use it - it is responsible for + // updating the map accordingly + filter(property, value, map, copy); + } else { + // use the default behavior - check to see if the key + // already exists on the map it will be overwritten + map[value[property]] = value; + } + } + } + // go ahead and return our modified map + return map; +}; + +/** +* Creates and returns a shallow copy of an [Object]{@glossary Object} or an +* [Array]{@glossary Array}. For objects, by default, properties will be scanned and +* copied directly to the clone such that they would pass the +* [hasOwnProperty()]{@glossary Object.hasOwnProperty} test. This is expensive and often not +* required. In this case, the optional second parameter may be used to allow a more efficient +* [copy]{@link Object.create} to be made. +* +* @param {(Object|Array)} base - The [Object]{@glossary Object} or +* [Array]{@glossary Array} to be cloned. +* @param {Boolean} [quick] - If `true`, when cloning objects, a faster [copy]{@link Object.create} +* method will be used. This parameter has no meaning when cloning arrays. +* @returns {*} A clone of the provided `base` if `base` is of the correct type; otherwise, +* returns `base` as it was passed in. +* @public +*/ +var clone = exports.clone = function (base, quick) { + if (base) { + + // avoid the overhead of calling yet another internal function to do type-checking + // just copy the array and be done with it + if (base instanceof Array) return base.slice(); + else if (base instanceof Object) { + return quick ? Object.create(base) : mixin({}, base); + } + } + + // we will only do this if it is not an array or native object + return base; +}; + +var empty = {}; +var mixinDefaults = { + exists: false, + ignore: false, + filter: null +}; + +/** + @todo Rewrite with appropriate documentation for options parameter (typedef) + @todo document 'quick' option + + Will take a variety of options to ultimately mix a set of properties + from objects into single object. All configurations accept a boolean as + the final parameter to indicate whether or not to ignore _truthy_/_existing_ + values on any _objects_ prior. + + If _target_ exists and is an object, it will be the base for all properties + and the returned value. If the parameter is used but is _falsy_, a new + object will be created and returned. If no such parameter exists, the first + parameter must be an array of objects and a new object will be created as + the _target_. + + The _source_ parameter may be an object or an array of objects. If no + _target_ parameter is provided, _source_ must be an array of objects. + + The _options_ parameter allows you to set the _ignore_ and/or _exists_ flags + such that if _ignore_ is true, it will not override any truthy values in the + target, and if _exists_ is true, it will only use truthy values from any of + the sources. You may optionally add a _filter_ method-option that returns a + true or false value to indicate whether the value should be used. It receives + parameters in this order: _property_, _source value_, _source values_, + _target_, _options_. Note that modifying the target in the filter method can + have unexpected results. + + Setting _options_ to true will set all options to true. + +* @method enyo.mixin +* @public +*/ +var mixin = exports.mixin = function () { + var ret = arguments[0], + src = arguments[1], + opts = arguments[2], + val; + + if (!ret) ret = {}; + else if (ret instanceof Array) { + opts = src; + src = ret; + ret = {}; + } + + if (!opts || opts === true) opts = mixinDefaults; + + if (src instanceof Array) for (var i=0, it; (it=src[i]); ++i) mixin(ret, it, opts); + else { + for (var key in src) { + val = src[key]; + + // quickly ensure the property isn't a default + if (empty[key] !== val) { + if ( + (!opts.exists || val) && + (!opts.ignore || !ret[key]) && + (opts.filter? opts.filter(key, val, src, ret, opts): true) + ) { + ret[key] = val; + } + } + } + } + + return ret; +}; + +/** +* Returns an [array]{@glossary Array} of the values of all properties in an +* [object]{@glossary Object}. +* +* @param {Object} obj - The [Object]{@glossary Object} to read the values from. +* @returns {Array} An [array]{@glossary Array} with the values from the `obj`. +* @public +*/ +exports.values = function (obj) { + var ret = []; + for (var key in obj) { + if (obj.hasOwnProperty(key)) ret.push(obj[key]); + } + return ret; +}; + +// ---------------------------------- +// Array Functions +// ---------------------------------- + +/** +* Because our older API parameters are not consistent with other array API methods, and also +* because only [IE8 lacks integrated support]{@glossary polyfill} for +* [indexOf()]{@linkcode external:Array.indexOf}, we ensure it is defined (only IE8) and advise, +* moving forward, that the built-in method be used. But to preserve our original API, it will +* simply call this method, knowing it exists. +* +* @private +*/ +Array.prototype.indexOf = Array.prototype.indexOf || function (el, offset) { + var len = this.length >>> 0; + + offset = +offset || 0; + + if (Math.abs(offset) === Infinity) offset = 0; + if (offset < 0) offset += len; + if (offset < 0) offset = 0; + + for (; offset < len; ++offset) { + if (this[offset] === el) return offset; + } + + return -1; +}; + +/** +* Because our older API parameters are not consistent with other array API methods, and also +* because only [IE8 lacks integrated support]{@glossary polyfill} for +* [lastIndexOf()]{@glossary Array.lastIndexOf} we ensure it is defined (only IE8) and +* advise, moving forward, that the built-in method be used. But to preserve our original API, it +* will simply call this method, knowing it exists. +* +* @private +*/ +Array.prototype.lastIndexOf = Array.prototype.lastIndexOf || function (el, offset) { + var array = Object(this) + , len = array.length >>> 0; + + if (len === 0) return -1; + + if (offset !== undefined) { + offset = Number(offset); + if (Math.abs(offset) > len) offset = len; + if (offset === Infinity || offset === -Infinity) offset = len; + if (offset < 0) offset += len; + } else offset = len; + + for (; offset > -1; --offset) { + if (array[offset] === el) return offset; + } + + return -1; +}; + +/** +* A [polyfill]{@glossary polyfill} for platforms that don't support +* [Array.findIndex()]{@glossary Array.findIndex}. +*/ +Array.prototype.findIndex = Array.prototype.findIndex || function (fn, ctx) { + for (var i=0, len=this.length >>> 0; i>> 0; i>> 0; i>> 0; i>> 0; i>> 0; i>> 0; i -1) roots.splice(idx, 1); + + // now we can call the original + destroy.apply(this, arguments); + }; + } +}; + +}],'enyo/AnimationSupport/Matrix':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Matrix module for matrix related calculation +* +* @module enyo/AnimationSupport/Matrix +*/ +module.exports = { + /** + * @public + */ + identity: function() { + return [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]; + }, + + /** + * @public + */ + translate: function (x, y, z) { + return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y ? y : 0, z ? z : 0, 1]; + }, + + /** + * @public + */ + translateX: function (x) { + return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x ? x : 0, 0, 0, 1]; + }, + + /** + * @public + */ + translateY: function (y) { + return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, y ? y : 0, 0, 1]; + }, + + /** + * @public + */ + translateZ: function (z) { + return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, z ? z : 0, 1]; + }, + + /** + * @public + */ + scale: function (x, y, z) { + return [x, 0, 0, 0, 0, y ? y : 1, 0, 0, 0, 0, z ? z : 1, 0, 0, 0, 0, 1]; + }, + + /** + * @public + */ + skew: function (a, b) { + a = a ? Math.tan(a * Math.PI / 180): 0; + b = b ? Math.tan(b * Math.PI / 180): 0; + return [1, b, 0, 0, a, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + }, + + /** + * @public + */ + rotateX: function (a) { + var cosa, sina; + a = a * Math.PI / 180; + cosa = Math.cos(a); + sina = Math.sin(a); + return [1, 0, 0, 0, 0, cosa, -sina, 0, 0, sina, cosa, 0, 0, 0, 0, 1]; + }, + + /** + * @public + */ + rotateY: function (b) { + var cosb, sinb; + b = b * Math.PI / 180; + cosb = Math.cos(b); + sinb = Math.sin(b); + return [cosb, 0, sinb, 0, 0, 1, 0, 0, -sinb, 0, cosb, 0, 0, 0, 0, 1]; + }, + + /** + * @public + */ + rotateZ: function (g) { + var cosg, sing; + g = g * Math.PI / 180; + cosg = Math.cos(g); + sing = Math.sin(g); + return [cosg, -sing, 0, 0, sing, cosg, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + }, + + /** + * @public + */ + rotate: function (a, b, g) { + a = a * Math.PI / 180; + b = b * Math.PI / 180; + g = g * Math.PI / 180; + var ca = Math.cos(a); + var sa = Math.sin(a); + var cb = Math.cos(b); + var sb = Math.sin(b); + var cg = Math.cos(g); + var sg = Math.sin(g); + var m = [ + cb * cg, + ca * sg + sa * sb * cg, + sa * sg - ca * sb * cg, + 0, + -cb * sg, + ca * cg - sa * sb * sg, + sa * cg + ca * sb * sg, + 0, + sb, + -sa * cb, + ca * cb, + 0, + 0, 0, 0, 1 + ]; + return m; + }, + + /** + * @public + */ + multiply: function(m1, m2) { + return [ + m1[0] * m2[0] + m1[4] * m2[1] + m1[8] * m2[2], + m1[1] * m2[0] + m1[5] * m2[1] + m1[9] * m2[2], + m1[2] * m2[0] + m1[6] * m2[1] + m1[10] * m2[2], + 0, + m1[0] * m2[4] + m1[4] * m2[5] + m1[8] * m2[6], + m1[1] * m2[4] + m1[5] * m2[5] + m1[9] * m2[6], + m1[2] * m2[4] + m1[6] * m2[5] + m1[10] * m2[6], + 0, + m1[0] * m2[8] + m1[4] * m2[9] + m1[8] * m2[10], + m1[1] * m2[8] + m1[5] * m2[9] + m1[9] * m2[10], + m1[2] * m2[8] + m1[6] * m2[9] + m1[10] * m2[10], + 0, + m1[0] * m2[12] + m1[4] * m2[13] + m1[8] * m2[14] + m1[12], + m1[1] * m2[12] + m1[5] * m2[13] + m1[9] * m2[14] + m1[13], + m1[2] * m2[12] + m1[6] * m2[13] + m1[10] * m2[14] + m1[14], + 1 + ]; + }, + + /** + * @public + */ + multiplyN: function(m1, m2) { + var i, j, sum, + m = [], + l1 = m1.length, + l2 = m2.length; + + for (i = 0; i < l1; i++) { + sum = 0; + for (j = 0; j < l2; j++) { + sum += m1[i][j] * m2[j]; + } + m.push(sum); + } + return m; + }, + + /** + * @public + */ + inverseN: function(matrix, n) { + var i, j, k, r, t, + precision = 100000, + result = [], + row = []; + for (i = 0; i < n; i++) { + for (j = n; j < 2 * n; j++) { + if (i == (j - n)) matrix[i][j] = 1.0; + else matrix[i][j] = 0.0; + } + } + + for (i = 0; i < n; i++) { + for (j = 0; j < n; j++) { + if (i != j) { + r = matrix[j][i] / matrix[i][i]; + r = Math.round(r * precision) / precision; + for (k = 0; k < 2 * n; k++) { + t = Math.round(matrix[j][k] * precision) / precision; + t -= Math.round((r * matrix[i][k]) * precision) / precision; + matrix[j][k] = t; + } + } + } + } + + for (i = 0; i < n; i++) { + t = matrix[i][i]; + for (j = 0; j < 2 * n; j++) { + matrix[i][j] = matrix[i][j] / t; + } + } + + for (i = 0; i < n; i++) { + row = []; + for (k = 0, j = n; j < 2 * n; j++, k++) { + row.push(matrix[i][j]); + } + result.push(row); + } + + return result; + }, + + /** + * @public + */ + toString: function (m) { + var ms = 'matrix3d('; + for (var i = 0; i < 15; i++) { + ms += (m[i] < 0.000001 && m[i] > -0.000001) ? '0,' : m[i] + ','; + } + ms += m[15] + ')'; + return ms; + } +}; +}],'enyo/AnimationSupport/Vector':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Vector module for vector related calculations. +* Also provides API's for Quaternion vectors for rotation. +* +* @module enyo/AnimationSupport/Vector +*/ +module.exports = { + /** + * Divides vector with a scalar value. + * @public + */ + divide: function (q, s) { + return [q[0] / s, q[1] / s, q[2] / s]; + }, + + /** + * Length of 3D vectors + * @public + */ + len: function (q) { + return Math.sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2]); + }, + + /** + * Dot product of 3D vectors + * @public + */ + dot: function (q1, q2) { + return (q1[0] * q2[0]) + (q1[1] * q2[1]) + (q1[2] * q2[2]) + q1[3] && q2[3] ? (q1[3] * q2[3]) : 0; + }, + + /** + * Cross product of two vectors + * @public + */ + cross: function (q1, q2) { + return [ + q1[1] * q2[2] - q1[2] * q2[1], + q1[2] * q2[0] - q1[0] * q2[2], + q1[0] * q2[1] - q1[1] * q2[0] + ]; + }, + + /** + * Normalizing a vector is obtaining another unit vector in the same direction. + * To normalize a vector, divide the vector by its magnitude. + * @public + */ + normalize: function (q) { + return this.divide(q, this.len(q)); + }, + + /** + * Combine scalar values with two vectors. + * Required during parsing scaler values matrix. + * @public + */ + combine: function (a, b, ascl, bscl) { + return [ + (ascl * a[0]) + (bscl * b[0]), + (ascl * a[1]) + (bscl * b[1]), + (ascl * a[2]) + (bscl * b[2]) + ]; + }, + + /** + * Converts a quaternion vector to a rotation vector. + * @public + */ + toVector: function (rv) { + var r = 2 * Math.acos(rv[3]); + var sA = Math.sqrt(1.0 - rv[3] * rv[3]); + if (Math.abs(sA) < 0.0005) sA = 1; + return [rv[0] / sA, rv[1] / sA, rv[2] / sA, r * 180 / Math.PI]; + }, + + /** + * Converts a rotation vector to a quaternion vector. + * @public + */ + toQuant: function (q) { + if (!q) q = []; + + var x = q[0] || 0, + y = q[1] || 0, + z = q[2] || 0, + deg = q[3] || 0, + r = deg * (Math.PI / 360), + sR = Math.sin(r), + cR = Math.cos(r); + + q[0] = x * sR; + q[1] = y * sR; + q[2] = z * sR; + q[3] = cR; + return q; + } +}; +}],'enyo/ModelList':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/ModelList~ModelList} Object. +* @module enyo/ModelList +*/ + +/** +* A special type of [array]{@glossary Array} used internally by data layer +* [kinds]{@glossary kind}. +* +* @class ModelList +* @protected +*/ +function ModelList (args) { + Array.call(this); + this.table = {}; + if (args) this.add(args, 0); +} + +ModelList.prototype = Object.create(Array.prototype); + +module.exports = ModelList; + +/** +* Adds [models]{@link module:enyo/Model~Model} to the [list]{@link module:enyo/ModelList~ModelList}, updating an +* internal table by the model's [primaryKey]{@link module:enyo/Model~Model#primaryKey} (if +* possible) and its [euid]{@glossary euid}. +* +* @name module:enyo/ModelList~ModelList#add +* @method +* @param {(module:enyo/Model~Model|module:enyo/Model~Model[])} models The [model or models]{@link module:enyo/Model~Model} +* to add to the [list]{@link module:enyo/ModelList~ModelList}. +* @param {Number} [idx] - If provided and valid, the models will be +* [spliced]{@glossary Array.splice} into the list at this position. +* @returns {module:enyo/Model~Model[]} An immutable [array]{@glossary Array} of models +* that were actually added to the list. +* @protected +*/ +ModelList.prototype.add = function (models, idx) { + var table = this.table, + added = [], + model, + euid, + id, + i = 0; + + if (models && !(models instanceof Array)) models = [models]; + + for (; (model = models[i]); ++i) { + euid = model.euid; + + // we only want to actually add models we haven't already seen... + if (!table[euid]) { + id = model.get(model.primaryKey); + + if (id != null) { + + // @TODO: For now if we already have an entry for a model by its supposed unique + // identifier but it isn't the instance we just found we can't just + // overwrite the previous instance so we mark the new one as headless + if (table[id] && table[id] !== model) model.headless = true; + // otherwise we do the normal thing and add the entry for it + else table[id] = model; + } + + // nomatter what though the euid should be unique + table[euid] = model; + added.push(model); + } + } + + if (added.length) { + idx = !isNaN(idx) ? Math.min(Math.max(0, idx), this.length) : 0; + added.unshift(0); + added.unshift(idx); + this.splice.apply(this, added); + } + + if (added.length > 0) added = added.slice(2); + added.at = idx; + + return added; +}; + +/** +* Removes the specified [models]{@link module:enyo/Model~Model} from the [list]{@link module:enyo/ModelList~ModelList}. +* +* @name module:enyo/ModelList~ModelList#remove +* @method +* @param {(module:enyo/Model~Model|module:enyo/Model~Model[])} models The [model or models]{@link module:enyo/Model~Model} +* to remove from the [list]{@link module:enyo/ModelList~ModelList}. +* @returns {module:enyo/Model~Model[]} An immutable [array]{@glossary Array} of +* models that were actually removed from the list. +* @protected +*/ +ModelList.prototype.remove = function (models) { + var table = this.table, + removed = [], + model, + idx, + id, + i, + + // these modifications are made to allow more performant logic to take place in + // views that may need to know this information + low = Infinity, + high = -1, + indices = []; + + if (models && !(models instanceof Array)) models = [models]; + + // we start at the end to ensure that you could even pass the list itself + // and it will work + for (i = models.length - 1; (model = models[i]); --i) { + table[model.euid] = null; + id = model.get(model.primaryKey); + + if (id != null) table[id] = null; + + idx = models === this ? i : this.indexOf(model); + if (idx > -1) { + if (idx < low) low = idx; + if (idx > high) high = idx; + + this.splice(idx, 1); + removed.push(model); + indices.push(idx); + } + } + + // since this is a separate array we will add this property to it for internal use only + removed.low = low; + removed.high = high; + removed.indices = indices; + + return removed; +}; + +/** +* Determines whether the specified [model]{@link module:enyo/Model~Model} is present in the +* [list]{@link module:enyo/ModelList~ModelList}. Will attempt to resolve a [string]{@glossary String} +* or [number]{@glossary Number} to either a [primaryKey]{@link module:enyo/Model~Model#primaryKey} +* or [euid]{@glossary euid}. +* +* @name module:enyo/ModelList~ModelList#has +* @method +* @param {(module:enyo/Model~Model|String|Number)} model An identifier representing either the +* [model]{@link module:enyo/Model~Model} instance, its [primaryKey]{@link module:enyo/Model~Model#primaryKey}, +* or its [euid]{@glossary euid}. +* @returns {Boolean} Whether or not the model is present in the [list]{@link module:enyo/ModelList~ModelList}. +* @protected +*/ +ModelList.prototype.has = function (model) { + if (model === undefined || model === null) return false; + + if (typeof model == 'string' || typeof model == 'number') { + return !! this.table[model]; + } else return this.indexOf(model) > -1; +}; + +/** +* Given an identifier, attempts to return the associated [model]{@link module:enyo/Model~Model}. +* The identifier should be a [string]{@glossary String} or [number]{@glossary Number}. +* +* @name module:enyo/ModelList~ModelList#resolve +* @method +* @param {(String|Number)} model - An identifier (either a +* [primaryKey]{@link module:enyo/Model~Model#primaryKey} or an [euid]{@glossary euid}). +* @returns {(undefined|null|module:enyo/Model~Model)} If the identifier could be resolved, a +* [model]{@link module:enyo/Model~Model} instance is returned; otherwise, `undefined`, or +* possibly `null` if the model once belonged to the [list]{@link module:enyo/ModelList~ModelList}. +* @protected +*/ +ModelList.prototype.resolve = function (model) { + if (typeof model == 'string' || typeof model == 'number') { + return this.table[model]; + } else return model; +}; + +/** +* Copies the current [list]{@link module:enyo/ModelList~ModelList} and returns an shallow copy. This +* method differs from the [slice()]{@glossary Array.slice} method inherited from +* native [Array]{@glossary Array} in that this returns an {@link module:enyo/ModelList~ModelList}, +* while `slice()` returns an array. +* +* @name module:enyo/ModelList~ModelList#copy +* @method +* @returns {module:enyo/ModelList~ModelList} A shallow copy of the callee. +* @protected +*/ +ModelList.prototype.copy = function () { + return new ModelList(this); +}; + +}],'enyo/States':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Shared values for various [kinds]{@glossary kind} used to indicate a state or +* (multiple, simultaneous) states. These flags are binary values represented by +* hexadecimal numerals. They may be modified and compared (or even extended) using +* [bitwise operations]{@glossary bitwise} or various +* [API methods]{@link module:enyo/StateSupport~StateSupport} available to the kinds that support them. +* Make sure to explore the documentation for individual kinds, as they may have +* specific uses for a given flag. +* +* As a cursory overview, here is a table of the values already declared by built-in flags. +* Each hexadecimal numeral represents a unique power of 2 in binary, from which we can use +* [bitwise masks]{@glossary bitwise} to determine if a particular value is present. +* +* ```javascript +* HEX DEC BIN +* 0x0001 1 0000 0000 0000 0001 +* 0x0002 2 0000 0000 0000 0010 +* 0x0004 4 0000 0000 0000 0100 +* 0x0008 8 0000 0000 0000 1000 +* 0x0010 16 0000 0000 0001 0000 +* 0x0020 32 0000 0000 0010 0000 +* 0x0040 64 0000 0000 0100 0000 +* 0x0080 128 0000 0000 1000 0000 +* 0x0100 256 0000 0001 0000 0000 +* 0x0200 512 0000 0010 0000 0000 +* 0x0400 1024 0000 0100 0000 0000 +* 0x0800 2048 0000 1000 0000 0000 +* +* ... +* +* 0x1000 4096 0001 0000 0000 0000 +* ``` +* +* As a hint, converting (HEX) 0x0800 to DEC do: +* +* ```javascript +* (0*16^3) + (8*16^2) + (0*16^1) + (0*16^0) = 2048 +* ``` +* +* As a hint, converting (HEX) 0x0800 to BIN do: +* +* ```javascript +* 0 8 0 0 (HEX) +* ---- ---- ---- ---- +* 0000 1000 0000 0000 (BIN) +* ``` +* +* @module enyo/States +* @public +* @see module:enyo/StateSupport~StateSupport +*/ +module.exports = { + + /** + * Only exists in the client and was created during the runtime of the + * [application]{@glossary application}. + * + * @type {Number} + * @default 1 + */ + NEW: 0x0001, + + /** + * Has been modified locally only. + * + * @type {Number} + * @default 2 + */ + DIRTY: 0x0002, + + /** + * Has not been modified locally. + * + * @type {Number} + * @default 4 + */ + CLEAN: 0x0004, + + /** + * Can no longer be modified. + * @type {Number} + * @default 8 + */ + DESTROYED: 0x0008, + + /** + * Currently attempting to fetch. + * + * @see module:enyo/Model~Model#fetch + * @see module:enyo/RelationalModel~RelationalModel#fetch + * @see module:enyo/Collection~Collection#fetch + * + * @type {Number} + * @default 16 + */ + FETCHING: 0x0010, + + /** + * Currently attempting to commit. + * + * @see module:enyo/Model~Model#commit + * @see module:enyo/RelationalModel~RelationalModel#commit + * @see module:enyo/Collection~Collection#commit + * + * @type {Number} + * @default 32 + */ + COMMITTING: 0x0020, + + /** + * Currently attempting to destroy. + * + * @see module:enyo/Model~Model#destroy + * @see module:enyo/RelationalModel~RelationalModel#destroy + * @see module:enyo/Collection~Collection#destroy + * + * @type {Number} + * @default 64 + */ + DESTROYING: 0x0040, + + /** + * There was an error during commit. + * + * @see module:enyo/Model~Model#commit + * @see module:enyo/RelationalModel~RelationalModel#commit + * @see module:enyo/Collection~Collection#commit + * + * @type {Number} + * @default 128 + */ + ERROR_COMMITTING: 0x0080, + + /** + * There was an error during fetch. + * + * @see module:enyo/Model~Model#fetch + * @see module:enyo/RelationalModel~RelationalModel#fetch + * @see module:enyo/Collection~Collection#fetch + * + * @type {Number} + * @default 256 + */ + ERROR_FETCHING: 0x0100, + + /** + * There was an error during destroy. + * + * @see module:enyo/Model~Model#destroy + * @see module:enyo/RelationalModel~RelationalModel#destroy + * @see module:enyo/Collection~Collection#destroy + * + * @type {Number} + * @default 512 + */ + ERROR_DESTROYING: 0x0200, + + /** + * An error was encountered for which there was no exact flag, or an invalid error was + * specified. + * + * @type {Number} + * @default 1024 + */ + ERROR_UNKNOWN: 0x0400, + + /** + * A multi-state [bitmask]{@glossary bitwise}. Compares a given flag to the states + * included in the definition of `BUSY`. By default, this is one of + * [FETCHING]{@link module:enyo/States.FETCHING}, [COMMITTING]{@link module:enyo/States.COMMITTING}, or + * [DESTROYING]{@link module:enyo/States.DESTROYING}. It may be extended to include additional + * values using the [bitwise]{@glossary bitwise} `OR` operator (`|`). + * + * @type {Number} + * @default 112 + */ + BUSY: 0x0010 | 0x0020 | 0x0040, + + /** + * A multi-state [bitmask]{@glossary bitwise}. Compares a given flag to the states + * included in the definition of `ERROR`. By default, this is one of + * [ERROR_FETCHING]{@link module:enyo/States.ERROR_FETCHING}, + * [ERROR_COMMITTING]{@link module:enyo/States.ERROR_COMMITTING}, + * [ERROR_DESTROYING]{@link module:enyo/States.ERROR_DESTROYING}, or + * [ERROR_UNKNOWN]{@link module:enyo/States.ERROR_UNKNOWN}. It may be extended to include + * additional values using the [bitwise]{@glossary bitwise} `OR` operator (`|`). + * + * @type {Number} + * @default 1920 + */ + ERROR: 0x0080 | 0x0100 | 0x0200 | 0x0400, + + /** + * A multi-state [bitmask]{@glossary bitwise}. Compares a given flag to the states + * included in the definition of `READY`. By default, this is the inverse of any + * values included in [BUSY]{@link module:enyo/States.BUSY} or [ERROR]{@link module:enyo/States.ERROR}. + * + * @type {Number} + * @default -2041 + */ + READY: ~(0x0008 | 0x0010 | 0x0020 | 0x0040 | 0x0080 | 0x0100 | 0x0200 | 0x0400) +}; + +}],'enyo/job':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains methods for dealing with jobs. +* @module enyo/job +*/ +var _jobs = {}; + +/** +* Runs a job after the specified amount of time has elapsed +* since a job with the same name has run. +* +* Jobs can be used to throttle behaviors. If some event may occur one time or +* multiple times, but we want a response to occur only once every `n` seconds, +* we can use a job. +* +* @example +* onscroll: function() { +* // updateThumb will be called, but only when 1 second has elapsed since the +* // last onscroll +* exports("updateThumb", this.bindSafely("updateThumb"), 1000); +* } +* +* @param {String} nom - The name of the job to throttle. +* @param {(Function|String)} job - Either the name of a method or a [function]{@glossary Function} +* to execute as the requested job. +* @param {Number} wait - The number of milliseconds to wait before executing the job again. +* @function +* @public +*/ +exports = module.exports = function (nom, job, wait) { + exports.stop(nom); + _jobs[nom] = setTimeout(function() { + exports.stop(nom); + job(); + }, wait); +}; + +/** +* Cancels the named job, if it has not already fired. +* +* @param {String} nom - The name of the job to cancel. +* @static +* @public +*/ +exports.stop = function (nom) { + if (_jobs[nom]) { + clearTimeout(_jobs[nom]); + delete _jobs[nom]; + } +}; + +/** +* Immediately invokes the job and prevents any other calls +* to `exports.throttle()` with the same job name from running for the +* specified amount of time. +* +* This is used for throttling user events when you want to provide an +* immediate response, but later invocations might just be noise if they arrive +* too often. +* +* @param {String} nom - The name of the job to throttle. +* @param {(Function|String)} job - Either the name of a method or a [function]{@glossary Function} +* to execute as the requested job. +* @param {Number} wait - The number of milliseconds to wait before executing the +* job again. +* @static +* @public +*/ +exports.throttle = function (nom, job, wait) { + // if we still have a job with this name pending, return immediately + if (_jobs[nom]) { + return; + } + job(); + _jobs[nom] = setTimeout(function() { + exports.stop(nom); + }, wait); +}; + +}],'enyo/platform':[function (module,exports,global,require,request){ +require('enyo'); + +var utils = require('./utils'); + +/** +* Determines OS versions of platforms that need special treatment. Can have one of the following +* properties: +* +* * android +* * androidChrome (Chrome on Android, standard starting in 4.1) +* * androidFirefox +* * ie +* * ios +* * webos +* * windowsPhone +* * blackberry +* * tizen +* * safari (desktop version) +* * chrome (desktop version) +* * firefox (desktop version) +* * firefoxOS +* +* If the property is defined, its value will be the major version number of the platform. +* +* Example: +* ```javascript +* // android 2 does not have 3d css +* if (enyo.platform.android < 3) { +* t = 'translate(30px, 50px)'; +* } else { +* t = 'translate3d(30px, 50px, 0)'; +* } +* this.applyStyle('-webkit-transform', t); +* ``` +* +* @module enyo/platform +*/ +exports = module.exports = + /** @lends module:enyo/platform~platform */ { + //* `true` if the platform has native single-finger [events]{@glossary event}. + touch: Boolean(('ontouchstart' in window) || window.navigator.msMaxTouchPoints), + //* `true` if the platform has native double-finger [events]{@glossary event}. + gesture: Boolean(('ongesturestart' in window) || window.navigator.msMaxTouchPoints) +}; + +/** +* @private +*/ +var ua = navigator.userAgent; +var ep = exports; +var platforms = [ + // Android 4+ using Chrome + {platform: 'androidChrome', regex: /Android .* Chrome\/(\d+)[.\d]+/}, + // Android 2 - 4 + {platform: 'android', regex: /Android (\d+)/}, + // Kindle Fire + // Force version to 2, (desktop mode does not list android version) + {platform: 'android', regex: /Silk\/1./, forceVersion: 2, extra: {silk: 1}}, + // Kindle Fire HD (Silk versions 2 or 3) + // Force version to 4 + {platform: 'android', regex: /Silk\/2./, forceVersion: 4, extra: {silk: 2}}, + {platform: 'android', regex: /Silk\/3./, forceVersion: 4, extra: {silk: 3}}, + // Windows Phone 7 - 8 + {platform: 'windowsPhone', regex: /Windows Phone (?:OS )?(\d+)[.\d]+/}, + // IE 8 - 10 + {platform: 'ie', regex: /MSIE (\d+)/}, + // IE 11 + {platform: 'ie', regex: /Trident\/.*; rv:(\d+)/}, + // iOS 3 - 5 + // Apple likes to make this complicated + {platform: 'ios', regex: /iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/}, + // webOS 1 - 3 + {platform: 'webos', regex: /(?:web|hpw)OS\/(\d+)/}, + // webOS 4 / OpenWebOS + {platform: 'webos', regex: /WebAppManager|Isis|webOS\./, forceVersion: 4}, + // Open webOS release LuneOS + {platform: 'webos', regex: /LuneOS/, forceVersion: 4, extra: {luneos: 1}}, + // desktop Safari + {platform: 'safari', regex: /Version\/(\d+)[.\d]+\s+Safari/}, + // desktop Chrome + {platform: 'chrome', regex: /Chrome\/(\d+)[.\d]+/}, + // Firefox on Android + {platform: 'androidFirefox', regex: /Android;.*Firefox\/(\d+)/}, + // FirefoxOS + {platform: 'firefoxOS', regex: /Mobile;.*Firefox\/(\d+)/}, + // desktop Firefox + {platform: 'firefox', regex: /Firefox\/(\d+)/}, + // Blackberry Playbook + {platform: 'blackberry', regex: /PlayBook/i, forceVersion: 2}, + // Blackberry 10+ + {platform: 'blackberry', regex: /BB1\d;.*Version\/(\d+\.\d+)/}, + // Tizen + {platform: 'tizen', regex: /Tizen (\d+)/} +]; +for (var i = 0, p, m, v; (p = platforms[i]); i++) { + m = p.regex.exec(ua); + if (m) { + if (p.forceVersion) { + v = p.forceVersion; + } else { + v = Number(m[1]); + } + ep[p.platform] = v; + if (p.extra) { + utils.mixin(ep, p.extra); + } + ep.platformName = p.platform; + break; + } +} + +},{'./utils':'enyo/utils'}],'enyo/EventEmitter':[function (module,exports,global,require,request){ +/** +* Exports the {@link module:enyo/EventEmitter~EventEmitter} mixin. +* @module enyo/EventEmitter +*/ + +require('enyo'); + +var + utils = require('./utils'); + +var + eventTable = {}; + +/** +* @private +*/ +function addListener(obj, e, fn, ctx) { + + obj.listeners().push({ + event: e, + method: fn, + ctx: ctx || obj + }); + + return obj; +} + +/** +* @private +*/ +function removeListener(obj, e, fn, ctx) { + var listeners = obj.listeners() + , idx; + + if (listeners.length) { + idx = listeners.findIndex(function (ln) { + return ln.event == e && ln.method === fn && (ctx? ln.ctx === ctx: true); + }); + idx >= 0 && listeners.splice(idx, 1); + } + + return obj; +} + +/** +* @private +*/ +function emit(obj, args) { + var len = args.length + , e = args[0] + , listeners = obj.listeners(e); + + if (listeners.length) { + if (len > 1) { + args = utils.toArray(args); + args.unshift(obj); + } else { + args = [obj, e]; + } + + for (var i=0, ln; (ln=listeners[i]); ++i) ln.method.apply(ln.ctx, args); + + return true; + } + + return false; +} + +/** +* {@link module:enyo/EventEmitter~EventEmitter} is a {@glossary mixin} that adds support for +* registered {@glossary event} listeners. These events are different from +* bubbled events (e.g., DOM events and [handlers]{@link module:enyo/Component~Component#handlers}). +* When [emitted]{@link module:enyo/EventEmitter~EventEmitter#emit}, these events **do not bubble** +* and will only be handled by [registered listeners]{@link module:enyo/EventEmitter~EventEmitter#on}. +* +* @mixin +* @public +*/ +var EventEmitter = { + + /** + * @private + */ + name: 'EventEmitter', + + /** + * @private + */ + _silenced: false, + + /** + * @private + */ + _silenceCount: 0, + + /** + * Disables propagation of [events]{@glossary event}. This is a counting + * semaphor and [unsilence()]{@link module:enyo/EventEmitter~EventEmitter.unsilence} will need to + * be called the same number of times that this method is called. + * + * @see module:enyo/EventEmitter~EventEmitter.unsilence + * @returns {this} The callee for chaining. + * @public + */ + silence: function () { + this._silenced = true; + this._silenceCount++; + return this; + }, + + /** + * Enables propagation of [events]{@glossary event}. This is a counting + * semaphor and this method will need to be called the same number of times + * that [silence()]{@link module:enyo/EventEmitter~EventEmitter.silence} was called. + * + * @see module:enyo/EventEmitter~EventEmitter.silence + * @returns {this} The callee for chaining. + * @public + */ + unsilence: function (force) { + if (force) { + this._silenceCount = 0; + this._silenced = false; + } else { + this._silenceCount && this._silenceCount--; + this._silenceCount === 0 && (this._silenced = false); + } + return this; + }, + + /** + * Determines whether the callee is currently [silenced]{@link module:enyo/EventEmitter~EventEmitter.silence}. + * + * @returns {Boolean} Whether or not the callee is + * [silenced]{@link module:enyo/EventEmitter~EventEmitter.silence}. + * @public + */ + isSilenced: function () { + return this._silenced; + }, + + /** + * @alias enyo.EventEmitter.on + * @deprecated + * @public + */ + addListener: function (e, fn, ctx) { + return addListener(this, e, fn, ctx); + }, + + /** + * Adds an {@glossary event} listener. Until [removed]{@link module:enyo/EventEmitter~EventEmitter.off}, + * this listener will fire every time the event is + * [emitted]{@link module:enyo/EventEmitter~EventEmitter#emit}. + * + * @param {String} e - The {@glossary event} name to register for. + * @param {Function} fn - The listener. + * @param {Object} [ctx] - The optional context under which to execute the listener. + * @returns {this} The callee for chaining. + * @public + */ + on: function (e, fn, ctx) { + return addListener(this, e, fn, ctx); + }, + + /** + * @alias enyo.EventEmitter.off + * @deprecated + * @public + */ + removeListener: function (e, fn, ctx) { + return removeListener(this, e, fn, ctx); + }, + + /** + * Removes an {@glossary event} listener. + * + * @param {String} e - The {@glossary event} name. + * @param {Function} fn - The listener to unregister. + * @param {Object} [ctx] - If the listener was registered with a context, it + * should be provided when unregistering as well. + * @returns {this} The callee for chaining. + * @public + */ + off: function (e, fn, ctx) { + return removeListener(this, e, fn, ctx); + }, + + /** + * Removes all listeners, or all listeners for a given {@glossary event}. + * + * @param {String} [e] - The optional target {@glossary event}. + * @returns {this} The callee for chaining. + */ + removeAllListeners: function (e) { + var euid = this.euid + , loc = euid && eventTable[euid]; + + if (loc) { + if (e) { + eventTable[euid] = loc.filter(function (ln) { + return ln.event != e; + }); + } else { + eventTable[euid] = null; + } + } + + return this; + }, + + /** + * Primarily intended for internal use, this method returns an immutable copy + * of all listeners, or all listeners for a particular {@glossary event} (if any). + * + * @param {String} [e] - The targeted {@glossary event}. + * @returns {Object[]} Event listeners are stored in [hashes]{@glossary Object}. + * The return value will be an [array]{@glossary Array} of these hashes + * if any listeners exist. + * @public + */ + listeners: function (e) { + var euid = this.euid || (this.euid = utils.uid('e')) + , loc = eventTable[euid] || (eventTable[euid] = []); + + return !e? loc: loc.filter(function (ln) { + return ln.event == e || ln.event == '*'; + }); + }, + + /** + * @alias enyo.EventEmitter.emit + * @deprecated + * @public + */ + triggerEvent: function () { + return !this._silenced? emit(this, arguments): false; + }, + + /** + * Emits the named {@glossary event}. All subsequent arguments will be passed + * to the event listeners. + * + * @param {String} e - The {@glossary event} to emit. + * @param {...*} args All subsequent arguments will be passed to the event listeners. + * @returns {Boolean} Whether or not any listeners were notified. + * @public + */ + emit: function () { + return !this._silenced? emit(this, arguments): false; + } +}; + +module.exports = EventEmitter; + +},{'./utils':'enyo/utils'}],'enyo/StateSupport':[function (module,exports,global,require,request){ +/** +* Exports the {@link module:enyo/StateSupport~StateSupport} mixin +* @module enyo/StateSupport +*/ + +require('enyo'); + +var + States = require('./States'); + +/** +* Provides generic API methods related to using {@link module:enyo/States}. +* +* @mixin +* @public +*/ +var StateSupport = { + + /** + * @private + */ + name: 'StateSupport', + + /** + * The given status. This property will be modified by the other API methods of + * {@link module:enyo/StateSupport~StateSupport}. + * + * @type module:enyo/States + * @default null + */ + status: null, + + /** + * Will remove any [error flags]{@link module:enyo/States.ERROR} from the given + * [status]{@link module:enyo/StateSupport~StateSupport.status}. + * + * @public + */ + clearError: function () { + this.status = this.status & ~States.ERROR; + }, + + /** + * Convenience method to avoid using [bitwise]{@glossary bitwise} comparison for the + * [status]{@link module:enyo/StateSupport~StateSupport.status}. Determines whether the current status + * (or the optional passed-in value) is an [error state]{@link module:enyo/States.ERROR}. + * The passed-in value will only be used if it is a [Number]{@glossary Number}. + * + * @param {module:enyo/States} [status] - The specific value to compare as an + * [error state]{@link module:enyo/States.ERROR}. + * @returns {Boolean} Whether the value is an [error state]{@link module:enyo/States.ERROR} or not. + * @public + */ + isError: function (status) { + return !! ((isNaN(status) ? this.status : status) & States.ERROR); + }, + + /** + * Convenience method to avoid using [bitwise]{@glossary bitwise} comparison for the + * [status]{@link module:enyo/StateSupport~StateSupport.status}. Determines whether the current status + * (or the optional passed-in value) is a [busy state]{@link module:enyo/States.BUSY}. The + * passed-in value will only be used if it is a [Number]{@glossary Number}. + * + * @param {module:enyo/States} [status] - The specific value to compare as a + * [busy state]{@link module:enyo/States.BUSY}. + * @returns {Boolean} Whether the value is a [busy state]{@link module:enyo/States.BUSY} or not. + * @public + */ + isBusy: function (status) { + return !! ((isNaN(status) ? this.status : status) & States.BUSY); + }, + + /** + * Convenience method to avoid using [bitwise]{@glossary bitwise} comparison for the + * [status]{@link module:enyo/StateSupport~StateSupport.status}. Determines whether the current status + * (or the optional passed-in value) is a [ready state]{@link module:enyo/States.READY}. The + * passed-in value will only be used if it is a [Number]{@glossary Number}. + * + * @param {module:enyo/States} [status] - The specific value to compare as a + * [ready state]{@link module:enyo/States.READY}. + * @returns {Boolean} Whether the value is a [ready state]{@link module:enyo/States.BUSY} or not. + * @public + */ + isReady: function (status) { + return !! ((isNaN(status) ? this.status : status) & States.READY); + } +}; + +module.exports = StateSupport; + +},{'./States':'enyo/States'}],'enyo/logger':[function (module,exports,global,require,request){ +require('enyo'); + +var + json = require('./json'), + utils = require('./utils'), + platform = require('./platform'); + +/** +* These platforms only allow one argument for [console.log()]{@glossary console.log}: +* +* * android +* * ios +* * webos +* +* @private +*/ +var dumbConsole = Boolean(platform.android || platform.ios || platform.webos); + +/** +* Internally used methods and properties associated with logging. +* +* @module enyo/logging +* @public +*/ +exports = module.exports = { + + /** + * The log level to use. Can be a value from -1 to 99, where -1 disables all + * logging, 0 is 'error', 10 is 'warn', and 20 is 'log'. It is preferred that + * this value be set using the [setLogLevel()]{@link module:enyo/logging#setLogLevel} + * method. + * + * @type {Number} + * @default 99 + * @public + */ + level: 99, + + /** + * The known levels. + * + * @private + */ + levels: {log: 20, warn: 10, error: 0}, + + /** + * @private + */ + shouldLog: function (fn) { + var ll = parseInt(this.levels[fn], 0); + return (ll <= this.level); + }, + + /** + * @private + */ + validateArgs: function (args) { + // gracefully handle and prevent circular reference errors in objects + for (var i=0, l=args.length, item; (item=args[i]) || i/g,'>') : ''; + }, + + /** + * Returns an object describing the geometry of this node. + * + * @param {Node} n - The [node]{@glossary Node} to measure. + * @returns {Object} An object containing the properties `top`, `left`, + * `height`, and `width`. + * @public + */ + getBounds: function(n) { + if (n) { + return {left: n.offsetLeft, top: n.offsetTop, width: n.offsetWidth, height: n.offsetHeight}; + } + else { + return null; + } + }, + + /** + * This is designed to be copied into the `computedStyle` object. + * + * @private + */ + _ie8GetComputedStyle: function(prop) { + var re = /(\-([a-z]){1})/g; + if (prop === 'float') { + prop = 'styleFloat'; + } else if (re.test(prop)) { + prop = prop.replace(re, function () { + return arguments[2].toUpperCase(); + }); + } + return this[prop] !== undefined ? this[prop] : null; + }, + + /** + * @private + */ + getComputedStyle: function(node) { + if(platform.ie < 9 && node && node.currentStyle) { + //simple global.getComputedStyle polyfill for IE8 + var computedStyle = utils.clone(node.currentStyle); + computedStyle.getPropertyValue = this._ie8GetComputedStyle; + computedStyle.setProperty = function() { + return node.currentStyle.setExpression.apply(node.currentStyle, arguments); + }; + computedStyle.removeProperty = function() { + return node.currentStyle.removeAttribute.apply(node.currentStyle, arguments); + }; + return computedStyle; + } else { + return global.getComputedStyle && node && global.getComputedStyle(node, null); + } + }, + + /** + * @private + */ + getComputedStyleValue: function(node, property, computedStyle) { + var s = computedStyle || this.getComputedStyle(node), + nIE = platform.ie; + + s = s ? s.getPropertyValue(property) : null; + + if (nIE) { + var oConversion = { + 'thin' : (nIE > 8 ? 2 : 1) + 'px', + 'medium' : (nIE > 8 ? 4 : 3) + 'px', + 'thick' : (nIE > 8 ? 6 : 5) + 'px', + 'none' : '0' + }; + if (typeof oConversion[s] != 'undefined') { + s = oConversion[s]; + } + + if (s == 'auto') { + switch (property) { + case 'width': + s = node.offsetWidth; + break; + case 'height': + s = node.offsetHeight; + break; + } + } + } + + return s; + }, + + /** + * @private + */ + getFirstElementByTagName: function(tagName) { + var e = document.getElementsByTagName(tagName); + return e && e[0]; + }, + + /** + * @private + */ + applyBodyFit: function() { + var h = this.getFirstElementByTagName('html'); + if (h) { + this.addClass(h, 'enyo-document-fit'); + } + dom.addBodyClass('enyo-body-fit'); + dom.bodyIsFitting = true; + }, + + /** + * @private + */ + getWindowWidth: function() { + if (global.innerWidth) { + return global.innerWidth; + } + if (document.body && document.body.offsetWidth) { + return document.body.offsetWidth; + } + if (document.compatMode=='CSS1Compat' && + document.documentElement && + document.documentElement.offsetWidth ) { + return document.documentElement.offsetWidth; + } + return 320; + }, + + /** + * @private + */ + getWindowHeight: function() { + if (global.innerHeight) { + return global.innerHeight; + } + if (document.body && document.body.offsetHeight) { + return document.body.offsetHeight; + } + if (document.compatMode=='CSS1Compat' && + document.documentElement && + document.documentElement.offsetHeight ) { + return document.documentElement.offsetHeight; + } + return 480; + }, + + /** + * The proportion by which the `body` tag differs from the global size, in both X and Y + * dimensions. This is relevant when we need to scale the whole interface down from 1920x1080 + * (1080p) to 1280x720 (720p), for example. + * + * @private + */ + _bodyScaleFactorY: 1, + _bodyScaleFactorX: 1, + updateScaleFactor: function() { + var bodyBounds = this.getBounds(document.body); + this._bodyScaleFactorY = bodyBounds.height / this.getWindowHeight(); + this._bodyScaleFactorX = bodyBounds.width / this.getWindowWidth(); + }, + + /** + * @private + */ + // Workaround for lack of compareDocumentPosition support in IE8 + // Code MIT Licensed, John Resig; source: http://ejohn.org/blog/comparing-document-position/ + compareDocumentPosition: function(a, b) { + return a.compareDocumentPosition ? + a.compareDocumentPosition(b) : + a.contains ? + (a != b && a.contains(b) && 16) + + (a != b && b.contains(a) && 8) + + (a.sourceIndex >= 0 && b.sourceIndex >= 0 ? + (a.sourceIndex < b.sourceIndex && 4) + + (a.sourceIndex > b.sourceIndex && 2) : + 1) + + 0 : + 0; + }, + + /** + * @private + */ + // moved from FittableLayout.js into common protected code + _ieCssToPixelValue: function(node, value) { + var v = value; + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + var s = node.style; + // store style and runtime style values + var l = s.left; + var rl = node.runtimeStyle && node.runtimeStyle.left; + // then put current style in runtime style. + if (rl) { + node.runtimeStyle.left = node.currentStyle.left; + } + // apply given value and measure its pixel value + s.left = v; + v = s.pixelLeft; + // finally restore previous state + s.left = l; + if (rl) { + s.runtimeStyle.left = rl; + } + return v; + }, + + /** + * @private + */ + _pxMatch: /px/i, + getComputedBoxValue: function(node, prop, boundary, computedStyle) { + var s = computedStyle || this.getComputedStyle(node); + if (s && (!platform.ie || platform.ie >= 9)) { + var p = s.getPropertyValue(prop + '-' + boundary); + return p === 'auto' ? 0 : parseInt(p, 10); + } else if (node && node.currentStyle) { + var v = node.currentStyle[prop + utils.cap(boundary)]; + if (!v.match(this._pxMatch)) { + v = this._ieCssToPixelValue(node, v); + } + return parseInt(v, 0); + } + return 0; + }, + + /** + * Gets the boundaries of a [node's]{@glossary Node} `margin` or `padding` box. + * + * @param {Node} node - The [node]{@glossary Node} to measure. + * @param {Node} box - The boundary to measure from ('padding' or 'margin'). + * @returns {Object} An object containing the properties `top`, `right`, `bottom`, and + * `left`. + * @public + */ + calcBoxExtents: function(node, box) { + var s = this.getComputedStyle(node); + return { + top: this.getComputedBoxValue(node, box, 'top', s), + right: this.getComputedBoxValue(node, box, 'right', s), + bottom: this.getComputedBoxValue(node, box, 'bottom', s), + left: this.getComputedBoxValue(node, box, 'left', s) + }; + }, + + /** + * Gets the calculated padding of a node. Shortcut for + * {@link module:enyo/dom#calcBoxExtents}. + * + * @param {Node} node - The [node]{@glossary Node} to measure. + * @returns {Object} An object containing the properties `top`, `right`, `bottom`, and + * `left`. + * @public + */ + calcPaddingExtents: function(node) { + return this.calcBoxExtents(node, 'padding'); + }, + + /** + * Gets the calculated margin of a node. Shortcut for + * {@link module:enyo/dom#calcBoxExtents}. + * + * @param {Node} node - The [node]{@glossary Node} to measure. + * @returns {Object} An object containing the properties `top`, `right`, `bottom`, and + * `left`. + * @public + */ + calcMarginExtents: function(node) { + return this.calcBoxExtents(node, 'margin'); + }, + /** + * Returns an object like `{top: 0, left: 0, bottom: 100, right: 100, height: 10, width: 10}` + * that represents the object's position relative to `relativeToNode` (suitable for absolute + * positioning within that parent node). Negative values mean part of the object is not + * visible. If you leave `relativeToNode` as `undefined` (or it is not a parent element), then + * the position will be relative to the viewport and suitable for absolute positioning in a + * floating layer. + * + * @param {Node} node - The [node]{@glossary Node} to measure. + * @param {Node} relativeToNode - The [node]{@glossary Node} to measure the distance from. + * @returns {Object} An object containing the properties `top`, `right`, `bottom`, `left`, + * `height`, and `width`. + * @public + */ + calcNodePosition: function(targetNode, relativeToNode) { + // Parse upward and grab our positioning relative to the viewport + var top = 0, + left = 0, + node = targetNode, + width = node.offsetWidth, + height = node.offsetHeight, + transformProp = dom.getStyleTransformProp(), + xregex = /translateX\((-?\d+)px\)/i, + yregex = /translateY\((-?\d+)px\)/i, + borderLeft = 0, borderTop = 0, + totalHeight = 0, totalWidth = 0, + offsetAdjustLeft = 0, offsetAdjustTop = 0; + + if (relativeToNode) { + totalHeight = relativeToNode.offsetHeight; + totalWidth = relativeToNode.offsetWidth; + } else { + totalHeight = (document.body.parentNode.offsetHeight > this.getWindowHeight() ? this.getWindowHeight() - document.body.parentNode.scrollTop : document.body.parentNode.offsetHeight); + totalWidth = (document.body.parentNode.offsetWidth > this.getWindowWidth() ? this.getWindowWidth() - document.body.parentNode.scrollLeft : document.body.parentNode.offsetWidth); + } + + if (node.offsetParent) { + do { + // Adjust the offset if relativeToNode is a child of the offsetParent + // For IE 8 compatibility, have to use integer 8 instead of Node.DOCUMENT_POSITION_CONTAINS + if (relativeToNode && this.compareDocumentPosition(relativeToNode, node.offsetParent) & 8) { + offsetAdjustLeft = relativeToNode.offsetLeft; + offsetAdjustTop = relativeToNode.offsetTop; + } + // Ajust our top and left properties based on the position relative to the parent + left += node.offsetLeft - (node.offsetParent ? node.offsetParent.scrollLeft : 0) - offsetAdjustLeft; + if (transformProp && xregex.test(node.style[transformProp])) { + left += parseInt(node.style[transformProp].replace(xregex, '$1'), 10); + } + top += node.offsetTop - (node.offsetParent ? node.offsetParent.scrollTop : 0) - offsetAdjustTop; + if (transformProp && yregex.test(node.style[transformProp])) { + top += parseInt(node.style[transformProp].replace(yregex, '$1'), 10); + } + // Need to correct for borders if any exist on parent elements + if (node !== targetNode) { + if (node.currentStyle) { + // Oh IE, we do so love working around your incompatibilities + borderLeft = parseInt(node.currentStyle.borderLeftWidth, 10); + borderTop = parseInt(node.currentStyle.borderTopWidth, 10); + } else if (global.getComputedStyle) { + borderLeft = parseInt(global.getComputedStyle(node, '').getPropertyValue('border-left-width'), 10); + borderTop = parseInt(global.getComputedStyle(node, '').getPropertyValue('border-top-width'), 10); + } else { + // No computed style options, so try the normal style object (much less robust) + borderLeft = parseInt(node.style.borderLeftWidth, 10); + borderTop = parseInt(node.style.borderTopWidth, 10); + } + if (borderLeft) { + left += borderLeft; + } + if (borderTop) { + top += borderTop; + } + } + // Continue if we have an additional offsetParent, and either don't have a relativeToNode or the offsetParent is contained by the relativeToNode (if offsetParent contains relativeToNode, then we have already calculated up to the node, and can safely exit) + // For IE 8 compatibility, have to use integer 16 instead of Node.DOCUMENT_POSITION_CONTAINED_BY + } while ((node = node.offsetParent) && (!relativeToNode || this.compareDocumentPosition(relativeToNode, node) & 16)); + } + return { + 'top': top, + 'left': left, + 'bottom': totalHeight - top - height, + 'right': totalWidth - left - width, + 'height': height, + 'width': width + }; + }, + + /** + * Sets the `innerHTML` property of the specified `node` to `html`. + * + * @param {Node} node - The [node]{@glossary Node} to set. + * @param {String} html - An HTML string. + * @public + */ + setInnerHtml: function(node, html) { + node.innerHTML = html; + }, + + /** + * Checks a [DOM]{@glossary Node} [node]{@glossary Node} for a specific CSS class. + * + * @param {Node} node - The [node]{@glossary Node} to set. + * @param {String} s - The class name to check for. + * @returns {(Boolean|undefined)} `true` if `node` has the `s` class; `undefined` + * if there is no `node` or it has no `className` property. + * @public + */ + hasClass: function(node, s) { + if (!node || !node.className) { return; } + return (' ' + node.className + ' ').indexOf(' ' + s + ' ') >= 0; + }, + + /** + * Uniquely adds a CSS class to a DOM node. + * + * @param {Node} node - The [node]{@glossary Node} to set. + * @param {String} s - The class name to add. + * @public + */ + addClass: function(node, s) { + if (node && !this.hasClass(node, s)) { + var ss = node.className; + node.className = (ss + (ss ? ' ' : '') + s); + } + }, + + /** + * Removes a CSS class from a DOM node if it exists. + * + * @param {Node} node - The [node]{@glossary Node} from which to remove the class. + * @param {String} s - The class name to remove from `node`. + * @public + */ + removeClass: function(node, s) { + if (node && this.hasClass(node, s)) { + var ss = node.className; + node.className = (' ' + ss + ' ').replace(' ' + s + ' ', ' ').slice(1, -1); + } + }, + + /** + * Adds a class to `document.body`. This defers the actual class change if nothing has been + * rendered into `body` yet. + * + * @param {String} s - The class name to add to the document's `body`. + * @public + */ + addBodyClass: function(s) { + if (!utils.exists(roots.roots) || roots.roots.length === 0) { + if (dom._bodyClasses) { + dom._bodyClasses.push(s); + } else { + dom._bodyClasses = [s]; + } + } + else { + dom.addClass(document.body, s); + } + }, + + /** + * Returns an object describing the absolute position on the screen, relative to the top left + * corner of the screen. This function takes into account account absolute/relative + * `offsetParent` positioning, `scroll` position, and CSS transforms (currently + * `translateX`, `translateY`, and `matrix3d`). + * + * ```javascript + * {top: ..., right: ..., bottom: ..., left: ..., height: ..., width: ...} + * ``` + * + * Values returned are only valid if `hasNode()` is truthy. If there's no DOM node for the + * object, this returns a bounds structure with `undefined` as the value of all fields. + * + * @param {Node} n - The [node]{@glossary Node} to measure. + * @returns {Object} An object containing the properties `top`, `right`, `bottom`, `left`, + * `height`, and `width`. + * @public + */ + getAbsoluteBounds: function(targetNode) { + return utils.clone(targetNode.getBoundingClientRect()); + }, + + /** + * @private + */ + flushBodyClasses: function() { + if (dom._bodyClasses) { + for (var i = 0, c; (c=dom._bodyClasses[i]); ++i) { + dom.addClass(document.body, c); + } + dom._bodyClasses = null; + } + }, + + /** + * @private + */ + _bodyClasses: null, + + /** + * Convert to various unit formats. Useful for converting pixels to a resolution-independent + * measurement method, like "rem". Other units are available if defined in the + * {@link module:enyo/dom#unitToPixelFactors} object. + * + * ```javascript + * var + * dom = require('enyo/dom'); + * + * // Do calculations and get back the desired CSS unit. + * var frameWidth = 250, + * frameWithMarginInches = dom.unit( 10 + frameWidth + 10, 'in' ), + * frameWithMarginRems = dom.unit( 10 + frameWidth + 10, 'rem' ); + * // '2.8125in' == frameWithMarginInches + * // '22.5rem' == frameWithMarginRems + * ``` + * + * @param {(String|Number)} pixels - The the pixels or math to convert to the unit. + * ("px" suffix in String format is permitted. ex: `'20px'`) + * @param {(String)} toUnit - The name of the unit to convert to. + * @returns {(Number|undefined)} Resulting conversion, in case of malformed input, `undefined` + * @public + */ + unit: function (pixels, toUnit) { + if (!toUnit || !this.unitToPixelFactors[toUnit]) return; + if (typeof pixels == 'string' && pixels.substr(-2) == 'px') pixels = parseInt(pixels.substr(0, pixels.length - 2), 10); + if (typeof pixels != 'number') return; + + return (pixels / this.unitToPixelFactors[toUnit]) + '' + toUnit; + }, + + /** + * Object that stores all of the pixel conversion factors to each keyed unit. + * + * @public + */ + unitToPixelFactors: { + 'rem': 12, + 'in': 96 + } +}; + +// override setInnerHtml for Windows 8 HTML applications +if (typeof global.MSApp !== 'undefined') { + dom.setInnerHtml = function(node, html) { + global.MSApp.execUnsafeLocalFunction(function() { + node.innerHTML = html; + }); + }; +} + +// use faster classList interface if it exists +if (document.head && document.head.classList) { + dom.hasClass = function(node, s) { + if (node) { + return node.classList.contains(s); + } + }; + dom.addClass = function(node, s) { + if (node) { + return node.classList.add(s); + } + }; + dom.removeClass = function (node, s) { + if (node) { + return node.classList.remove(s); + } + }; +} + +/** +* Allows bootstrapping in environments that do not have a global object right away. +* +* @param {Function} func - The function to run +* @public +*/ +dom.requiresWindow = function(func) { + func(); +}; + + +var cssTransformProps = ['transform', '-webkit-transform', '-moz-transform', '-ms-transform', '-o-transform'], + styleTransformProps = ['transform', 'webkitTransform', 'MozTransform', 'msTransform', 'OTransform']; + +/** +* @private +*/ +dom.calcCanAccelerate = function() { + /* Android 2 is a liar: it does NOT support 3D transforms, even though Perspective is the best check */ + if (platform.android <= 2) { + return false; + } + var p$ = ['perspective', 'WebkitPerspective', 'MozPerspective', 'msPerspective', 'OPerspective']; + for (var i=0, p; (p=p$[i]); i++) { + if (typeof document.body.style[p] != 'undefined') { + return true; + } + } + return false; +}; +/** +* @private +*/ +dom.getCssTransformProp = function() { + if (this._cssTransformProp) { + return this._cssTransformProp; + } + var i = utils.indexOf(this.getStyleTransformProp(), styleTransformProps); + this._cssTransformProp = cssTransformProps[i]; + return this._cssTransformProp; +}; + +/** +* @private +*/ +dom.getStyleTransformProp = function() { + if (this._styleTransformProp || !document.body) { + return this._styleTransformProp; + } + for (var i = 0, p; (p = styleTransformProps[i]); i++) { + if (typeof document.body.style[p] != 'undefined') { + this._styleTransformProp = p; + return this._styleTransformProp; + } + } +}; + +/** +* @private +*/ +dom.domTransformsToCss = function(inTransforms) { + var n, v, text = ''; + for (n in inTransforms) { + v = inTransforms[n]; + if ((v !== null) && (v !== undefined) && (v !== '')) { + text += n + '(' + v + ') '; + } + } + return text; +}; + +/** +* @private +*/ +dom.transformsToDom = function(control) { + var css = this.domTransformsToCss(control.domTransforms), + styleProp; + + if (control.hasNode()) { + styleProp = this.getStyleTransformProp(); + } else { + styleProp = this.getCssTransformProp(); + } + + if (styleProp) control.applyStyle(styleProp, css); +}; + +/** +* Returns `true` if the platform supports CSS3 Transforms. +* +* @returns {Boolean} `true` if platform supports CSS `transform` property; +* otherwise, `false`. +* @public +*/ +dom.canTransform = function() { + return Boolean(this.getStyleTransformProp()); +}; + +/** +* Returns `true` if platform supports CSS3 3D Transforms. +* +* Typically used like this: +* ``` +* if (dom.canAccelerate()) { +* dom.transformValue(this.$.slidingThing, 'translate3d', x + ',' + y + ',' + '0') +* } else { +* dom.transformValue(this.$.slidingThing, 'translate', x + ',' + y); +* } +* ``` +* +* @returns {Boolean} `true` if platform supports CSS3 3D Transforms; +* otherwise, `false`. +* @public +*/ +dom.canAccelerate = function() { + return (this.accelerando !== undefined) ? this.accelerando : document.body && (this.accelerando = this.calcCanAccelerate()); +}; + +/** +* Applies a series of transforms to the specified {@link module:enyo/Control~Control}, using +* the platform's prefixed `transform` property. +* +* **Note:** Transforms are not commutative, so order is important. +* +* Transform values are updated by successive calls, so +* ```javascript +* dom.transform(control, {translate: '30px, 40px', scale: 2, rotate: '20deg'}); +* dom.transform(control, {scale: 3, skewX: '-30deg'}); +* ``` +* +* is equivalent to: +* ```javascript +* dom.transform(control, {translate: '30px, 40px', scale: 3, rotate: '20deg', skewX: '-30deg'}); +* ``` +* +* When applying these transforms in a WebKit browser, this is equivalent to: +* ```javascript +* control.applyStyle('-webkit-transform', 'translate(30px, 40px) scale(3) rotate(20deg) skewX(-30deg)'); +* ``` +* +* And in Firefox, this is equivalent to: +* ```javascript +* control.applyStyle('-moz-transform', 'translate(30px, 40px) scale(3) rotate(20deg) skewX(-30deg)'); +* ``` +* +* @param {module:enyo/Control~Control} control - The {@link module:enyo/Control~Control} to transform. +* @param {Object} transforms - The set of transforms to apply to `control`. +* @public +*/ +dom.transform = function(control, transforms) { + var d = control.domTransforms = control.domTransforms || {}; + utils.mixin(d, transforms); + this.transformsToDom(control); +}; + +/** +* Applies a single transform to the specified {@link module:enyo/Control~Control}. +* +* Example: +* ``` +* tap: function(inSender, inEvent) { +* var c = inEvent.originator; +* var r = c.rotation || 0; +* r = (r + 45) % 360; +* c.rotation = r; +* dom.transformValue(c, 'rotate', r); +* } +* ``` +* +* This will rotate the tapped control by 45 degrees clockwise. +* +* @param {module:enyo/Control~Control} control - The {@link module:enyo/Control~Control} to transform. +* @param {String} transform - The name of the transform function. +* @param {(String|Number)} value - The value to apply to the transform. +* @public +*/ +dom.transformValue = function(control, transform, value) { + var d = control.domTransforms = control.domTransforms || {}; + d[transform] = value; + this.transformsToDom(control); +}; + +/** +* Applies a transform that should trigger GPU compositing for the specified +* {@link module:enyo/Control~Control}. By default, the acceleration is only +* applied if the browser supports it. You may also optionally force-set `value` +* directly, to be applied to `translateZ(value)`. +* +* @param {module:enyo/Control~Control} control - The {@link module:enyo/Control~Control} to accelerate. +* @param {(String|Number)} [value] - An optional value to apply to the acceleration transform +* property. +* @public +*/ +dom.accelerate = function(control, value) { + var v = value == 'auto' ? this.canAccelerate() : value; + this.transformValue(control, 'translateZ', v ? 0 : null); +}; + + +/** + * The CSS `transition` property name for the current browser/platform, e.g.: + * + * * `-webkit-transition` + * * `-moz-transition` + * * `transition` + * + * @type {String} + * @private + */ +dom.transition = (platform.ios || platform.android || platform.chrome || platform.androidChrome || platform.safari) + ? '-webkit-transition' + : (platform.firefox || platform.firefoxOS || platform.androidFirefox) + ? '-moz-transition' + : 'transition'; + +},{'./roots':'enyo/roots','./utils':'enyo/utils','./platform':'enyo/platform'}],'enyo/animation':[function (module,exports,global,require,request){ +/** +* Contains methods useful for animations. +* @module enyo/animation +*/ + +require('enyo'); + +var + platform = require('./platform'), + utils = require('./utils'); + +var ms = Math.round(1000/60); +var prefix = ['webkit', 'moz', 'ms', 'o', '']; +var r = 'requestAnimationFrame'; +var c = 'cancel' + utils.cap(r); + +/* +* Fallback on setTimeout +* +* @private +*/ +var _requestFrame = function(inCallback) { + return global.setTimeout(inCallback, ms); +}; + +/* +* Fallback on clearTimeout +* +* @private +*/ +var _cancelFrame = function(inId) { + return global.clearTimeout(inId); +}; + +for (var i = 0, pl = prefix.length, p, wc, wr; (p = prefix[i]) || i < pl; i++) { + // if we're on ios 6 just use setTimeout, requestAnimationFrame has some kinks currently + if (platform.ios >= 6) { + break; + } + + // if prefixed, becomes Request and Cancel + wc = p ? (p + utils.cap(c)) : c; + wr = p ? (p + utils.cap(r)) : r; + // Test for cancelRequestAnimationFrame, because some browsers (Firefix 4-10) have a request without a cancel + if (global[wc]) { + _cancelFrame = global[wc]; + _requestFrame = global[wr]; + if (p == 'webkit') { + /* + Note: In Chrome, the first return value of webkitRequestAnimationFrame is 0. + We make 1 bogus call so the first used return value of webkitRequestAnimationFrame is > 0, as the spec requires. + This makes it so that the requestId is always truthy. + (we choose to do this rather than wrapping the native function to avoid the overhead) + */ + _cancelFrame(_requestFrame(utils.nop)); + } + break; + } +} +/** +* Requests an animation callback. +* +* On compatible browsers, if `node` is defined, the [callback]{@glossary callback} will +* fire only if `node` is visible. +* +* @param {Function} callback - A [callback]{@glossary callback} to be executed on the +* animation frame. +* @param {Node} node - The DOM node to request the animation frame for. +* @returns {Object} A request id to be used with +* {@link module:enyo/animation#cancelRequestAnimationFrame}. +* @public +*/ +exports.requestAnimationFrame = function(callback, node) { + return _requestFrame(callback, node); +}; +/** +* Cancels a requested animation callback with the specified id. +* +* @public +*/ +exports.cancelRequestAnimationFrame = function(inId) { + return _cancelFrame(inId); +}; + +/** +* A set of interpolation functions for animations, similar in function to CSS3 +* transitions. +* +* These are intended for use with {@link module:enyo/animation#easedLerp}. Each easing function +* accepts one (1) [Number]{@glossary Number} parameter and returns one (1) +* [Number]{@glossary Number} value. +* +* @public +*/ +exports.easing = /** @lends module:enyo/animation~easing.prototype */ { + /** + * cubicIn + * + * @public + */ + cubicIn: function(n) { + return Math.pow(n, 3); + }, + /** + * cubicOut + * + * @public + */ + cubicOut: function(n) { + return Math.pow(n - 1, 3) + 1; + }, + /** + * expoOut + * + * @public + */ + expoOut: function(n) { + return (n == 1) ? 1 : (-1 * Math.pow(2, -10 * n) + 1); + }, + /** + * quadInOut + * + * @public + */ + quadInOut: function(n) { + n = n * 2; + if (n < 1) { + return Math.pow(n, 2) / 2; + } + return -1 * ((--n) * (n - 2) - 1) / 2; + }, + /** + * linear + * + * @public + */ + linear: function(n) { + return n; + } +}; + +/** +* Gives an interpolation of an animated transition's distance from 0 to 1. +* +* Given a start time (`t0`) and an animation duration (`duration`), this +* method applies the `easing` function to the percentage of time elapsed +* divided by duration, capped at 100%. +* +* @param {Number} t0 - Start time. +* @param {Number} duration - Duration in milliseconds. +* @param {Function} easing - An easing [function]{@glossary Function} reference from +* {@link module:enyo/animation#easing}. +* @param {Boolean} reverse - Whether the animation will run in reverse. +* @returns {Number} The resulting position, capped at a maximum of 100%. +* @public +*/ +exports.easedLerp = function(t0, duration, easing, reverse) { + var lerp = (utils.perfNow() - t0) / duration; + if (reverse) { + return lerp >= 1 ? 0 : (1 - easing(1 - lerp)); + } else { + return lerp >= 1 ? 1 : easing(lerp); + } +}; + +/** +* Gives an interpolation of an animated transition's distance from +* `startValue` to `valueChange`. +* +* Applies the `easing` function with a wider range of variables to allow for +* more complex animations. +* +* @param {Number} t0 - Start time. +* @param {Number} duration - Duration in milliseconds. +* @param {Function} easing - An easing [function]{@glossary Function} reference from +* {@link module:enyo/animation#easing}. +* @param {Boolean} reverse - Whether the animation will run in reverse. +* @param {Number} time +* @param {Number} startValue - Starting value. +* @param {Number} valueChange +* @returns {Number} The resulting position, capped at a maximum of 100%. +* @public +*/ +exports.easedComplexLerp = function(t0, duration, easing, reverse, time, startValue, valueChange) { + var lerp = (utils.perfNow() - t0) / duration; + if (reverse) { + return easing(1 - lerp, time, startValue, valueChange, duration); + } else { + return easing(lerp, time, startValue, valueChange, duration); + } +}; + +},{'./platform':'enyo/platform','./utils':'enyo/utils'}],'enyo/kind':[function (module,exports,global,require,request){ +require('enyo'); + +var + logger = require('./logger'), + utils = require('./utils'); + +var defaultCtor = null; + +/** +* Creates a JavaScript [constructor]{@glossary constructor} function with +* a prototype defined by `props`. **All constructors must have a unique name.** +* +* `enyo.kind()` makes it easy to build a constructor-with-prototype (like a +* class) that has advanced features like prototype-chaining +* ([inheritance]{@glossary inheritance}). +* +* A plug-in system is included for extending the abilities of the +* [kind]{@glossary kind} generator, and constructors are allowed to +* perform custom operations when subclassed. +* +* If you make changes to `enyo.kind()`, be sure to add or update the appropriate +* [unit tests](@link https://github.com/enyojs/enyo/tree/master/tools/test/core/tests). +* +* For more information, see the documentation on +* [Kinds]{@linkplain $dev-guide/key-concepts/kinds.html} in the Enyo Developer Guide. +* +* @module enyo/kind +* @param {Object} props - A [hash]{@glossary Object} of properties used to define and create +* the [kind]{@glossary kind} +* @public +*/ +/*jshint -W120*/ +var kind = exports = module.exports = function (props) { +/*jshint +W120*/ + // extract 'name' property + var name = props.name || ''; + delete props.name; + // extract 'kind' property + var hasKind = ('kind' in props); + var kindName = props.kind; + delete props.kind; + // establish base class reference + var base = constructorForKind(kindName); + var isa = base && base.prototype || null; + // if we have an explicit kind property with value undefined, we probably + // tried to reference a kind that is not yet in scope + if (hasKind && kindName === undefined || base === undefined) { + var problem = kindName === undefined ? 'undefined kind' : 'unknown kind (' + kindName + ')'; + throw 'enyo.kind: Attempt to subclass an ' + problem + '. Check dependencies for [' + (name || '') + '].'; + } + // make a boilerplate constructor + var ctor = kind.makeCtor(); + // semi-reserved word 'constructor' causes problems with Prototype and IE, so we rename it here + if (props.hasOwnProperty('constructor')) { + props._constructor = props.constructor; + delete props.constructor; + } + // create our prototype + //ctor.prototype = isa ? enyo.delegate(isa) : {}; + utils.setPrototype(ctor, isa ? utils.delegate(isa) : {}); + // there are special cases where a base class has a property + // that may need to be concatenated with a subclasses implementation + // as opposed to completely overwriting it... + kind.concatHandler(ctor, props); + + // put in our props + utils.mixin(ctor.prototype, props); + // alias class name as 'kind' in the prototype + // but we actually only need to set this if a new name was used, + // not if it is inheriting from a kind anonymously + if (name) { + ctor.prototype.kindName = name; + } + // this is for anonymous constructors + else { + ctor.prototype.kindName = base && base.prototype? base.prototype.kindName: ''; + } + // cache superclass constructor + ctor.prototype.base = base; + // reference our real constructor + ctor.prototype.ctor = ctor; + // support pluggable 'features' + utils.forEach(kind.features, function(fn){ fn(ctor, props); }); + + if (name) kindCtors[name] = ctor; + + return ctor; +}; + +exports.setDefaultCtor = function (ctor) { + defaultCtor = ctor; +}; + +var getDefaultCtor = exports.getDefaultCtor = function () { + return defaultCtor; +}; + +/** +* @private +*/ +var concatenated = exports.concatenated = []; + +/** +* Creates a singleton of a given [kind]{@glossary kind} with a given +* definition. **The `name` property will be the instance name of the singleton +* and must be unique.** +* +* ```javascript +* enyo.singleton({ +* kind: 'enyo.Control', +* name: 'app.MySingleton', +* published: { +* value: 'foo' +* }, +* makeSomething: function() { +* //... +* } +* }); +* +* app.MySingleton.makeSomething(); +* app.MySingleton.setValue('bar'); +*``` +* +* @public +*/ +exports.singleton = function (conf) { + // extract 'name' property (the name of our singleton) + delete(conf.name); + // create an unnamed kind and save its constructor's function + var Kind = kind(conf); + var inst = new Kind(); + return inst; +}; + +/** +* @private +*/ +kind.makeCtor = function () { + var enyoConstructor = function () { + if (!(this instanceof enyoConstructor)) { + throw 'enyo.kind: constructor called directly, not using "new"'; + } + + // two-pass instantiation + var result; + if (this._constructor) { + // pure construction + result = this._constructor.apply(this, arguments); + } + // defer initialization until entire constructor chain has finished + if (this.constructed) { + // post-constructor initialization + this.constructed.apply(this, arguments); + } + + if (result) { + return result; + } + }; + return enyoConstructor; +}; + +/** +* Classes referenced by name may omit this namespace (e.g., "Button" instead of "enyo.Button") +* +* @private +*/ +kind.defaultNamespace = 'enyo'; + +/** +* Feature hooks for the oop system +* +* @private +*/ +kind.features = []; + +/** +* Used internally by several mechanisms to allow safe and normalized handling for extending a +* [kind's]{@glossary kind} super-methods. It can take a +* [constructor]{@glossary constructor}, a [prototype]{@glossary Object.prototype}, or an +* instance. +* +* @private +*/ +kind.extendMethods = function (ctor, props, add) { + var proto = ctor.prototype || ctor, + b = proto.base; + if (!proto.inherited && b) { + proto.inherited = kind.inherited; + } + // rename constructor to _constructor to work around IE8/Prototype problems + if (props.hasOwnProperty('constructor')) { + props._constructor = props.constructor; + delete props.constructor; + } + // decorate function properties to support inherited (do this ex post facto so that + // ctor.prototype is known, relies on elements in props being copied by reference) + for (var n in props) { + var p = props[n]; + if (isInherited(p)) { + // ensure that if there isn't actually a super method to call, it won't + // fail miserably - while this shouldn't happen often, it is a sanity + // check for mixin-extensions for kinds + if (add) { + p = proto[n] = p.fn(proto[n] || utils.nop); + } else { + p = proto[n] = p.fn(b? (b.prototype[n] || utils.nop): utils.nop); + } + } + if (utils.isFunction(p)) { + if (add) { + proto[n] = p; + p.displayName = n + '()'; + } else { + p._inherited = b? b.prototype[n]: null; + // FIXME: we used to need some extra values for inherited, then inherited got cleaner + // but in the meantime we used these values to support logging in Object. + // For now we support this legacy situation, by suppling logging information here. + p.displayName = proto.kindName + '.' + n + '()'; + } + } + } +}; +kind.features.push(kind.extendMethods); + +/** +* Called by {@link module:enyo/CoreObject~Object} instances attempting to access super-methods +* of a parent class ([kind]{@glossary kind}) by calling +* `this.inherited(arguments)` from within a kind method. This can only be done +* safely when there is known to be a super class with the same method. +* +* @private +*/ +kind.inherited = function (originals, replacements) { + // one-off methods are the fast track + var target = originals.callee; + var fn = target._inherited; + + // regardless of how we got here, just ensure we actually + // have a function to call or else we throw a console + // warning to notify developers they are calling a + // super method that doesn't exist + if ('function' === typeof fn) { + var args = originals; + if (replacements) { + // combine the two arrays, with the replacements taking the first + // set of arguments, and originals filling up the rest. + args = []; + var i = 0, l = replacements.length; + for (; i < l; ++i) { + args[i] = replacements[i]; + } + l = originals.length; + for (; i < l; ++i) { + args[i] = originals[i]; + } + } + return fn.apply(this, args); + } else { + logger.warn('enyo.kind.inherited: unable to find requested ' + + 'super-method from -> ' + originals.callee.displayName + ' in ' + this.kindName); + } +}; + +// dcl inspired super-inheritance + +/** +* @private +*/ +var Inherited = function (fn) { + this.fn = fn; +}; + +/** +* When defining a method that overrides an existing method in a [kind]{@glossary kind}, you +* can wrap the definition in this function and it will decorate it appropriately for inheritance +* to work. +* +* The older `this.inherited(arguments)` method still works, but this version results in much +* faster code and is the only one supported for kind [mixins]{@glossary mixin}. +* +* @param {Function} fn - A [function]{@glossary Function} that takes a single +* argument (usually named `sup`) and returns a function where +* `sup.apply(this, arguments)` is used as a mechanism to make the +* super-call. +* @public +*/ +exports.inherit = function (fn) { + return new Inherited(fn); +}; + +/** +* @private +*/ +var isInherited = exports.isInherited = function (fn) { + return fn && (fn instanceof Inherited); +}; + + +// +// 'statics' feature +// +kind.features.push(function(ctor, props) { + // install common statics + if (!ctor.subclass) { + ctor.subclass = kind.statics.subclass; + } + if (!ctor.extend) { + ctor.extend = kind.statics.extend; + } + if (!ctor.kind) { + ctor.kind = kind.statics.kind; + } + // move props statics to constructor + if (props.statics) { + utils.mixin(ctor, props.statics); + delete ctor.prototype.statics; + } + // also support protectedStatics which won't interfere with defer + if (props.protectedStatics) { + utils.mixin(ctor, props.protectedStatics); + delete ctor.prototype.protectedStatics; + } + // allow superclass customization + var base = ctor.prototype.base; + while (base) { + base.subclass(ctor, props); + base = base.prototype.base; + } +}); + +/** +* @private +*/ +kind.statics = { + + /** + * A [kind]{@glossary kind} may set its own `subclass()` method as a + * static method for its [constructor]{@glossary constructor}. Whenever + * it is subclassed, the constructor and properties will be passed through + * this method for special handling of important features. + * + * @param {Function} ctor - The [constructor]{@glossary constructor} of the + * [kind]{@glossary kind} being subclassed. + * @param {Object} props - The properties of the kind being subclassed. + * @memberof enyo.kind + * @public + */ + subclass: function (ctor, props) {}, + + /** + * Allows for extension of the current [kind]{@glossary kind} without + * creating a new kind. This method is available on all + * [constructors]{@glossary constructor}, although calling it on a + * [deferred]{@glossary deferred} constructor will force it to be + * resolved at that time. This method does not re-run the + * {@link module:enyo/kind~kind.features} against the constructor or instance. + * + * @param {Object|Object[]} props A [hash]{@glossary Object} or [array]{@glossary Array} + * of [hashes]{@glossary Object}. Properties will override + * [prototype]{@glossary Object.prototype} properties. If a + * method that is being added already exists, the new method will + * supersede the existing one. The method may call + * `this.inherited()` or be wrapped with `kind.inherit()` to call + * the original method (this chains multiple methods tied to a + * single [kind]{@glossary kind}). + * @param {Object} [target] - The instance to be extended. If this is not specified, then the + * [constructor]{@glossary constructor} of the + * [object]{@glossary Object} this method is being called on will + * be extended. + * @returns {Object} The constructor of the class, or specific + * instance, that has been extended. + * @memberof enyo.kind + * @public + */ + extend: function (props, target) { + var ctor = this + , exts = utils.isArray(props)? props: [props] + , proto, fn; + + fn = function (key, value) { + return !(typeof value == 'function' || isInherited(value)) && concatenated.indexOf(key) === -1; + }; + + proto = target || ctor.prototype; + for (var i=0, ext; (ext=exts[i]); ++i) { + kind.concatHandler(proto, ext, true); + kind.extendMethods(proto, ext, true); + utils.mixin(proto, ext, {filter: fn}); + } + + return target || ctor; + }, + + /** + * Creates a new sub-[kind]{@glossary kind} of the current kind. + * + * @param {Object} props A [hash]{@glossary Object} of properties used to define and create + * the [kind]{@glossary kind} + * @return {Function} Constructor of the new kind + * @memberof enyo.kind + * @public + */ + kind: function (props) { + if (props.kind && props.kind !== this) { + logger.warn('Creating a different kind from a constructor\'s kind() method is not ' + + 'supported and will be replaced with the constructor.'); + } + props.kind = this; + return kind(props); + } +}; + +/** +* @private +*/ +exports.concatHandler = function (ctor, props, instance) { + var proto = ctor.prototype || ctor + , base = proto.ctor; + + while (base) { + if (base.concat) base.concat(ctor, props, instance); + base = base.prototype.base; + } +}; + +/** +* Factory for [kinds]{@glossary kind} identified by [strings]{@glossary String}. +* +* @private +*/ +var kindCtors = exports._kindCtors = {}; + +/** +* @private +*/ +var constructorForKind = exports.constructorForKind = function (kind) { + if (kind === null) { + return kind; + } else if (kind === undefined) { + return getDefaultCtor(); + } + else if (utils.isFunction(kind)) { + return kind; + } + logger.warn('Creating instances by name is deprecated. Name used:', kind); + // use memoized constructor if available... + var ctor = kindCtors[kind]; + if (ctor) { + return ctor; + } + // otherwise look it up and memoize what we find + // + // if kind is an object in enyo, say "Control", then ctor = enyo["Control"] + // if kind is a path under enyo, say "Heritage.Button", then ctor = enyo["Heritage.Button"] || enyo.Heritage.Button + // if kind is a fully qualified path, say "enyo.Heritage.Button", then ctor = enyo["enyo.Heritage.Button"] || enyo.enyo.Heritage.Button || enyo.Heritage.Button + // + // Note that kind "Foo" will resolve to enyo.Foo before resolving to global "Foo". + // This is important so "Image" will map to built-in Image object, instead of enyo.Image control. + ctor = Theme[kind] || (global.enyo && global.enyo[kind]) || utils.getPath.call(global, 'enyo.' + kind) || global[kind] || utils.getPath.call(global, kind); + + // If what we found at this namespace isn't a function, it's definitely not a kind constructor + if (!utils.isFunction(ctor)) { + throw '[' + kind + '] is not the name of a valid kind.'; + } + kindCtors[kind] = ctor; + return ctor; +}; + +/** +* Namespace for current theme (`enyo.Theme.Button` references the Button specialization for the +* current theme). +* +* @private +*/ +var Theme = exports.Theme = {}; + +/** +* @private +*/ +exports.registerTheme = function (ns) { + utils.mixin(Theme, ns); +}; + +/** +* @private +*/ +exports.createFromKind = function (nom, param) { + var Ctor = nom && constructorForKind(nom); + if (Ctor) { + return new Ctor(param); + } +}; + +},{'./logger':'enyo/logger','./utils':'enyo/utils'}],'enyo/resolution':[function (module,exports,global,require,request){ +require('enyo'); + +var + Dom = require('./dom'); + +var _baseScreenType = 'standard', + _riRatio, + _screenType, + _screenTypes = [ {name: 'standard', pxPerRem: 16, width: global.innerWidth, height: global.innerHeight, aspectRatioName: 'standard'} ], // Assign one sane value in case defineScreenTypes is never run. + _screenTypeObject, + _oldScreenType; + +var getScreenTypeObject = function (type) { + type = type || _screenType; + if (_screenTypeObject && _screenTypeObject.name == type) { + return _screenTypeObject; + } + return _screenTypes.filter(function (elem) { + return (type == elem.name); + })[0]; +}; + +/** +* Resolution independence methods +* @module enyo/resolution +*/ +var ri = module.exports = { + /** + * Setup screen resolution scaling capabilities by defining all of the screens you're working + * with. These should be in the order of smallest to largest (according to width). Running + * this also initializes the rest of this resolution code. + * + * In the arguments, the following properties are required: 'name', 'pxPerRem', 'width', + * 'aspectRatioName'. The property 'base' defines the primary or default resoultion that + * everything else will be based upon. + * + * ``` + * ri.defineScreenTypes([ + * {name: 'vga', pxPerRem: 8, width: 640, height: 480, aspectRatioName: 'standard'}, + * {name: 'xga', pxPerRem: 16, width: 1024, height: 768, aspectRatioName: 'standard'}, + * {name: 'hd', pxPerRem: 16, width: 1280, height: 720, aspectRatioName: 'hdtv'}, + * {name: 'fhd', pxPerRem: 24, width: 1920, height: 1080, aspectRatioName: 'hdtv', base: true}, + * {name: 'uw-uxga', pxPerRem: 24, width: 2560, height: 1080, aspectRatioName: 'cinema'}, + * {name: 'uhd', pxPerRem: 48, width: 3840, height: 2160, aspectRatioName: 'hdtv'} + * ]); + * ``` + * + * @param {Array} types An array of objects with arguments like the example + * @public + */ + defineScreenTypes: function (types) { + _screenTypes = types; + for (var i = 0; i < _screenTypes.length; i++) { + if (_screenTypes[i]['base']) _baseScreenType = _screenTypes[i].name; + } + ri.init(); + }, + + /** + * Fetches the best-matching screen type name for the current screen size. The "best" screen type + * is determined by the screen type name that is the closest to the screen resolution without + * going over. ("The Price is Right" style.) + * + * @param {Object} [rez] - Optional measurement scheme. Must have "height" and "width" properties. + * @returns {String} Screen type, like "fhd", "uhd", etc. + * @public + */ + getScreenType: function (rez) { + rez = rez || { + height: global.innerHeight, + width: global.innerWidth + }; + var i, + types = _screenTypes, + bestMatch = types[types.length - 1].name; + + // loop thorugh resolutions + for (i = types.length - 1; i >= 0; i--) { + // find the one that matches our current size or is smaller. default to the first. + if (rez.width <= types[i].width) { + bestMatch = types[i].name; + } + } + // return the name of the resolution if we find one. + return bestMatch; + }, + + /** + * @private + */ + updateScreenBodyClasses: function (type) { + type = type || _screenType; + if (_oldScreenType) { + Dom.removeClass(document.body, 'enyo-res-' + _oldScreenType.toLowerCase()); + var oldScrObj = getScreenTypeObject(_oldScreenType); + if (oldScrObj && oldScrObj.aspectRatioName) { + Dom.removeClass(document.body, 'enyo-aspect-ratio-' + oldScrObj.aspectRatioName.toLowerCase()); + } + } + if (type) { + Dom.addBodyClass('enyo-res-' + type.toLowerCase()); + var scrObj = getScreenTypeObject(type); + if (scrObj.aspectRatioName) { + Dom.addBodyClass('enyo-aspect-ratio-' + scrObj.aspectRatioName.toLowerCase()); + } + return type; + } + }, + + /** + * @private + */ + getRiRatio: function (type) { + type = type || _screenType; + if (type) { + var ratio = this.getUnitToPixelFactors(type) / this.getUnitToPixelFactors(_baseScreenType); + if (type == _screenType) { + // cache this if it's for our current screen type. + _riRatio = ratio; + } + return ratio; + } + return 1; + }, + + /** + * @private + */ + getUnitToPixelFactors: function (type) { + type = type || _screenType; + if (type) { + return getScreenTypeObject(type).pxPerRem; + } + return 1; + }, + + /** + * Calculates the aspect ratio of the screen type provided. If none is provided the current + * screen type is used. + * + * @param {String} type Screen type to get the aspect ratio of. Providing nothing uses the + * current screen type. + * @returns {Number} The calculated screen ratio (1.333, 1.777, 2.333, etc) + * @public + */ + getAspectRatio: function (type) { + var scrObj = getScreenTypeObject(type); + if (scrObj.width && scrObj.height) { + return (scrObj.width / scrObj.height); + } + return 1; + }, + + /** + * Returns the name of the aspect ration given the screen type or the default screen type if + * none is proided. + * + * @param {String} type Screen type to get the aspect ratio of. Providing nothing uses the + * current screen type. + * @returns {String} The name of the type of screen ratio + * @public + */ + getAspectRatioName: function (type) { + var scrObj = getScreenTypeObject(type); + return scrObj.aspectRatioName || 'standard'; + }, + + /** + * Takes a provided pixel value and preforms a scaling operation on the number based on the + * current screen type. + * + * @param {Number} px The amount of standard-resolution pixels to scale to the current screen + * resolution. + * @returns {Number} The scaled value based on the current screen scaling factor. + * @public + */ + scale: function (px) { + return (_riRatio || this.getRiRatio()) * px; + }, + + /** + * The default configurable [options]{@link ri.selectSrc#options}. + * + * @typedef {Object} ri.selectSrc~src + * @property {String} hd - HD / 720p Resolution image asset source URI/URL + * @property {String} fhd - FHD / 1080p Resolution image asset source URI/URL + * @property {String} uhd - UHD / 4K Resolution image asset source URI/URL + * + * @typedef {String} ri.selectSrc~src - Image asset source URI/URL + */ + + /** + * Image src chooser. A simple utility method to select the ideal image asset from a set of + * assets, based on various screen resolutions: HD (720p), FHD (1080p), UHD (4k). When provided + * with a src argument, multiResSrc will choose the best image with respect to the current screen + * resolution. `src` may be either the traditional string, which will pass straight through, or a + * hash/object of screen types and their asset sources (keys:screen and values:src). The image + * sources will be used chosen when the screen resolution is less than or equal to the provided + * screen types. + * + * ``` + * // Take advantage of the multi-rez mode + * {kind: 'moon.Image', src: { + * 'hd': 'http://lorempixel.com/64/64/city/1/', + * 'fhd': 'http://lorempixel.com/128/128/city/1/', + * 'uhd': 'http://lorempixel.com/256/256/city/1/' + * }, alt: 'Multi-rez'}, + * // Standard string `src` + * {kind: 'moon.Image', src: http://lorempixel.com/128/128/city/1/', alt: 'Large'}, + * ``` + * + * @param {(String|moon.ri.selectSrc~src)} src A string containing a single image src or a + * key/value hash/object containing keys representing screen types (hd, fhd, uhd, etc) and + * values containing the asset src for that target screen resolution. + * @returns {String} The choosen src given the string or list provided. + * @public + */ + selectSrc: function (src) { + if (typeof src != 'string' && src) { + var i, t, + newSrc = src.fhd || src.uhd || src.hd, + types = _screenTypes; + + // loop through resolutions + for (i = types.length - 1; i >= 0; i--) { + t = types[i].name; + if (_screenType == t && src[t]) newSrc = src[t]; + } + + src = newSrc; + } + return src; + }, + + /** + * This will need to be re-run any time the screen size changes, so all the values can be + * re-cached. + * + * @public + */ + // Later we can wire this up to a screen resize event so it doesn't need to be called manually. + init: function () { + _oldScreenType = _screenType; + _screenType = this.getScreenType(); + _screenTypeObject = getScreenTypeObject(); + this.updateScreenBodyClasses(); + Dom.unitToPixelFactors.rem = this.getUnitToPixelFactors(); + _riRatio = this.getRiRatio(); + } +}; + +ri.init(); + +},{'./dom':'enyo/dom'}],'enyo/HTMLStringDelegate':[function (module,exports,global,require,request){ +require('enyo'); + +var + Dom = require('./dom'); + +var selfClosing = {img: 1, hr: 1, br: 1, area: 1, base: 1, basefont: 1, input: 1, link: 1, + meta: 1, command: 1, embed: 1, keygen: 1, wbr: 1, param: 1, source: 1, track: 1, col: 1}; + +/** +* This is the default render delegate used by {@link module:enyo/Control~Control}. It +* generates the HTML [string]{@glossary String} content and correctly inserts +* it into the DOM. A string-concatenation technique is used to perform DOM +* insertion in batches. +* +* @module enyo/HTMLStringDelegate +* @public +*/ +module.exports = { + + /** + * @private + */ + invalidate: function (control, item) { + switch (item) { + case 'content': + this.renderContent(control); + break; + default: + control.tagsValid = false; + break; + } + }, + + /** + * @private + */ + render: function (control) { + if (control.parent) { + control.parent.beforeChildRender(control); + + if (!control.parent.generated) return; + if (control.tag === null) return control.parent.render(); + } + + if (!control.hasNode()) this.renderNode(control); + if (control.hasNode()) { + this.renderDom(control); + if (control.generated) control.rendered(); + } + }, + + /** + * @private + */ + renderInto: function (control, parentNode) { + parentNode.innerHTML = this.generateHtml(control); + + if (control.generated) control.rendered(); + }, + + /** + * @private + */ + renderNode: function (control) { + this.teardownRender(control); + control.node = document.createElement(control.tag); + control.addNodeToParent(); + control.set('generated', true); + }, + + /** + * @private + */ + renderDom: function (control) { + this.renderAttributes(control); + this.renderStyles(control); + this.renderContent(control); + }, + + /** + * @private + */ + renderStyles: function (control) { + var style = control.style; + + // we can safely do this knowing it will synchronize properly without a double + // set in the DOM because we're flagging the internal property + if (control.hasNode()) { + control.node.style.cssText = style; + // retrieve the parsed value for synchronization + control.cssText = style = control.node.style.cssText; + // now we set it knowing they will be synchronized and everybody that is listening + // will also be updated to know about the change + control.set('style', style); + } + }, + + /** + * @private + */ + renderAttributes: function (control) { + var attrs = control.attributes, + node = control.hasNode(), + key, + val; + + if (node) { + for (key in attrs) { + val = attrs[key]; + if (val === null || val === false || val === "") { + node.removeAttribute(key); + } else { + node.setAttribute(key, val); + } + } + } + }, + + /** + * @private + */ + renderContent: function (control) { + if (control.generated) this.teardownChildren(control); + if (control.hasNode()) control.node.innerHTML = this.generateInnerHtml(control); + }, + + /** + * @private + */ + generateHtml: function (control) { + var content, + html; + + if (control.canGenerate === false) { + return ''; + } + // do this first in case content generation affects outer html (styles or attributes) + content = this.generateInnerHtml(control); + // generate tag, styles, attributes + html = this.generateOuterHtml(control, content); + // NOTE: 'generated' is used to gate access to findNodeById in + // hasNode, because findNodeById is expensive. + // NOTE: we typically use 'generated' to mean 'created in DOM' + // but that has not actually happened at this point. + // We set 'generated = true' here anyway to avoid having to walk the + // control tree a second time (to set it later). + // The contract is that insertion in DOM will happen synchronously + // to generateHtml() and before anybody should be calling hasNode(). + control.set('generated', true); + return html; + }, + + /** + * @private + */ + generateOuterHtml: function (control, content) { + if (!control.tag) return content; + if (!control.tagsValid) this.prepareTags(control); + return control._openTag + content + control._closeTag; + }, + + /** + * @private + */ + generateInnerHtml: function (control) { + var allowHtml = control.allowHtml, + content; + + // flow can alter the way that html content is rendered inside + // the container regardless of whether there are children. + control.flow(); + if (control.children.length) return this.generateChildHtml(control); + else { + content = control.get('content'); + return allowHtml ? content : Dom.escape(content); + } + }, + + /** + * @private + */ + generateChildHtml: function (control) { + var child, + html = '', + i = 0, + delegate; + + for (; (child = control.children[i]); ++i) { + delegate = child.renderDelegate || this; + html += delegate.generateHtml(child); + } + + return html; + }, + + /** + * @private + */ + prepareTags: function (control) { + var html = ''; + + // open tag + html += '<' + control.tag + (control.style ? ' style="' + control.style + '"' : ''); + html += this.attributesToHtml(control.attributes); + if (selfClosing[control.tag]) { + control._openTag = html + '/>'; + control._closeTag = ''; + } else { + control._openTag = html + '>'; + control._closeTag = ''; + } + + control.tagsValid = true; + }, + + /** + * @private + */ + attributesToHtml: function(attrs) { + var key, + val, + html = ''; + + for (key in attrs) { + val = attrs[key]; + if (val != null && val !== false && val !== '') { + html += ' ' + key + '="' + this.escapeAttribute(val) + '"'; + } + } + + return html; + }, + + /** + * @private + */ + escapeAttribute: function (text) { + if (typeof text != 'string') return text; + + return String(text).replace(/&/g, '&').replace(/\"/g, '"'); + }, + + /** + * @private + */ + teardownRender: function (control, cache) { + if (control.generated) this.teardownChildren(control, cache); + control.node = null; + control.set('generated', false); + }, + + /** + * @private + */ + teardownChildren: function (control, cache) { + var child, + i = 0; + + for (; (child = control.children[i]); ++i) { + child.teardownRender(cache); + } + } +}; + +},{'./dom':'enyo/dom'}],'enyo/AnimationSupport/Frame':[function (module,exports,global,require,request){ +require('enyo'); + +var + Dom = require('../dom'), + Vector = require('./Vector'), + Matrix = require('./Matrix'); + +var + COLOR = {"color": 1, "background-color": 1}, + TRANSFORM = {"translate": 1, "translateX": 1, "translateY": 1, "translateZ": 1, "rotateX": 1, "rotateY": 1, "rotateZ": 1, "rotate": 1, "skew": 1, "scale": 1, "perspective": 1}; + +/** +* Frame is a module responsible for providing animation features required for a frame. +* This module exposes bunch of animation API's like matrix calculation, +* fetching inital DOM properties and also applying style updates to DOM. +* +* These methods need to be merged with DOM API's of enyo. +* +* @module enyo/AnimationSupport/Frame +*/ +var frame = module.exports = { + /** + * @public + * Creates a matrix based on transformation vectors. + * @param: trns- translate vector + * rot - rotate quaternion vector + * sc - scale vector + * sq - sqew vector + * per - perspective vector + */ + recomposeMatrix: function(trns, rot, sc, sq, per) { + var i, + x = rot[0], + y = rot[1], + z = rot[2], + w = rot[3], + m = Matrix.identity(), + sM = Matrix.identity(), + rM = Matrix.identity(); + + // apply perspective + if(per) { + m[3] = per[0]; + m[7] = per[1]; + m[11] = per[2]; + m[15] = per[3]; + } + + m[12] = trns[0]; + m[13] = trns[1]; + m[14] = trns[2]; + + // apply rotate + rM[0] = 1 - 2 * (y * y + z * z); + rM[1] = 2 * (x * y - z * w); + rM[2] = 2 * (x * z + y * w); + rM[4] = 2 * (x * y + z * w); + rM[5] = 1 - 2 * (x * x + z * z); + rM[6] = 2 * (y * z - x * w); + rM[8] = 2 * (x * z - y * w); + rM[9] = 2 * (y * z + x * w); + rM[10] = 1 - 2 * (x * x + y * y); + + m = Matrix.multiply(m, rM); + + // apply skew + if (sq[2]) { + sM[9] = sq[2]; + m = Matrix.multiply(m, sM); + } + + if (sq[1]) { + sM[9] = 0; + sM[8] = sq[1]; + m = Matrix.multiply(m, sM); + } + + if (sq[0]) { + sM[8] = 0; + sM[4] = sq[0]; + m = Matrix.multiply(m, sM); + } + + // apply scale + for (i = 0; i < 12; i += 4) { + m[0 + i] *= sc[0]; + m[1 + i] *= sc[1]; + m[2 + i] *= sc[2]; + } + return m; + }, + + /** + * @public + * Get transformation vectors out of matrix + * @param matrix - Transformation matrix + * ret - Return object which holds translate, + * rotate, scale, sqew & perspective. + */ + decomposeMatrix: function(matrix, ret) { + var i, + tV = [], + rV = [], + pV = [], + skV = [], + scV = [], + row = [], + pdum3 = {}; + + if (matrix[15] === 0) return false; + + for (i = 0; i < 16; i++) + matrix[0] /= matrix[15]; + + //TODO: decompose perspective + pV = [0, 0, 0, 0]; + + for (i = 0; i < 3; i++) + tV[i] = matrix[12 + i]; + + for (i = 0; i < 12; i += 4) { + row.push([ + matrix[0 + i], + matrix[1 + i], + matrix[2 + i] + ]); + } + + scV[0] = Vector.len(row[0]); + row[0] = Vector.normalize(row[0]); + skV[0] = Vector.dot(row[0], row[1]); + row[1] = Vector.combine(row[1], row[0], 1.0, -skV[0]); + + scV[1] = Vector.len(row[1]); + row[1] = Vector.normalize(row[1]); + skV[0] /= scV[1]; + + // Compute XZ and YZ shears, orthogonalize 3rd row + skV[1] = Vector.dot(row[0], row[2]); + row[2] = Vector.combine(row[2], row[0], 1.0, -skV[1]); + skV[2] = Vector.dot(row[1], row[2]); + row[2] = Vector.combine(row[2], row[1], 1.0, -skV[2]); + + // Next, get Z scale and normalize 3rd row. + scV[2] = Vector.len(row[2]); + row[2] = Vector.normalize(row[2]); + skV[1] /= scV[2]; + skV[2] /= scV[2]; + + pdum3 = Vector.cross(row[1], row[2]); + if (Vector.dot(row[0], pdum3) < 0) { + for (i = 0; i < 3; i++) { + scV[i] *= -1; + row[i][0] *= -1; + row[i][1] *= -1; + row[i][2] *= -1; + } + } + + rV[0] = 0.5 * Math.sqrt(Math.max(1 + row[0][0] - row[1][1] - row[2][2], 0)); + rV[1] = 0.5 * Math.sqrt(Math.max(1 - row[0][0] + row[1][1] - row[2][2], 0)); + rV[2] = 0.5 * Math.sqrt(Math.max(1 - row[0][0] - row[1][1] + row[2][2], 0)); + rV[3] = 0.5 * Math.sqrt(Math.max(1 + row[0][0] + row[1][1] + row[2][2], 0)); + + if (row[2][1] > row[1][2]) rV[0] = -rV[0]; + if (row[0][2] > row[2][0]) rV[1] = -rV[1]; + if (row[1][0] > row[0][1]) rV[2] = -rV[2]; + + ret.translate = tV; + ret.rotate = rV; + ret.scale = scV; + ret.skew = skV; + ret.perspective = pV; + return true; + }, + + /** + * Clones an array based on offset value. + * @public + */ + copy: function(v, offset) { + return Array.prototype.slice.call(v, offset || 0); + }, + + /** + * Validates if property is a transform property. + * @public + */ + isTransform: function(transform) { + return TRANSFORM[transform]; + }, + + /** + * Applies trasnformation to DOM element with the matrix values. + * @public + */ + accelerate: function (ele, m) { + m = m ? m : Matrix.identity(); + frame.setTransformProperty(ele, m); + }, + + /** + * Reform matrix 2D to 3D + * @public + */ + parseMatrix: function (v) { + var m = Matrix.identity(); + v = v.replace(/^\w*\(/, '').replace(')', ''); + v = this.parseValue(v); + if (v.length <= 6) { + m[0] = v[0]; + m[1] = v[1]; + m[4] = v[2]; + m[5] = v[3]; + m[12] = v[4]; + m[13] = v[5]; + } else { + m = v; + } + return m; + }, + + /** + * Converts comma seperated values to array. + * @public + */ + parseValue: function (val) { + return val.toString().split(",").map(function(v) { + return parseFloat(v, 10); + }); + }, + + /** + * Gets a matrix for DOM element. + * @public + */ + getMatrix: function (style) { + var m = style.getPropertyValue('transform') || + style.getPropertyValue('-moz-transform') || + style.getPropertyValue('-webkit-transform') || + style.getPropertyValue('-ms-transform') || + style.getPropertyValue('-o-transform'); + if (m === undefined || m === null || m == "none") { + return ""; + } + return this.parseMatrix(m); + }, + + /** + * Gets a style property applied from the DOM element. + * @param: style - Computed style of a DOM. + * key - property name for which style has to be fetched. + * @public + */ + getStyleValue: function (style, key) { + var v = style.getPropertyValue(key); + if (v === undefined || v === null || v == "auto" || isNaN(v)) { + return 0; + } + if (COLOR[key]) { + return v.replace(/^\w*\(/, '').replace(')', ''); + } + v = parseFloat(v, 10); + return v; + }, + + + /** + * Applies style property to DOM element. + * @public + */ + setProperty: function (ele, prop, val) { + if (COLOR[prop]) { + val = val.map(function(v) { return parseInt(v, 10);}); + val = 'rgb('+ val + ')'; + } else if (prop == 'opacity') { + val = val[0].toFixed(6); + val = (val <= 0) ? '0.000001' : val; + } else { + val = val[0] + 'px'; + } + ele.style[prop] = val; + }, + + /** + * Applies transform property to DOM element. + * @public + */ + setTransformProperty: function (element, matrix) { + var mat = Matrix.toString(matrix); + element.style.transform = mat; + element.style.webkitTransform = mat; + element.style.MozTransform = mat; + element.style.msTransform = mat; + element.style.OTransform = mat; + }, + + /** + * Get DOM node animation properties. + * @param: node- DOM node + * props- Properties to fetch from DOM. + * initial-Default properties to be applied. + * @public + */ + getCompoutedProperty: function (node, props, initial) { + if(!node || !props) return; + + var eP = {}, + sP = initial ? this.copy(initial) : {}, + tP = {}, + dP = {}, + m, k, v, + s = Dom.getComputedStyle(node); + + for (k in props) { + v = sP[k]; + if (!this.isTransform(k)) { + v = v || this.getStyleValue(s, k); + eP[k] = this.parseValue(props[k]); + sP[k] = this.parseValue(v); + } else { + v = this.parseValue(props[k]); + tP[k] = k == 'rotate' ? Vector.toQuant(v) : v; + } + } + + if (initial) { + dP.translate = initial.translate; + dP.rotate = initial.rotate; + dP.scale = initial.scale; + dP.skew = initial.skew; + dP.perspective = initial.perspective; + } else { + m = this.getMatrix(s) || Matrix.identity(); + this.decomposeMatrix(m, dP); + } + + for(k in dP) { + sP[k] = dP[k]; + eP[k] = tP[k] || dP[k]; + } + return {_startAnim: sP, _endAnim: eP, _transform: dP, currentState: dP}; + } +}; +},{'../dom':'enyo/dom','./Vector':'enyo/AnimationSupport/Vector','./Matrix':'enyo/AnimationSupport/Matrix'}],'enyo/gesture/util':[function (module,exports,global,require,request){ +var + dom = require('../dom'), + platform = require('../platform'), + utils = require('../utils'); + +/** +* Used internally by {@link module:enyo/gesture} +* +* @module enyo/gesture/util +* @private +*/ +module.exports = { + + /** + * @private + */ + eventProps: ['target', 'relatedTarget', 'clientX', 'clientY', 'pageX', 'pageY', + 'screenX', 'screenY', 'altKey', 'ctrlKey', 'metaKey', 'shiftKey', + 'detail', 'identifier', 'dispatchTarget', 'which', 'srcEvent'], + + /** + * Creates an {@glossary event} of type `type` and returns it. + * `evt` should be an event [object]{@glossary Object}. + * + * @param {String} type - The type of {@glossary event} to make. + * @param {(Event|Object)} evt - The event you'd like to clone or an object that looks like it. + * @returns {Object} The new event [object]{@glossary Object}. + * @public + */ + makeEvent: function(type, evt) { + var e = {}; + e.type = type; + for (var i=0, p; (p=this.eventProps[i]); i++) { + e[p] = evt[p]; + } + e.srcEvent = e.srcEvent || evt; + e.preventDefault = this.preventDefault; + e.disablePrevention = this.disablePrevention; + + if (dom._bodyScaleFactorX !== 1 || dom._bodyScaleFactorY !== 1) { + // Intercept only these events, not all events, like: hold, release, tap, etc, + // to avoid doing the operation again. + if (e.type == 'move' || e.type == 'up' || e.type == 'down' || e.type == 'enter' || e.type == 'leave') { + e.clientX *= dom._bodyScaleFactorX; + e.clientY *= dom._bodyScaleFactorY; + } + } + // + // normalize event.which and event.pageX/event.pageY + // Note that while 'which' works in IE9, it is broken for mousemove. Therefore, + // in IE, use global.event.button + if (platform.ie < 10) { + //Fix for IE8, which doesn't include pageX and pageY properties + if(platform.ie==8 && e.target) { + e.pageX = e.clientX + e.target.scrollLeft; + e.pageY = e.clientY + e.target.scrollTop; + } + var b = global.event && global.event.button; + if (b) { + // multi-button not supported, priority: left, right, middle + // (note: IE bitmask is 1=left, 2=right, 4=center); + e.which = b & 1 ? 1 : (b & 2 ? 2 : (b & 4 ? 3 : 0)); + } + } else if (platform.webos || global.PalmSystem) { + // Temporary fix for owos: it does not currently supply 'which' on move events + // and the user agent string doesn't identify itself so we test for PalmSystem + if (e.which === 0) { + e.which = 1; + } + } + return e; + }, + + /** + * Installed on [events]{@glossary event} and called in event context. + * + * @private + */ + preventDefault: function() { + if (this.srcEvent) { + this.srcEvent.preventDefault(); + } + }, + + /** + * @private + */ + disablePrevention: function() { + this.preventDefault = utils.nop; + if (this.srcEvent) { + this.srcEvent.preventDefault = utils.nop; + } + } +}; + +},{'../dom':'enyo/dom','../platform':'enyo/platform','../utils':'enyo/utils'}],'enyo/ApplicationSupport':[function (module,exports,global,require,request){ +/** +* Exports the {@link module:enyo/ApplicationSupport~ApplicationSupport} mixin. +* @module enyo/ApplicationSupport +*/ + +require('enyo'); + +var kind = require('./kind'); + +/** +* An internally-used support {@glossary mixin} that is applied to all +* [components]{@link module:enyo/Component~Component} of an {@link module:enyo/Application~Application} instance +* (and to their components, recursively). This mixin adds an `app` property to +* each component -- a local reference to the `Application` instance that +* the component belongs to. +* +* @mixin +* @protected +*/ +var ApplicationSupport = { + + /** + * @private + */ + name: 'ApplicationSupport', + + /** + * @private + */ + adjustComponentProps: kind.inherit(function (sup) { + return function (props) { + props.app = props.app || this.app; + sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + destroy: kind.inherit(function (sup) { + return function () { + // release the reference to the application + this.app = null; + sup.apply(this, arguments); + }; + }) + +}; + +module.exports = ApplicationSupport; + +},{'./kind':'enyo/kind'}],'enyo/ComponentBindingSupport':[function (module,exports,global,require,request){ +/** +* Exports the {@link module:enyo/ComponentBindingSupport~ComponentBindingSupport} mixin. +* @module enyo/ComponentBindingSupport +*/ + +require('enyo'); + +var + kind = require('./kind'); + +/** +* An internally-used {@glossary mixin} applied to {@link module:enyo/Component~Component} +* instances to better support [bindings]{@link module:enyo/Binding~Binding}. +* +* @mixin +* @protected +*/ +var ComponentBindingSupport = { + + /** + * @private + */ + name: 'ComponentBindingSupport', + + /** + * @private + */ + adjustComponentProps: kind.inherit(function (sup) { + return function (props) { + sup.apply(this, arguments); + props.bindingTransformOwner || (props.bindingTransformOwner = this.getInstanceOwner()); + }; + }) +}; + +module.exports = ComponentBindingSupport; + +},{'./kind':'enyo/kind'}],'enyo/MultipleDispatchSupport':[function (module,exports,global,require,request){ +/** +* Exports the {@link module:enyo/MultipleDispatchSupport~MultipleDispatchSupport} mixin. +* @module enyo/MultipleDispatchSupport +*/ + +require('enyo'); + +var + kind = require('./kind'), + utils = require('./utils'); + +/** +* A collection of methods to allow a single {@link module:enyo/Component~Component} to +* [dispatch]{@link module:enyo/Component~Component#dispatchEvent} a single {@glossary event} to +* multiple targets. The events are synchronously propagated in the order in +* which the targets are encountered. Note that this {@glossary mixin} is +* already applied to a base [kind]{@glossary kind}, +* {@link module:enyo/MultipleDispatchComponent~MultipleDispatchComponent}. +* +* @mixin +* @public +*/ +var MultipleDispatchSupport = { + + /** + * @private + */ + name: 'MultipleDispatchSupport', + + /** + * Adds a target for dispatching. + * + * @param {module:enyo/Component~Component} component - The {@link module:enyo/Component~Component} to add as a dispatch target. + * @public + */ + addDispatchTarget: function (component) { + var dt = this._dispatchTargets; + if (component && !~utils.indexOf(component, dt)) { + dt.push(component); + } + }, + /** + * Removes a target from dispatching. + * + * @param {module:enyo/Component~Component} component - The {@link module:enyo/Component~Component} to remove as a dispatch + * target. + * @public + */ + removeDispatchTarget: function (component) { + var dt = this._dispatchTargets, i; + i = utils.indexOf(component, dt); + if (i > -1) { + dt.splice(i, 1); + } + }, + + /** + * @private + */ + bubbleUp: kind.inherit(function (sup) { + return function (name, event, sender) { + if (this._dispatchDefaultPath) { + sup.apply(this, arguments); + } + var dt = this._dispatchTargets; + for (var i=0, t; (t=dt[i]); ++i) { + if (t && !t.destroyed) { + t.dispatchBubble(name, event, sender); + } + } + }; + }), + + /** + * @private + */ + ownerChanged: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + var o = this.owner; + this._dispatchDefaultPath = !! o; + }; + }), + + /** + * @private + */ + constructor: kind.inherit(function (sup) { + return function () { + this._dispatchTargets = []; + return sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + destroy: kind.inherit(function (sup) { + return function () { + this._dispatchTargets = null; + sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + _dispatchDefaultPath: false +}; + +module.exports = MultipleDispatchSupport; + +},{'./kind':'enyo/kind','./utils':'enyo/utils'}],'enyo/Control/floatingLayer':[function (module,exports,global,require,request){ +/** +* Exports the {@link module:enyo/Control/floatingLayer~FloatingLayer} singleton instance. +* @module enyo/Control/floatingLayer +*/ + +var + kind = require('../kind'), + platform = require('../platform'); + +module.exports = function (Control) { + /** + * {@link module:enyo/Control/floatingLayer~FloatingLayer} is a + * [control]{@link module:enyo/Control~Control} that provides a layer for controls that should be + * displayed above an [application]{@link module:enyo/Application~Application}. The `floatingLayer` + * singleton can be set as a control's parent to have the control float above the application, e.g.: + * + * ``` + * var floatingLayer = require('enyo/floatingLayer'); + * ... + * create: kind.inherit(function (sup) { + * return function() { + * sup.apply(this, arguments); + * this.setParent(floatingLayer); + * } + * }); + * ``` + * + * Note: `FloatingLayer` is not meant to be instantiated by users. + * + * @class FloatingLayer + * @extends module:enyo/Control~Control + * @ui + * @protected + */ + var FloatingLayer = kind( + /** @lends module:enyo/Control/floatingLayer~FloatingLayer.prototype */ { + + /** + * @private + */ + kind: Control, + + /** + * @private + */ + classes: 'enyo-fit enyo-clip enyo-untouchable', + + /** + * @private + */ + accessibilityPreventScroll: true, + + /** + * @method + * @private + */ + create: kind.inherit(function (sup) { + return function() { + sup.apply(this, arguments); + this.setParent(null); + + if (platform.ie < 11) { + this.removeClass('enyo-fit'); + } + }; + }), + + /** + * Detects when [node]{@glossary Node} is detatched due to `document.body` being stomped. + * + * @method + * @private + */ + hasNode: kind.inherit(function (sup) { + return function() { + sup.apply(this, arguments); + if (this.node && !this.node.parentNode) { + this.teardownRender(); + } + return this.node; + }; + }), + + /** + * @method + * @private + */ + render: kind.inherit(function (sup) { + return function() { + this.parentNode = document.body; + return sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + generateInnerHtml: function () { + return ''; + }, + + /** + * @private + */ + beforeChildRender: function () { + if (!this.hasNode()) { + this.render(); + } + }, + + /** + * @private + */ + teardownChildren: function () { + } + }); + + return FloatingLayer; +}; +},{'../kind':'enyo/kind','../platform':'enyo/platform'}],'enyo/MixinSupport':[function (module,exports,global,require,request){ +/** +* Exports the {@link module:enyo/MixinSupport~MixinSupport} mixin. +* @module enyo/MixinSupport +*/ + +require('enyo'); + + +var + utils = require('./utils'), + kind = require('./kind'), + logger = require('./logger'); + +kind.concatenated.push('mixins'); + +var sup = kind.statics.extend; + +var extend = kind.statics.extend = function extend (args, target) { + if (utils.isArray(args)) return utils.forEach(args, function (ln) { extend.call(this, ln, target); }, this); + if (typeof args == 'string') apply(target || this.prototype, args); + else { + if (args.mixins) feature(target || this, args); + + // this allows for mixins to apply mixins which...is less than ideal but possible + if (args.name) apply(target || this.prototype, args); + else sup.apply(this, arguments); + } +}; + +/* +* Applies, with safeguards, a given mixin to an object. +*/ +function apply (proto, props) { + var applied = proto._mixins? (proto._mixins = proto._mixins.slice()): (proto._mixins = []) + , name = utils.isString(props)? props: props.name + , idx = utils.indexOf(name, applied); + if (idx < 0) { + name == props && (props = utils.getPath(name)); + // if we could not resolve the requested mixin (should never happen) + // we throw a simple little error + // @TODO: Normalize error format + !props && logger.error('Could not find the mixin ' + name); + + // it should be noted that this ensures it won't recursively re-add the same mixin but + // since it is possible for mixins to apply mixins the names will be out of order + // this name is pushed on but the nested mixins are applied before this one + name && applied.push(name); + + props = utils.clone(props); + + // we need to temporarily move the constructor if it has one so it + // will override the correct method - this is a one-time permanent + // runtime operation so subsequent additions of the mixin don't require + // it again + if (props.hasOwnProperty('constructor')) { + props._constructor = props.constructor; + delete props.constructor; + } + + delete props.name; + extend(props, proto); + + // now put it all back the way it was + props.name = name; + } +} + +function feature (ctor, props) { + if (props.mixins) { + var proto = ctor.prototype || ctor + , mixins = props.mixins; + + // delete props.mixins; + // delete proto.mixins; + + proto._mixins && (proto._mixins = proto._mixins.slice()); + utils.forEach(mixins, function (ln) { apply(proto, ln); }); + } +} + +kind.features.push(feature); + +/** +* An internally-used support {@glossary mixin} that adds API methods to aid in +* using and applying mixins to [kinds]{@glossary kind}. +* +* @mixin +* @protected +*/ +var MixinSupport = { + + /** + * @private + */ + name: 'MixinSupport', + + /** + * Extends the instance with the given properties. + * + * @param {Object} props - The property [hash]{@glossary Object} from which to extend + * the callee. + */ + extend: function (props) { + props && apply(this, props); + }, + + /** + * @private + */ + importProps: kind.inherit(function (sup) { + return function (props) { + props && props.mixins && feature(this, props); + + sup.apply(this, arguments); + }; + }) +}; + +module.exports = MixinSupport; + +},{'./utils':'enyo/utils','./kind':'enyo/kind','./logger':'enyo/logger'}],'enyo/ComputedSupport':[function (module,exports,global,require,request){ +/** +* Exports the {@link module:enyo/ComputedSupport~ComputedSupport} mixin. +* @module enyo/ComputedSupport +*/ + +require('enyo'); + +var + kind = require('./kind'), + utils = require('./utils'); + +var extend = kind.statics.extend; + +kind.concatenated.push('computed'); + +function getComputedValue (obj, path) { + var cache = obj._getComputedCache(path) + , isCached = obj._isComputedCached(path); + + // in the end, for efficiency and completeness in other situations + // it is better to know the returned value of all computed properties + // but in cases where they are set as cached we will sometimes use + // that value + if (cache.dirty || cache.dirty === undefined) { + isCached && (cache.dirty = false); + cache.previous = cache.value; + cache.value = obj[path](); + } + + return cache.value; +} + +function queueComputed (obj, path) { + var queue = obj._computedQueue || (obj._computedQueue = []) + , deps = obj._computedDependencies[path]; + + if (deps) { + for (var i=0, dep; (dep=deps[i]); ++i) { + if (!queue.length || -1 == queue.indexOf(dep)) queue.push(dep); + } + } +} + +function flushComputed (obj) { + var queue = obj._computedQueue; + obj._computedQueue = null; + if (queue && obj.isObserving()) { + for (var i=0, ln; (ln=queue[i]); ++i) { + obj.notify(ln, obj._getComputedCache(ln).value, getComputedValue(obj, ln)); + } + } +} + +/** +* A {@glossary mixin} that adds API methods to support +* [computed properties]{@glossary computed_property}. Unlike other support mixins, +* this mixin does not need to be explicitly included by a [kind]{@glossary kind}. If the +* `computed` [array]{@glossary Array} is found in a kind definition, this mixin will +* automatically be included. +* +* @mixin +* @public +*/ +var ComputedSupport = { + /** + * @private + */ + name: 'ComputedSuport', + + /** + * @private + */ + _computedRecursion: 0, + + /** + * Primarily intended for internal use, this method determines whether the + * given path is a known [computed property]{@glossary computed_property}. + * + * @param {String} path - The property or path to test. + * @returns {Boolean} Whether or not the `path` is a + * [computed property]{@glossary computed_property}. + * @public + */ + isComputed: function (path) { + // if it exists it will be explicitly one of these cases and it is cheaper than hasOwnProperty + return this._computed && (this._computed[path] === true || this._computed[path] === false); + }, + + /** + * Primarily intended for internal use, this method determines whether the + * given path is a known dependency of a + * [computed property]{@glossary computed_property}. + * + * @param {String} path - The property or path to test. + * @returns {Boolean} Whether or not the `path` is a dependency of a + * [computed property]{@glossary computed_property}. + * @public + */ + isComputedDependency: function (path) { + return !! (this._computedDependencies? this._computedDependencies[path]: false); + }, + + /** + * @private + */ + get: kind.inherit(function (sup) { + return function (path) { + return this.isComputed(path)? getComputedValue(this, path): sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + set: kind.inherit(function (sup) { + return function (path) { + // we do not accept parameters for computed properties + return this.isComputed(path)? this: sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + notifyObservers: function () { + return this.notify.apply(this, arguments); + }, + + /** + * @private + */ + notify: kind.inherit(function (sup) { + return function (path, was, is) { + this.isComputedDependency(path) && queueComputed(this, path); + this._computedRecursion++; + sup.apply(this, arguments); + this._computedRecursion--; + this._computedQueue && this._computedRecursion === 0 && flushComputed(this); + return this; + }; + }), + + /** + * @private + */ + _isComputedCached: function (path) { + return this._computed[path]; + }, + + /** + * @private + */ + _getComputedCache: function (path) { + var cache = this._computedCache || (this._computedCache = {}); + return cache[path] || (cache[path] = {}); + } +}; + +module.exports = ComputedSupport; + +/* +* Hijack the original so we can add additional default behavior. +*/ +var sup = kind.concatHandler; + +// @NOTE: It seems like a lot of work but it really won't happen that much and the more +// we push to kind-time the better for initialization time + +/** +* @private +*/ +kind.concatHandler = function (ctor, props, instance) { + + sup.call(this, ctor, props, instance); + + // only matters if there are computed properties to manage + if (props.computed) { + + var proto = ctor.prototype || ctor + , computed = proto._computed? Object.create(proto._computed): {} + , dependencies = proto._computedDependencies? Object.create(proto._computedDependencies): {}; + + // if it hasn't already been applied we need to ensure that the prototype will + // actually have the computed support mixin present, it will not apply it more + // than once to the prototype + extend(ComputedSupport, proto); + + // @NOTE: This is the handling of the original syntax provided for computed properties in 2.3.ish... + // All we do here is convert it to a structure that can be used for the other scenario and preferred + // computed declarations format + if (!props.computed || !(props.computed instanceof Array)) { + (function () { + var tmp = [], deps, name, conf; + // here is the slow iteration over the properties... + for (name in props.computed) { + // points to the dependencies of the computed method + deps = props.computed[name]; + /*jshint -W083 */ + conf = deps && deps.find(function (ln) { + // we deliberately remove the entry here and forcibly return true to break + return typeof ln == 'object'? (utils.remove(deps, ln) || true): false; + }); + /*jshint +W083 */ + // create a single entry now for the method/computed with all dependencies + tmp.push({method: name, path: deps, cached: conf? conf.cached: null}); + } + + // note that we only do this one so even for a mixin that is evaluated several + // times this would only happen once + props.computed = tmp; + }()); + } + + var addDependency = function (path, dep) { + // its really an inverse look at the original + var deps; + + if (dependencies[path] && !dependencies.hasOwnProperty(path)) dependencies[path] = dependencies[path].slice(); + deps = dependencies[path] || (dependencies[path] = []); + deps.push(dep); + }; + + // now we handle the new computed properties the way we intended to + for (var i=0, ln; (ln=props.computed[i]); ++i) { + // if the entry already exists we are merely updating whether or not it is + // now cached + computed[ln.method] = !! ln.cached; + // we must now look to add an entry for any given dependencies and map them + // back to the computed property they will trigger + /*jshint -W083 */ + if (ln.path && ln.path instanceof Array) ln.path.forEach(function (dep) { addDependency(dep, ln.method); }); + /*jshint +W083 */ + else if (ln.path) addDependency(ln.path, ln.method); + } + + // arg, free the key from the properties so it won't be applied later... + // delete props.computed; + // make sure to reassign the correct items to the prototype + proto._computed = computed; + proto._computedDependencies = dependencies; + } +}; + +},{'./kind':'enyo/kind','./utils':'enyo/utils'}],'enyo/Source':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Source~Source} kind. +* @module enyo/Source +*/ + +var + kind = require('./kind'), + utils = require('./utils'), + logger = require('./logger'); + +/** +* All of the known, instanced [sources]{@link module:enyo/Source~Source}, by name. +* +* @name enyo~sources +* @type {Object} +* @readonly +*/ +var sources = {}; + +/** +* This is an abstract base class. A [source]{@link module:enyo/Source~Source} is a communication +* layer used by data layer [kinds]{@glossary kind} to retrieve and persist data and +* application state via its abstract API methods. +* +* @class Source +* @public +*/ +var Source = module.exports = kind( + /** @lends module:enyo/Source~Source.prototype */ { + + name: 'enyo.Source', + + /** + * @private + */ + kind: null, + + /** + * @private + */ + + + /** + * When initialized, the source should be passed properties to set on itself. + * These properties should include the name by which it will be referenced in + * the application. + * + * @param {Object} [props] - The properties to set on itself. + * @public + */ + constructor: function (props) { + if (props) this.importProps(props); + // automatic coersion of name removing prefix + this.name || (this.name = this.kindName.replace(/^(.*)\./, "")); + // now add to the global registry of sources + sources[this.name] = this; + }, + + /** + * Overload this method to handle retrieval of data. This method should accept an options + * [hash]{@glossary Object} with additional configuration properties, including `success` + * and `error` callbacks to handle the result. + * + * @virtual + * @param {(module:enyo/Model~Model|module:enyo/Collection~Collection)} model The [model]{@link module:enyo/Model~Model} or + * [collection]{@link module:enyo/Collection~Collection} to be retrieved. + * @param {Object} opts - The configuration options [hash]{@glossary Object}, including + * `success` and `error` callbacks. + */ + fetch: function (model, opts) { + // + }, + + /** + * Overload this method to handle persisting of data. This method should accept an options + * [hash]{@glossary Object} with additional configuration properties, including `success` + * and `error` callbacks to handle the result. + * + * @virtual + * @param {(module:enyo/Model~Model|module:enyo/Collection~Collection)} model The [model]{@link module:enyo/Model~Model} or + * [collection]{@link module:enyo/Collection~Collection} to be persisted. + * @param {Object} opts - The configuration options [hash]{@glossary Object}, including + * `success` and `error` callback. + */ + commit: function (model, opts) { + // + }, + + /** + * Overload this method to handle deletion of data. This method should accept an options + * [hash]{@glossary Object} with additional configuration properties, including `success` + * and `error` callbacks to handle the result. If called without parameters, it will + * instead destroy itself and be removed from [enyo.sources]{@link enyo~sources}, rendering + * itself unavailable for further operations. + * + * @param {(module:enyo/Model~Model|module:enyo/Collection~Collection)} model The [model]{@link module:enyo/Model~Model} or + * [collection]{@link module:enyo/Collection~Collection} to be deleted. + * @param {Object} opts - The configuration options [hash]{@glossary Object}, including + * `success` and `error` callbacks. + */ + destroy: function (model, opts) { + + // if called with no parameters we actually just breakdown the source and remove + // it as being available + if (!arguments.length) { + sources[this.name] = null; + this.name = null; + } + }, + + /** + * Overload this method to handle querying of data based on the passed-in constructor. This + * method should accept an options [hash]{@glossary Object} with additional configuration + * properties, including `success` and `error` callbacks to handle the result. + * + * @virtual + * @param {Function} ctor - The constructor for the [kind]{@glossary kind} of + * {@link module:enyo/Model~Model} or {@link module:enyo/Collection~Collection} to be queried. + * @param {Object} opts - The configuration options [hash]{@glossary Object}, including + * `success` and `error` callbacks. + */ + find: function (ctor, opts) { + // + }, + + /** + * @private + */ + importProps: function (props) { + props && utils.mixin(this, props); + }, + + /** + * @see module:enyo/utils#getPath + * @method + * @public + */ + get: utils.getPath, + + /** + * @see module:enyo/utils#setPath + * @method + * @public + */ + set: utils.setPath +}); + +/** +* Creates an instance of {@link module:enyo/Source~Source} with the given properties. These +* properties should include a `kind` property with the name of the +* [kind]{@glossary kind} of source and a `name` for the instance. This static +* method is also available on all [subkinds]{@glossary subkind} of +* `enyo.Source`. The instance will automatically be added to the +* [enyo.sources]{@link enyo~sources} [object]{@glossary Object} and may be +* referenced by its `name`. +* +* @name enyo.Source.create +* @static +* @method +* @param {Object} props - The properties to pass to the constructor for the requested +* [kind]{@glossary kind} of [source]{@link module:enyo/Source~Source}. +* @returns {module:enyo/Source~Source} An instance of the requested kind of source. +* @public +*/ +Source.create = function (props) { + var Ctor = (props && props.kind) || this; + + if (typeof Ctor == 'string') Ctor = kind.constructorForKind(Ctor); + + return new Ctor(props); +}; + +/** +* @static +* @private +*/ +Source.concat = function (ctor, props) { + + // force noDefer so that we can actually set this method on the constructor + if (props) props.noDefer = true; + + ctor.create = Source.create; +}; + +/** +* @static +* @private +*/ +Source.execute = function (action, model, opts) { + var source = opts.source || model.source, + + // we need to be able to bind the success and error callbacks for each of the + // sources we'll be using + options = utils.clone(opts, true), + nom = source, + msg; + + if (source) { + + // if explicitly set to true then we need to use all available sources in the + // application + if (source === true) { + + for (nom in sources) { + source = sources[nom]; + if (source[action]) { + + // bind the source name to the success and error callbacks + options.success = opts.success.bind(null, nom); + options.error = opts.error.bind(null, nom); + + source[action](model, options); + } + } + } + + // if it is an array of specific sources to use we, well, will only use those! + else if (source instanceof Array) { + source.forEach(function (nom) { + var src = typeof nom == 'string' ? sources[nom] : nom; + + if (src && src[action]) { + // bind the source name to the success and error callbacks + options.success = opts.success.bind(null, src.name); + options.error = opts.error.bind(null, src.name); + + src[action](model, options); + } + }); + } + + // if it is an instance of a source + else if (source instanceof Source && source[action]) { + + // bind the source name to the success and error callbacks + options.success = opts.success.bind(null, source.name); + options.error = opts.error.bind(null, source.name); + + source[action](model, options); + } + + // otherwise only one was specified and we attempt to use that + else if ((source = sources[nom]) && source[action]) { + + // bind the source name to the success and error callbacks + options.success = opts.success.bind(null, nom); + options.error = opts.error.bind(null, nom); + + source[action](model, options); + } + + // we could not resolve the requested source + else { + msg = 'enyo.Source.execute(): requested source(s) could not be found for ' + + model.kindName + '.' + action + '()'; + + logger.warn(msg); + + // we need to fail the attempt and let it be handled + opts.error(nom ? typeof nom == 'string' ? nom : nom.name : 'UNKNOWN', msg); + } + } else { + msg = 'enyo.Source.execute(): no source(s) provided for ' + model.kindName + '.' + + action + '()'; + + logger.warn(msg); + + // we need to fail the attempt and let it be handled + opts.error(nom ? typeof nom == 'string' ? nom : nom.name : 'UNKNOWN', msg); + } +}; + +Source.sources = sources; + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./logger':'enyo/logger'}],'enyo/Binding':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Binding~Binding} kind. +* @module enyo/Binding +*/ + +var + kind = require('./kind'), + utils = require('./utils'); + +var bindings = []; + +var DIRTY_FROM = 0x01 + , DIRTY_TO = 0x02; + +/** +* Used to determine if an {@link module:enyo/Binding~Binding} is actually ready. +* +* @private +*/ +function ready (binding) { + var rdy = binding.ready; + + if (!rdy) { + + var from = binding.from || '', + to = binding.to || '', + source = binding.source, + target = binding.target, + owner = binding.owner, + twoWay = !binding.oneWay, + toTarget; + + if (typeof from != 'string') from = ''; + if (typeof to != 'string') to = ''; + + if (!source) { + + // the worst case scenario here is for backward compatibility purposes + // we have to at least be able to derive the source via the from string + if (from[0] == '^') { + + // this means we're reaching for a global + var fromParts = from.split('.'); + from = fromParts.pop(); + source = utils.getPath.call(global, fromParts.join('.').slice(1)); + + } else { + source = owner; + } + + } + + if (!target) { + + // same worst case as above, for backwards compatibility purposes + // we have to at least be able to derive the target via the to string + if (to[0] == '^') { + + // this means we're reaching for a global + var toParts = to.split('.'); + to = toParts.pop(); + target = utils.getPath.call(global, toParts.join('.').slice(1)); + } else { + target = owner; + } + } + + // we do this so we don't overwrite the originals in case we need to reset later + binding._target = target; + binding._source = source; + binding._from = from[0] == '.'? from.slice(1): from; + binding._to = to[0] == '.'? to.slice(1): to; + + if (!twoWay) { + toTarget = binding._to.split('.'); + if (toTarget.length > 2) { + toTarget.pop(); + binding._toTarget = toTarget.join('.'); + } + } + + // now our sanitization + rdy = !! ( + (source && (typeof source == 'object')) && + (target && (typeof target == 'object')) && + (from) && + (to) + ); + } + + /*jshint -W093 */ + return (binding.ready = rdy); + /*jshint +W093 */ +} + +var PassiveBinding = kind( + /** @lends enyo.PassiveBinding.prototype */ { + + name: 'enyo.PassiveBinding', + + /** + * @private + */ + kind: null, + + /** + * This property is used extensively for various purposes within a + * [binding]{@link module:enyo/Binding~Binding}. One primary purpose is to serve as a root + * [object]{@glossary Object} from which to search for the binding's ends (the + * [source]{@link module:enyo/Binding~Binding#source} and/or [target]{@link module:enyo/Binding~Binding#target}). + * If the owner created the binding, it will also be responsible for destroying + * it (automatically). + * + * @type {module:enyo/CoreObject~Object} + * @default null + * @public + */ + owner: null, + + /** + * Set this only to a reference for an [object]{@glossary Object} to use + * as the source for the [binding]{@link module:enyo/Binding~Binding}. If this is not a + * [bindable]{@link module:enyo/BindingSupport~BindingSupport} object, the source will be derived + * from the [from]{@link module:enyo/Binding~Binding#from} property during initialization. + * + * @type {Object} + * @default null + * @public + */ + source: null, + + /** + * Set this only to a reference for an [object]{@glossary Object} to use + * as the target for the [binding]{@link module:enyo/Binding~Binding}. If this is not a + * [bindable]{@link module:enyo/BindingSupport~BindingSupport} object, the target will will be + * derived from the [to]{@link module:enyo/Binding~Binding#to} property during initialization. + * + * @type {Object} + * @default null + * @public + */ + target: null, + + /** + * A path in which the property of the [source]{@link module:enyo/Binding~Binding#source} to + * bind from may be found. If the source is explicitly provided and the path + * is relative (i.e., it begins with a `"."`), it is relative to the source; + * otherwise, it is relative to the [owner]{@link module:enyo/Binding~Binding#owner} of the + * [binding]{@link module:enyo/Binding~Binding}. To have a binding be evaluated from the + * global scope, prefix the path with a `"^"`. If the source and the `"^"` + * are used in tandem, the `"^"` will be ignored and the path will be assumed + * to be relative to the provided source. + * + * @type {String} + * @default null + * @public + */ + from: null, + + /** + * A path in which the property of the [target]{@link module:enyo/Binding~Binding#target} to + * bind from may be found. If the target is explicitly provided and the path + * is relative (i.e., it begins with a `"."`), it is relative to the target; + * otherwise, it is relative to the owner of the [binding]{@link module:enyo/Binding~Binding}. + * To have a binding be evaluated from the global scope, prefix the path with + * a `"^"`. If the target and the `"^"` are used in tandem, the `"^"` will be + * ignored and the path will be assumed to be relative to the provided target. + * + * @type {String} + * @default null + * @public + */ + to: null, + + /** + * Set this to a [function]{@glossary Function} or the name of a method on + * the [owner]{@link module:enyo/Binding~Binding#owner} of this [binding]{@link module:enyo/Binding~Binding}. + * The transform is used to programmatically modify the value being synchronized. + * See {@link module:enyo/Binding~Binding~Transform} for detailed information on the parameters + * that are available to `transform`. + * + * @type {module:enyo/Binding~Binding~Transform} + * @default null + * @public + */ + transform: null, + + /** + * Indicates whether the [binding]{@link module:enyo/Binding~Binding} is actually ready. + * + * @returns {Boolean} `true` if ready; otherwise, `false`. + * @public + */ + isReady: function () { + return this.ready || ready(this); + }, + + /** + * Causes a single propagation attempt to fail. Typically not called outside + * the scope of a [transform]{@link module:enyo/Binding~Binding#transform}. + * + * @public + */ + stop: function () { + this._stop = true; + }, + + /** + * Resets all properties to their original state. + * + * @returns {this} The callee for chaining. + * @public + */ + reset: function () { + this.ready = null; + this._source = this._target = this._to = this._from = this._toTarget = null; + return this; + }, + + /** + * Rebuilds the entire [binding]{@link module:enyo/Binding~Binding} and synchronizes + * the value from the [source]{@link module:enyo/Binding~Binding#source} to the + * [target]{@link module:enyo/Binding~Binding#target}. + * + * @returns {this} The callee for chaining. + * @public + */ + rebuild: function () { + return this.reset().sync(); + }, + + /** + * Synchronizes values from the [source]{@link module:enyo/Binding~Binding#source} to the + * [target]{@link module:enyo/Binding~Binding#target}. This usually will not need to be called manually. + * [Two-way bindings]{@link module:enyo/Binding~Binding#oneWay} will automatically synchronize from the + * target end once they are connected. + * + * @returns {this} The callee for chaining. + * @public + */ + sync: function () { + var source, target, from, to, xform, val; + + if (this.isReady()) { + source = this._source; + target = this._target; + from = this._from; + to = this._to; + xform = this.getTransform(); + val = utils.getPath.apply(source, [from]); + + if (xform) val = xform.call(this.owner || this, val, DIRTY_FROM, this); + if (!this._stop) utils.setPath.apply(target, [to, val, {create: false}]); + } + + return this; + }, + + /** + * Releases all of the [binding's]{@link module:enyo/Binding~Binding} parts. Typically, this method will + * not need to be called directly unless the binding was created without an + * [owner]{@link module:enyo/Binding~Binding#owner}. + * + * @returns {this} The callee for chaining. + * @public + */ + destroy: function () { + var owner = this.owner, + idx; + + this.owner = null; + this.source = this._source = null; + this.target = this._target = null; + this.ready = null; + this.destroyed = true; + + // @todo: remove me or postpone operation? + idx = bindings.indexOf(this); + if (idx > -1) bindings.splice(idx, 1); + + if (owner && !owner.destroyed) owner.removeBinding(this); + + return this; + }, + + /** + * @private + */ + getTransform: function () { + return this._didInitTransform ? this.transform : (function (bnd) { + bnd._didInitTransform = true; + + var xform = bnd.transform, + owner = bnd.owner, + xformOwner = owner && owner.bindingTransformOwner; + + if (xform) { + if (typeof xform == 'string') { + if (xformOwner && xformOwner[xform]) { + xform = xformOwner[xform]; + } else if (owner && owner[xform]) { + xform = owner[xform]; + } else { + xform = utils.getPath.call(global, xform); + } + } + + /*jshint -W093 */ + return (bnd.transform = (typeof xform == 'function' ? xform : null)); + /*jshint +W093 */ + } + })(this); + }, + + /** + * @private + */ + constructor: function (props) { + bindings.push(this); + + if (props) utils.mixin(this, props); + + if (!this.euid) this.euid = utils.uid('b'); + + this.sync(); + } +}); + +/** +* The details for an {@link module:enyo/Binding~Binding#transform} [function]{@glossary Function}, +* including the available parameters and how they can be used. +* +* @callback module:enyo/Binding~Binding~Transform +* @param {*} value - The value being synchronized. +* @param {Number} direction - The direction of synchronization; will be either +* 1 (source value has changed and will be written to target) or 2 (target +* value has changed and will be written to source). +* @param {Object} binding - A reference to the associated [binding]{@link module:enyo/Binding~Binding}. In cases +* where the binding should be interrupted and not propagate the synchronization at all, call +* the [stop()]{@link module:enyo/Binding~Binding#stop} method on the passed-in binding reference. +*/ + +/** +* {@link module:enyo/Binding~Binding} is a mechanism used to keep properties synchronized. A +* binding may be used to link two properties on different +* [objects]{@glossary Object}, or even two properties on the same object. +* Once a binding has been established, it will wait for change notifications; +* when a notification arrives, the binding will synchronize the value between +* the two ends. Note that bindings may be either +* [one-way]{@link module:enyo/Binding~Binding#oneWay} (the default) or +* [two-way]{@link module:enyo/Binding~Binding#oneWay}. +* +* Usually, you will not need to create Binding objects arbitrarily, but will +* instead rely on the public [BindingSupport API]{@link module:enyo/BindingSupport~BindingSupport}, +* which is applied to [Object]{@link module:enyo/CoreObject~Object} and so is available on +* all of its [subkinds]{@glossary subkind}. +* +* @class Binding +* @public +*/ +exports = module.exports = kind( + /** @lends module:enyo/Binding~Binding.prototype */ { + + name: 'enyo.Binding', + + /** + * @private + */ + kind: PassiveBinding, + + /** + * If a [binding]{@link module:enyo/Binding~Binding} is one-way, this flag should be `true` (the default). + * If this flag is set to `false`, the binding will be two-way. + * + * @type {Boolean} + * @default true + * @public + */ + oneWay: true, + + /** + * If the [binding]{@link module:enyo/Binding~Binding} was able to resolve both ends (i.e., its + * [source]{@link module:enyo/Binding~Binding#source} and [target]{@link module:enyo/Binding~Binding#target} + * [objects]{@glossary Object}), this value will be `true`. Setting this manually will + * have undesirable effects. + * + * @type {Boolean} + * @default false + * @public + */ + connected: false, + + /** + * By default, a [binding]{@link module:enyo/Binding~Binding} will attempt to connect to both ends + * ([source]{@link module:enyo/Binding~Binding#source} and [target]{@link module:enyo/Binding~Binding#target}). If this + * process should be deferred, set this flag to `false`. + * + * @type {Boolean} + * @default true + * @public + */ + autoConnect: true, + + /** + * By default, a [binding]{@link module:enyo/Binding~Binding} will attempt to synchronize its values from + * its [source]{@link module:enyo/Binding~Binding#source} to its [target]{@link module:enyo/Binding~Binding#target}. If + * this process should be deferred, set this flag to `false`. + * + * @type {Boolean} + * @default true + * @public + */ + autoSync: true, + + /** + * The `dirty` property represents the changed value state of both the property designated by + * the [from]{@link module:enyo/Binding~Binding#from} path and the property designated by the + * [to]{@link module:enyo/Binding~Binding#to} path. + * + * @type {Number} + * @default module:enyo/Binding#DIRTY_FROM + * @public + */ + dirty: DIRTY_FROM, + + /** + * Indicates whether the [binding]{@link module:enyo/Binding~Binding} is currently connected. + * + * @returns {Boolean} `true` if connected; otherwise, `false`. + * @public + */ + isConnected: function () { + var from = this._from, + to = this.oneWay ? (this._toTarget || this._to) : this._to, + source = this._source, + target = this._target, + toChain, + fromChain; + + if (from && to && source && target) { + if (!this.oneWay || this._toTarget) toChain = target.getChains()[to]; + fromChain = source.getChains()[from]; + + return this.connected + && (fromChain ? fromChain.isConnected() : true) + && (toChain ? toChain.isConnected() : true); + } + + return false; + }, + + /** + * Resets all properties to their original state. + * + * @returns {this} The callee for chaining. + * @public + */ + reset: function () { + this.disconnect(); + return PassiveBinding.prototype.reset.apply(this, arguments); + }, + + /** + * Rebuilds the entire [binding]{@link module:enyo/Binding~Binding}. Will synchronize if it is able to + * connect and the [autoSync]{@link module:enyo/Binding~Binding#autoSync} flag is `true`. + * + * @returns {this} The callee for chaining. + * @public + */ + rebuild: function () { + return this.reset().connect(); + }, + + /** + * Connects the ends (i.e., the [source]{@link module:enyo/Binding~Binding#source} and + * [target]{@link module:enyo/Binding~Binding#target}) of the [binding]{@link module:enyo/Binding~Binding}. While you + * typically won't need to call this method, it is safe to call even when the ends are + * already established. Note that if one or both of the ends does become connected and the + * [autoSync]{@link module:enyo/Binding~Binding#autoSync} flag is `true`, the ends will automatically be + * synchronized. + * + * @returns {this} The callee for chaining. + * @public + */ + connect: function () { + if (!this.isConnected()) { + if (this.isReady()) { + this._source.observe(this._from, this._sourceChanged, this, {priority: true}); + + // for two-way bindings we register to observe changes + // from the target + if (!this.oneWay) this._target.observe(this._to, this._targetChanged, this); + else if (this._toTarget) { + this._target.observe(this._toTarget, this._toTargetChanged, this, {priority: true}); + } + + // we flag it as having been connected + this.connected = true; + if (this.isConnected() && this.autoSync) this.sync(true); + } + } + + return this; + }, + + /** + * Disconnects from the ends (i.e., the [source]{@link module:enyo/Binding~Binding#source} and + * [target]{@link module:enyo/Binding~Binding#target}) if a connection exists at either end. This method + * will most likely not need to be called directly. + * + * @returns {this} The callee for chaining. + * @public + */ + disconnect: function () { + if (this.isConnected()) { + this._source.unobserve(this._from, this._sourceChanged, this); + + // for two-way bindings we unregister the observer from + // the target as well + if (!this.oneWay) this._target.unobserve(this._to, this._targetChanged, this); + else if (this._toTarget) { + this._target.unobserve(this._toTarget, this._toTargetChanged, this); + } + + this.connected = false; + } + + return this; + }, + + /** + * Synchronizes values from the [source]{@link module:enyo/Binding~Binding#source} to the + * [target]{@link module:enyo/Binding~Binding#target}. This usually will not need to be called manually. + * [Two-way bindings]{@link module:enyo/Binding~Binding#oneWay} will automatically synchronize from the + * target end once they are connected. + * + * @returns {this} The callee for chaining. + * @public + */ + sync: function (force) { + var source = this._source, + target = this._target, + from = this._from, + to = this._to, + xform = this.getTransform(), + val; + + if (this.isReady() && this.isConnected()) { + + switch (this.dirty || (force && DIRTY_FROM)) { + case DIRTY_TO: + val = target.get(to); + if (xform) val = xform.call(this.owner || this, val, DIRTY_TO, this); + if (!this._stop) source.set(from, val, {create: false}); + break; + case DIRTY_FROM: + + // @TODO: This should never need to happen but is here just in case + // it is ever arbitrarily called not having been dirty? + // default: + val = source.get(from); + if (xform) val = xform.call(this.owner || this, val, DIRTY_FROM, this); + if (!this._stop) target.set(to, val, {create: false}); + break; + } + this.dirty = null; + this._stop = null; + } + + return this; + }, + + /** + * Releases all of the [binding's]{@link module:enyo/Binding~Binding} parts and unregisters its + * [observers]{@link module:enyo/ObserverSupport~ObserverSupport}. Typically, this method will not need to be called + * directly unless the binding was created without an [owner]{@link module:enyo/Binding~Binding#owner}. + * + * @returns {this} The callee for chaining. + * @public + */ + destroy: function () { + this.disconnect(); + + return PassiveBinding.prototype.destroy.apply(this, arguments); + }, + + /** + * @private + */ + constructor: function (props) { + bindings.push(this); + + if (props) utils.mixin(this, props); + + if (!this.euid) this.euid = utils.uid('b'); + if (this.autoConnect) this.connect(); + }, + + /** + * @private + */ + _sourceChanged: function (was, is, path) { + // @TODO: Should it...would it benefit from using these passed in values? + this.dirty = this.dirty == DIRTY_TO ? null : DIRTY_FROM; + return this.dirty == DIRTY_FROM && this.sync(); + }, + + /** + * @private + */ + _targetChanged: function (was, is, path) { + // @TODO: Same question as above, it seems useful but would it affect computed + // properties or stale values? + this.dirty = this.dirty == DIRTY_FROM ? null : DIRTY_TO; + return this.dirty == DIRTY_TO && this.sync(); + }, + + /** + * @private + */ + _toTargetChanged: function (was, is, path) { + this.dirty = DIRTY_FROM; + this.reset().connect(); + } +}); + +/** +* Retrieves a [binding]{@link module:enyo/Binding~Binding} by its global id. +* +* @param {String} euid - The [Enyo global id]{@glossary EUID} by which to retrieve a +* [binding]{@link module:enyo/Binding~Binding}. +* @returns {module:enyo/Binding~Binding|undefined} A reference to the binding if the id +* is found; otherwise, it will return [undefined]{@glossary undefined}. +* +* @static +* @public +*/ +exports.find = function (euid) { + return bindings.find(function (ln) { + return ln.euid == euid; + }); +}; + +/** +* All {@link module:enyo/Binding~Binding} instances are stored in this list and may be retrieved via the +* {@link module:enyo/Binding.find} method using an {@link module:enyo/Binding~Binding#id} identifier. +* +* @type {module:enyo/Binding~Binding[]} +* @default [] +* @public +*/ +exports.bindings = bindings; + +/** +* Possible value of the [dirty]{@link module:enyo/Binding~Binding#dirty} property, indicating that the value +* of the [binding source]{@link module:enyo/Binding~Binding#source} has changed. +* +* @static +* @public +*/ +exports.DIRTY_FROM = DIRTY_FROM; + +/** +* Possible value of the [dirty]{@link module:enyo/Binding~Binding#dirty} property, indicating that the value +* of the [binding target]{@link module:enyo/Binding~Binding#target} has changed. +* +* @static +* @public +*/ +exports.DIRTY_TO = DIRTY_TO; + +/** +* The default [kind]{@glossary kind} that provides [binding]{@link module:enyo/Binding~Binding} +* functionality. +* +* @static +* @public +*/ +exports.defaultBindingKind = exports; + +exports.PassiveBinding = PassiveBinding; + +},{'./kind':'enyo/kind','./utils':'enyo/utils'}],'enyo/Layout':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Layout~Layout} kind. +* @module enyo/Layout +*/ + +var + kind = require('./kind'); + +/** +* {@link module:enyo/Layout~Layout} is the base [kind]{@glossary kind} for layout +* kinds. Layout kinds are used by {@link module:enyo/UiComponent~UiComponent}-based +* [controls]{@link module:enyo/Control~Control} to allow for arranging of child controls by +* setting the [layoutKind]{@link module:enyo/UiComponent~UiComponent#layoutKind} property. +* +* Derived kinds will usually provide their own +* [layoutClass]{@link module:enyo/Layout~Layout#layoutClass} property to affect the CSS +* rules used, and may also implement the [flow()]{@link module:enyo/Layout~Layout#flow} +* and [reflow()]{@link module:enyo/Layout~Layout#reflow} methods. `flow()` is called +* during control rendering, while `reflow()` is called when the associated +* control is resized. +* +* @class Layout +* @public +*/ +module.exports = kind( + /** @lends module:enyo/Layout~Layout.prototype */ { + + name: 'enyo.Layout', + + /** + * @private + */ + kind: null, + + /** + * CSS class that's added to the [control]{@link module:enyo/Control~Control} using this + * [layout]{@link module:enyo/Layout~Layout} [kind]{@glossary kind}. + * + * @type {String} + * @default '' + * @public + */ + layoutClass: '', + + /** + * @private + */ + constructor: function (container) { + this.container = container; + if (container) { + container.addClass(this.layoutClass); + } + }, + + /** + * @private + */ + destroy: function () { + if (this.container) { + this.container.removeClass(this.layoutClass); + } + }, + + /** + * Called during static property layout (i.e., during rendering). + * + * @public + */ + flow: function () { + }, + + /** + * Called during dynamic measuring layout (i.e., during a resize). + * + * May short-circuit and return `true` if the layout needs to be + * redone when the associated Control is next shown. This is useful + * for cases where the Control itself has `showing` set to `true` + * but an ancestor is hidden, and the layout is therefore unable to + * get accurate measurements of the Control or its children. + * + * @public + */ + reflow: function () { + } +}); + +},{'./kind':'enyo/kind'}],'enyo/LinkedListNode':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/LinkedListNode~LinkedListNode} kind. +* @module enyo/LinkedListNode +*/ + +var + kind = require('./kind'), + utils = require('./utils'); + +/** +* An abstract linked-list node. +* +* @class LinkedListNode +* @private +*/ +module.exports = kind( + /** @lends module:enyo/LinkedListNode~LinkedListNode.prototype */ { + + /** + * @private + */ + kind: null, + + /** + * @private + */ + + + /** + * @private + */ + prev: null, + + /** + * @private + */ + next: null, + + /** + * @private + */ + copy: function () { + var cpy = new this.ctor(); + cpy.prev = this.prev; + cpy.next = this.next; + return cpy; + }, + + /** + * @private + */ + constructor: function (props) { + props && utils.mixin(this, props); + }, + + /** + * @private + */ + destroy: function () { + // clear reference to previous node + this.prev = null; + + // if we have a reference to our next node + // we continue down the chain + this.next && this.next.destroy(); + + // clear our reference to the next node + this.next = null; + } +}); + +},{'./kind':'enyo/kind','./utils':'enyo/utils'}],'enyo/AnimationSupport/Tween':[function (module,exports,global,require,request){ +require('enyo'); + +var + frame = require('./Frame'), + matrixUtil = require('./Matrix'), + Vector = require('./Vector'); + +var oldState, newState, node, matrix, cState = []; +/** +* Tween is a module responsible for creating intermediate frames for an animation. +* The responsibilities of this module is to; +* - Interpolating current state of character. +* - Update DOM based on current state, using matrix for tranform and styles for others. +* +* @module enyo/AnimationSupport/Tween +*/ +module.exports = { + /** + * Tweens public API which notifies to change current state of + * a character. This method is normally trigger by the Animation Core to + * update the animating characters state based on the current timestamp. + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * @parameter chrac- Animating character + * ts- DOMHighResTimeStamp + * + * @public + */ + update: function (charc, ts) { + var t, + dur = charc._duration, + since = ts - charc._startTime; + + if (since < 0) return; + if (since <= dur) { + t = since / dur; + this.step(charc, t); + } else { + this.step(charc, 1); + } + }, + + /** + * @private + */ + step: function(charc, t) { + var k, c, d, pts; + + node = charc.node; + newState = charc._endAnim; + d = charc._duration; + props = charc.getAnimation(), + oldState = charc._startAnim; + charc.currentState = charc.currentState || {}; + + for (k in props) { + cState = frame.copy(charc.currentState[k] || []); + if (charc.ease && (typeof charc.ease !== 'function') && (k !== 'rotate')) { + pts = this.calculateEase(charc.ease, frame.copy(oldState[k]), frame.copy(newState[k])); + cState = this.getBezier(t, pts, cState); + } else { + c = k == 'rotate' ? this.slerp : this.lerp; + cState = t ? c(oldState[k], newState[k], ((typeof charc.ease === 'function') ? charc.ease : this.ease)(t, d), cState) : newState[k]; + } + + if (!frame.isTransform(k)) { + frame.setProperty(node, k, cState); + } + charc.currentState[k] = cState; + } + + if(charc._transform) { + matrix = frame.recomposeMatrix( + charc.currentState.translate, + charc.currentState.rotate, + charc.currentState.scale, + charc.currentState.skew, + charc.currentState.perspective + ); + frame.accelerate(node, matrix); + } + }, + + /** + * @private + */ + ease: function (t) { + return t; + }, + + calculateEase: function(easeObj, startPoint, endPoint) { + var order = (easeObj && Object.keys(easeObj).length) ? (Object.keys(easeObj).length + 1) : 0; + var controlPoints = [startPoint], + bValues = [], + m1 = [], + m2 = [], + m3 = [], + m4 = [], + l = 0; + + var t, a; + for (var key in easeObj) { + t = parseFloat(key) / 100; + a = parseFloat(easeObj[key]) / 100; + bValues = this.getBezierValues(t, order); + bValues.shift(); + m1.push(a - bValues.pop()); + m2.push(bValues); + } + + m3 = matrixUtil.inverseN(m2, bValues.length); + + m4 = matrixUtil.multiplyN(m3, m1); + l = m4.length; + for (var i = 0; i < l; i++) { + controlPoints.push([m4[i], m4[i], m4[i]]); + } + + controlPoints.push(endPoint); + + // console.log("m1", m1); + // console.log("m2", m2); + // console.log("m3", m3); + // console.log("m4", m4); + console.log("CP", controlPoints); + return controlPoints; + }, + + complete: function (charc) { + charc.animating = false; + charc._startAnim = undefined; + charc._endAnim = undefined; + }, + + lerp: function (vA, vB, t, vR) { + if (!vR) vR = []; + var i, l = vA.length; + + for (i = 0; i < l; i++) { + vR[i] = (1 - t) * vA[i] + t * vB[i]; + } + return vR; + }, + + slerp: function (qA, qB, t, qR) { + if (!qR) qR = []; + var a, b, w, theta, dot = Vector.dot(qA, qB); + + dot = Math.min(Math.max(dot, -1.0), 1.0); + if (dot == 1.0) { + qR = frame.copy(qA); + return qR; + } + + theta = Math.acos(dot); + w = Math.sin(t * theta) * 1 / Math.sqrt(1 - dot * dot); + for (var i = 0; i < 4; i++) { + a = qA[i] * (Math.cos(t * theta) - dot * w); + b = qB[i] * w; + qR[i] = a + b; + } + return qR; + }, + + /** + * @public + * @params t: time, points: knot and control points, vR: resulting point + */ + getBezier: function (t, points, vR) { + if (!vR) vR = []; + + var i, j, + c = points.length, + l = points[0].length, + lastIndex = (c - 1), + endPoint = points[lastIndex], + values = this.getBezierValues(t, lastIndex); + + for (i = 0; i < l; i++) { + vR[i] = 0; + for (j = 0; j < c; j++) { + if ((j > 0) && (j < (c - 1))) { + vR[i] = vR[i] + (points[j][i] * endPoint[i] * values[j]); + } else { + vR[i] = vR[i] + (points[j][i] * values[j]); + } + } + } + + console.log("vR", vR); + return vR; + }, + + /** + * @private + * @params n: order, k: current position + */ + getCoeff: function (n, k) { + n = parseInt(n); + k = parseInt(k); + // Credits + // https://math.stackexchange.com/questions/202554/how-do-i-compute-binomial-coefficients-efficiently#answer-927064 + if (isNaN(n) || isNaN(k)) + return void 0; + if ((n < 0) || (k < 0)) + return void 0; + if (k > n) + return void 0; + if (k === 0) + return 1; + if (k === n) + return 1; + if (k > n / 2) + return this.getCoeff(n, n - k); + + return n * this.getCoeff(n - 1, k - 1) / k; + }, + + /** + * @public + * @params t: time, n: order + */ + getBezierValues: function (t, n) { + t = parseFloat(t), + n = parseInt(n); + + if (isNaN(t) || isNaN(n)) + return void 0; + if ((t < 0) || (n < 0)) + return void 0; + if (t > 1) + return void 0; + + var c, + values = [], + x = (1 - t), + y = t; + + // + // Binomial theorem to expand (x+y)^n + // + for (var k = 0; k <= n; k++) { + c = this.getCoeff(n, k) * Math.pow(x, (n - k)) * Math.pow(y, k); + values.push(c); + } + + return values; + } +}; +},{'./Frame':'enyo/AnimationSupport/Frame','./Matrix':'enyo/AnimationSupport/Matrix','./Vector':'enyo/AnimationSupport/Vector'}],'enyo/BindingSupport':[function (module,exports,global,require,request){ +/** +* Exports the {@link module:enyo/BindingSupport~BindingSupport} mixin +* @module enyo/BindingSupport +*/ + +require('enyo'); + +var + kind = require('./kind'), + utils = require('./utils'); + +var + Binding = require('./Binding'); + +kind.concatenated.push('bindings'); + +/** +* An internally-used {@glossary mixin} that is added to {@link module:enyo/CoreObject~Object} +* and its [subkinds]{@glossary subkind}. It includes public and protected API +* methods for working with [bindings]{@link module:enyo/Binding~Binding}. +* +* @mixin +* @protected +*/ +var BindingSupport = { + + /** + * @private + */ + name: 'BindingSupport', + + /** + * @private + */ + _bindingSupportInitialized: false, + + /** + * Imperatively creates a [binding]{@link module:enyo/Binding~Binding}. Merges a variable + * number of [hashes]{@glossary Object} and instantiates a binding that + * will have its [owner]{@link module:enyo/Binding~Binding#owner} property set to the callee + * (the current {@link module:enyo/CoreObject~Object}). Bindings created in this way will be + * [destroyed]{@link module:enyo/Binding~Binding#destroy} when their `owner` is + * [destroyed]{@link module:enyo/CoreObject~Object#destroy}. + * + * @param {...Object} props A variable number of [hashes]{@glossary Object} that will + * be merged into the properties applied to the {@link module:enyo/Binding~Binding} instance. + * @returns {this} The callee for chaining. + * @public + */ + binding: function () { + var args = utils.toArray(arguments) + , props = utils.mixin(args) + , bindings = this.bindings || (this.bindings = []) + , passiveBindings = this.passiveBindings || (this.passiveBindings = []) + , PBCtor = Binding.PassiveBinding + , Ctor, bnd; + + props.owner = props.owner || this; + Ctor = props.kind = props.kind || this.defaultBindingKind || Binding.defaultBindingKind; + + if (this._bindingSupportInitialized) { + utils.isString(Ctor) && (Ctor = props.kind = kind.constructorForKind(Ctor)); + bnd = new Ctor(props); + bindings.push(bnd); + if (Ctor === PBCtor) { + passiveBindings.push(bnd); + } + return bnd; + } else bindings.push(props); + + return this; + }, + + /** + * Removes and [destroys]{@link module:enyo/Binding~Binding#destroy} all of, or a subset of, + * the [bindings]{@link module:enyo/Binding~Binding} belonging to the callee. + * + * @param {module:enyo/Binding~Binding[]} [subset] - The optional [array]{@glossary Array} of + * [bindings]{@link module:enyo/Binding~Binding} to remove. + * @returns {this} The callee for chaining. + * @public + */ + clearBindings: function (subset) { + var bindings = subset || (this.bindings && this.bindings.slice()); + bindings.forEach(function (bnd) { + bnd.destroy(); + }); + + return this; + }, + + syncBindings: function (opts) { + var all = opts && opts.all, + force = opts && opts.force, + bindings = all ? this.bindings : this.passiveBindings; + + bindings.forEach(function (b) { + b.sync(force); + }); + }, + + /** + * Removes a single {@link module:enyo/Binding~Binding} from the callee. (This does not + * [destroy]{@link module:enyo/Binding~Binding#destroy} the binding.) Also removes the + * [owner]{@link module:enyo/Binding~Binding#owner} reference if it is the callee. + * + * It should be noted that when a binding is destroyed, it is automatically + * removed from its owner. + * + * @param {module:enyo/Binding~Binding} binding - The {@link module:enyo/Binding~Binding} instance to remove. + * @returns {this} The callee for chaining. + * @public + */ + removeBinding: function (binding) { + utils.remove(binding, this.bindings); + if (binding.ctor === Binding.PassiveBinding) { + utils.remove(binding, this.passiveBindings); + } + + if (binding.owner === this) binding.owner = null; + + return this; + }, + + /** + * @private + */ + constructed: kind.inherit(function (sup) { + return function () { + var bindings = this.bindings; + this._bindingSupportInitialized = true; + if (bindings) { + this.bindings = []; + this.passiveBindings = []; + bindings.forEach(function (def) { + this.binding(def); + }, this); + } + sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + destroy: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + this.bindings && this.bindings.length && this.clearBindings(); + this.bindings = null; + this.passiveBindings = null; + }; + }) +}; + +module.exports = BindingSupport; + +/** + Hijack the original so we can add additional default behavior. +*/ +var sup = kind.concatHandler + , flags = {ignore: true}; + +/** +* @private +*/ +kind.concatHandler = function (ctor, props, instance) { + var proto = ctor.prototype || ctor + , kind = props && (props.defaultBindingKind || Binding.defaultBindingKind) + , defaults = props && props.bindingDefaults; + + sup.call(this, ctor, props, instance); + if (props.bindings) { + props.bindings.forEach(function (bnd) { + defaults && utils.mixin(bnd, defaults, flags); + bnd.kind || (bnd.kind = kind); + }); + + proto.bindings = proto.bindings? proto.bindings.concat(props.bindings): props.bindings; + delete props.bindings; + } +}; + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./Binding':'enyo/Binding'}],'enyo/RepeaterChildSupport':[function (module,exports,global,require,request){ +/** +* Exports the {@link module:enyo/RepeaterChildSupport~RepeaterChildSupport} mixin +* @module enyo/RepeaterChildSupport +*/ + +require('enyo'); + +var + kind = require('./kind'), + utils = require('./utils'); + +var + Binding = require('./Binding'); + +/** +* The {@link module:enyo/RepeaterChildSupport~RepeaterChildSupport} [mixin]{@glossary mixin} contains methods and +* properties that are automatically applied to all children of {@link module:enyo/DataRepeater~DataRepeater} +* to assist in selection support. (See {@link module:enyo/DataRepeater~DataRepeater} for details on how to +* use selection support.) This mixin also [adds]{@link module:enyo/Repeater~Repeater#decorateEvent} the +* `model`, `child` ([control]{@link module:enyo/Control~Control} instance), and `index` properties to +* all [events]{@glossary event} emitted from the repeater's children. +* +* @mixin +* @public +*/ +var RepeaterChildSupport = { + + /* + * @private + */ + name: 'RepeaterChildSupport', + + /** + * Indicates whether the current child is selected in the [repeater]{@link module:enyo/DataRepeater~DataRepeater}. + * + * @type {Boolean} + * @default false + * @public + */ + selected: false, + + /** + * Setting cachePoint: true ensures that events from the repeater child's subtree will + * always bubble up through the child, allowing the events to be decorated with repeater- + * related metadata and references. + * + * @type {Boolean} + * @default true + * @private + */ + cachePoint: true, + + /* + * @method + * @private + */ + selectedChanged: kind.inherit(function (sup) { + return function () { + if (this.repeater.selection) { + this.addRemoveClass(this.selectedClass || 'selected', this.selected); + // for efficiency purposes, we now directly call this method as opposed to + // forcing a synchronous event dispatch + var idx = this.repeater.collection.indexOf(this.model); + if (this.selected && !this.repeater.isSelected(this.model)) { + this.repeater.select(idx); + } else if (!this.selected && this.repeater.isSelected(this.model)) { + this.repeater.deselect(idx); + } + } + sup.apply(this, arguments); + }; + }), + + /* + * @method + * @private + */ + modelChanged: kind.inherit(function (sup) { + return function () { + this.syncBindings(); + sup.apply(this, arguments); + }; + }), + + /* + * @method + * @private + */ + decorateEvent: kind.inherit(function (sup) { + return function (sender, event) { + var c = this.repeater.collection; + if (c) { + event.model = this.model; + event.child = this; + event.index = this.repeater.collection.indexOf(this.model); + } + sup.apply(this, arguments); + }; + }), + + /* + * @private + */ + _selectionHandler: function () { + if (this.repeater.selection && !this.get('disabled')) { + if (this.repeater.selectionType != 'group' || !this.selected) { + this.set('selected', !this.selected); + } + } + }, + /** + * Deliberately used to supersede the default method and set + * [owner]{@link module:enyo/Component~Component#owner} to this [control]{@link module:enyo/Control~Control} so that there + * are no name collisions in the instance [owner]{@link module:enyo/Component~Component#owner}, and also so + * that [bindings]{@link module:enyo/Binding~Binding} will correctly map to names. + * + * @method + * @private + */ + createClientComponents: kind.inherit(function () { + return function (components) { + this.createComponents(components, {owner: this}); + }; + }), + /** + * Used so that we don't stomp on any built-in handlers for the `ontap` + * {@glossary event}. + * + * @method + * @private + */ + dispatchEvent: kind.inherit(function (sup) { + return function (name, event, sender) { + var owner; + + // if the event is coming from a child of the repeater-child (this...) and has a + // delegate assigned to it there is a distinct possibility it is supposed to be + // targeting the instanceOwner of repeater-child not the repeater-child itself + // so we have to check this case and treat it as expected - if there is a handler + // and it returns true then we must skip the normal flow + if (event.originator !== this && event.delegate && event.delegate.owner === this) { + if (typeof this[name] != 'function') { + // ok we don't have the handler here let's see if our owner does + owner = this.getInstanceOwner(); + if (owner && owner !== this) { + if (typeof owner[name] == 'function') { + // alright it appears that we're supposed to forward this to the + // next owner instead + return owner.dispatch(name, event, sender); + } + } + } + } + + if (!event._fromRepeaterChild) { + if (!!~utils.indexOf(name, this.repeater.selectionEvents)) { + this._selectionHandler(); + event._fromRepeaterChild = true; + } + } + return sup.apply(this, arguments); + }; + }), + + /* + * @method + * @private + */ + constructed: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + var r = this.repeater, + s = r.selectionProperty; + // this property will only be set if the instance of the repeater needs + // to track the selected state from the view and model and keep them in sync + if (s) { + var bnd = this.binding({ + from: 'model.' + s, + to: 'selected', + oneWay: false/*, + kind: enyo.BooleanBinding*/ + }); + this._selectionBindingId = bnd.euid; + } + }; + }), + + /* + * @method + * @private + */ + destroy: kind.inherit(function (sup) { + return function () { + if (this._selectionBindingId) { + var b$ = Binding.find(this._selectionBindingId); + if (b$) { + b$.destroy(); + } + } + sup.apply(this, arguments); + }; + }), + + /* + * @private + */ + _selectionBindingId: null +}; + +module.exports = RepeaterChildSupport; + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./Binding':'enyo/Binding'}],'enyo/LinkedList':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/LinkedList~LinkedList} kind. +* @module enyo/LinkedList +*/ + +var + kind = require('./kind'); + +var + LinkedListNode = require('./LinkedListNode'); + +/** +* An abstract linked-list. +* +* @class LinkedList +* @private +*/ +module.exports = kind( + /** @lends module:enyo/LinkedList~LinkedList.prototype */ { + + /** + * @private + */ + kind: null, + + /** + * @private + */ + + + /** + * @private + */ + nodeKind: LinkedListNode, + + /** + * @private + */ + head: null, + + /** + * @private + */ + tail: null, + + /** + * @private + */ + length: 0, + + /** + * @private + */ + clear: function () { + if (this.head) { + // this will trigger a chain event down the list + this.head.destroy(); + } + this.head = null; + this.tail = null; + this.length = 0; + }, + + /** + * @private + */ + slice: function (fromNode, toNode) { + var node = fromNode || this.head + , list = new this.ctor() + , cpy; + + // ensure we have a final node or our tail + toNode = toNode || this.tail; + + if (node && node !== toNode) { + do { + cpy = node.copy(); + list.appendNode(cpy); + } while ((node = node.next) && node !== toNode); + } + + return list; + }, + + /** + * @private + */ + destroy: function () { + this.clear(); + this.destroyed = true; + }, + + /** + * @private + */ + createNode: function (props) { + return new this.nodeKind(props); + }, + + /** + * @private + */ + deleteNode: function (node) { + this.removeNode(node); + + // can't chain destruct because we removed its chain references + node.destroy(); + return this; + }, + + /** + * @private + */ + removeNode: function (node) { + var prev = node.prev + , next = node.next; + + prev && (prev.next = next); + next && (next.prev = prev); + this.length--; + node.next = node.prev = null; + return this; + }, + + /** + * @private + */ + appendNode: function (node, targetNode) { + targetNode = targetNode || this.tail; + + if (targetNode) { + if (targetNode.next) { + node.next = targetNode.next; + } + + targetNode.next = node; + node.prev = targetNode; + + if (targetNode === this.tail) { + this.tail = node; + } + + this.length++; + } else { + + this.head = this.tail = node; + node.prev = node.next = null; + this.length = 1; + } + return this; + }, + + /** + * @private + */ + find: function (fn, ctx, targetNode) { + var node = targetNode || this.head; + if (node) { + do { + if (fn.call(ctx || this, node, this)) { + return node; + } + } while ((node = node.next)); + } + // if no node qualified it returns false + return false; + }, + + /** + * @private + */ + forward: function (fn, ctx, targetNode) { + var node = targetNode || this.head; + if (node) { + do { + if (fn.call(ctx || this, node, this)) { + break; + } + } while ((node = node.next)); + } + // returns the last node (if any) that was processed in the chain + return node; + }, + + /** + * @private + */ + backward: function (fn, ctx, targetNode) { + var node = targetNode || this.tail; + if (node) { + do { + if (fn.call(ctx || this, node, this)) { + break; + } + } while ((node = node.prev)); + } + // returns the last node (if any) that was processed in the chain + return node; + }, + + /** + * @private + */ + constructor: function () { + this.nodeType = kind.constructorForKind(this.nodeType); + } +}); + +},{'./kind':'enyo/kind','./LinkedListNode':'enyo/LinkedListNode'}],'enyo/ObserverChainNode':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/ObserverChainNode~ObserverChainNode} kind. +* @module enyo/ObserverChainNode +*/ + +var + kind = require('./kind'); + +var + LinkedListNode = require('./LinkedListNode'); + +function get (base, prop) { + return base && /*isObject(base)*/ (typeof base == 'object')? ( + base.get? base.get(prop): base[prop] + ): undefined; +} + +/** +* An internally used {@glossary kind}. +* +* @class ObserverChainNode +* @extends module:enyo/LinkedListNode~LinkedListNode +* @private +*/ +module.exports = kind( + /** @lends module:enyo/ObserverChainNode~ObserverChainNode.prototype */ { + + /** + * @private + */ + kind: LinkedListNode, + + /** + * @private + */ + + + /** + * @method + * @private + */ + constructor: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + this.connect(); + }; + }), + + /** + * @method + * @private + */ + destroy: kind.inherit(function (sup) { + return function () { + this.disconnect(); + sup.apply(this, arguments); + this.observer = null; + this.list = null; + this.object = null; + }; + }), + + /** + * @private + */ + connect: function () { + var obj = this.object + , obs = this._changed + , prop = this.property; + if (obj) { + if (obj.observe) obj.observe(prop, obs, this, {noChain: true, priority: true}); + this.connected = true; + this.list.connected++; + } + }, + + /** + * @private + */ + disconnect: function () { + var obj = this.object + , obs = this._changed + , prop = this.property + , was = this.connected; + obj && obj.unobserve && obj.unobserve(prop, obs, this); + this.connected = null; + if (was) this.list.connected--; + }, + + /** + * @private + */ + setObject: function (object) { + var cur = this.object + , prop = this.property + , was, is; + + if (cur !== object) { + this.disconnect(); + this.object = object; + this.connect(); + + if (this.list.tail === this) { + was = get(cur, prop); + is = get(object, prop); + // @TODO: It would be better to somehow cache values + // such that it could intelligently derive the difference + // without needing to continuously look it up with get + was !== is && this.list.observed(this, was, is); + } + } + }, + + /** + * @private + */ + _changed: function (was, is) { + this.list.observed(this, was, is); + } +}); + +},{'./kind':'enyo/kind','./LinkedListNode':'enyo/LinkedListNode'}],'enyo/ObserverChain':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/ObserverChain~ObserverChain} kind. +* @module enyo/ObserverChain +*/ + +var + kind = require('./kind'); + +var + LinkedList = require('./LinkedList'), + ObserverChainNode = require('./ObserverChainNode'); + +function get (base, prop) { + return base && /*isObject(base)*/ (typeof base == 'object')? ( + base.get? base.get(prop): base[prop] + ): undefined; +} + +/** +* An internally used {@glossary kind}. +* +* @class ObserverChain +* @extends module:enyo/LinkedList~LinkedList +* @private +*/ +module.exports = kind( + /** @lends module:enyo/ObserverChain~ObserverChain.prototype */ { + + /** + * @private + */ + kind: LinkedList, + + /** + * @private + */ + nodeKind: ObserverChainNode, + + /** + * @private + */ + + + /** + * @private + */ + connected: 0, + + /** + * @method + * @private + */ + constructor: function (path, object) { + this.object = object; + this.path = path; + this.parts = path.split('.'); + this.createChain(); + }, + + /** + * @method + * @private + */ + destroy: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + this.object = null; + this.parts = null; + this.path = null; + }; + }), + + /** + * @private + */ + rebuild: function (target) { + if (!this.rebuilding) { + this.rebuilding = true; + this.forward(function (node) { + if (node !== this.head) { + var src = node.prev.object + , prop = node.prev.property; + node.setObject(get(src, prop)); + } + }, this, target); + this.rebuilding = false; + } + }, + + /** + * @private + */ + isConnected: function () { + return !! (this.connected === this.length && this.length); + }, + + /** + * @private + */ + buildPath: function (target) { + var str = ''; + + this.backward(function (node) { + str = node.property + (str? ('.' + str): str); + }, this, target); + + return str; + }, + + /** + * @private + */ + createChain: function () { + var parts = this.parts + , next = this.object + , $ = false + , node, prop; + + for (var i=0; (prop=parts[i]); ++i) { + + // forEach(parts, function (prop, idx) { + // we create a special case for the $ hash property + if (prop == '$') { + $ = true; + } else { + // in cases where the chain has the $ property we arbitrarily + // force it onto our current nodes property and let the special handling + // in ObserverChainNode and ObserverSupport handle the rest + $ && (prop = '$.' + prop); + node = this.createNode({property: prop, object: next, list: this}); + this.appendNode(node); + next = get(next, prop); + $ = false; + } + // }, this); + } + }, + + /** + * @private + */ + observed: function (node, was, is) { + this.object.stopNotifications(); + // @NOTE: About the following two cases, they are mutually exclusive and this seems perfect + // that we don't see double notifications + // @TODO: Only notify if it was the full property path? This is far more efficient after + // testing but not as flexible... + node === this.tail /*&& was !== is*/ && this.object.notify(this.buildPath(node), was, is); + // @TODO: It seems the same case across the board that the rebuild only needs to take place + // from the beginning to the second-to-last elem + node !== this.tail && was !== is && this.rebuild(node); + this.object.startNotifications(); + } +}); + +},{'./kind':'enyo/kind','./LinkedList':'enyo/LinkedList','./ObserverChainNode':'enyo/ObserverChainNode'}],'enyo/ObserverSupport':[function (module,exports,global,require,request){ +/** +* Exports the {@link module:enyo/ObserverSupport~ObserverSupport} mixin +* @module enyo/ObserverSupport +*/ +require('enyo'); + +var + kind = require('./kind'), + utils = require('./utils'); + +var + ObserverChain = require('./ObserverChain'); + +var observerTable = {}; + +kind.concatenated.push("observers"); + +/** +* Responds to changes in one or more properties. +* [Observers]{@link module:enyo/ObserverSupport~ObserverSupport#observers} may be registered in +* several different ways. See the {@link module:enyo/ObserverSupport} documentation +* for more details. Also note that, while observers should not be called +* directly, if defined on a [kind]{@glossary kind}, they may be +* overloaded for special behavior. +* +* @see {@link module:enyo/ObserverSupport} +* @see {@link module:enyo/ObserverSupport~ObserverSupport#observe} +* @callback module:enyo/ObserverSupport~ObserverSupport~Observer +* @param {*} was - The previous value of the property that has changed. +* @param {*} is - The current value of the property that has changed. +* @param {String} prop - The name of the property that has changed. +* @public +*/ + +function addObserver (path, fn, ctx, opts) { + + var observers = this.getObservers(), + chains = this.getChains(), + parts = path.split('.'), + prio = opts && opts.priority, + entries, + noChain; + + noChain = (opts && opts.noChain) || + chains[path] || + parts.length < 2 || + (parts.length === 2 && path[0] == '$'); + + if (observers[path] && !observers.hasOwnProperty(path)) { + observers[path] = observers[path].slice(); + } + + entries = observers[path] || (observers[path] = []); + entries[prio ? 'unshift' : 'push']({method: fn, ctx: ctx || this}); + + if (!noChain) { + this.getChains()[path] = new ObserverChain(path, this); + } + + return this; +} + +function removeObserver (obj, path, fn, ctx) { + var observers = obj.getObservers(path) + , chains = obj.getChains() + , idx, chain; + + if (observers && observers.length) { + idx = observers.findIndex(function (ln) { + return ln.method === fn && (ctx? ln.ctx === ctx: true); + }); + idx > -1 && observers.splice(idx, 1); + } + + if ((chain = chains[path]) && !observers.length) { + chain.destroy(); + } + + return obj; +} + +function notifyObservers (obj, path, was, is, opts) { + if (obj.isObserving()) { + var observers = obj.getObservers(path); + + if (observers && observers.length) { + for (var i=0, ln; (ln=observers[i]); ++i) { + if (typeof ln.method == "string") obj[ln.method](was, is, path, opts); + else ln.method.call(ln.ctx || obj, was, is, path, opts); + } + } + } else enqueue(obj, path, was, is, opts); + + return obj; +} + +function enqueue (obj, path, was, is, opts) { + if (obj._notificationQueueEnabled) { + var queue = obj._notificationQueue || (obj._notificationQueue = {}) + , ln = queue[path] || (queue[path] = {}); + + ln.was = was; + ln.is = is; + ln.opts = opts; + } +} + +function flushQueue (obj) { + var queue = obj._notificationQueue + , path, ln; + + if (queue) { + obj._notificationQueue = null; + + for (path in queue) { + ln = queue[path]; + obj.notify(path, ln.was, ln.is, ln.opts); + } + } +} + +/** +* Adds support for notifications on property changes. Most +* [kinds]{@glossary kind} (including all kinds that inherit from +* {@link module:enyo/CoreObject~Object}) already have this {@glossary mixin} applied. +* This allows for +* [observers]{@link module:enyo/ObserverSupport~ObserverSupport~Observer} to be +* [declared]{@link module:enyo/ObserverSupport~ObserverSupport#observers} or "implied" (see below). +* +* Implied observers are not declared, but derived from their `name`. They take +* the form `Changed`, where `` is the property to +* [observe]{@link module:enyo/ObserverSupport~ObserverSupport#observe}. For example: +* +* ```javascript +* var +* kind = require('enyo/kind'); +* +* module.exports = kind({ +* name: 'MyKind', +* +* // some local property +* value: true, +* +* // and the implied observer of that property +* valueChanged: function (was, is) { +* // do something now that it has changed +* enyo.log('value was "' + was + '" but now it is "' + is + '"'); +* } +* }); +* +* var mine = new MyKind(); +* mine.set('value', false); // -> value was "true" but now it is "false" +* ``` +* +* Using the `observers` property for its declarative syntax, an observer may +* observe any property (or properties), regardless of its `name`. For example: +* +* ```javascript +* var +* kind = require('enyo/kind'); +* +* module.exports = kind({ +* name: 'MyKind', +* +* // some local property +* value: true, +* +* // another local property +* count: 1, +* +* // declaring the observer +* observers: [ +* // the path can be a single string or an array of strings +* {method: 'myObserver', path: ['value', 'count']} +* ], +* +* // now this observer will be notified of changes to both properties +* myObserver: function (was, is, prop) { +* // do something now that it changed +* enyo.log(prop + ' was "' + was + '" but now it is "' + is + '"'); +* } +* }); +* +* var mine = new MyKind(); +* mine.set('value', false); // -> value was "true" but now it is "false" +* mine.set('count', 2); // -> count was "1" but now it is "2" +* ``` +* +* While observers may be [notified]{@link module:enyo/ObserverSupport~ObserverSupport#notify} of +* changes to multiple properties, this is not a typical use case for implied +* observers, since, by convention, they are only registered for the named +* property. +* +* There is one additional way to use observers, if necessary. You may use the +* API methods [observe()]{@link module:enyo/ObserverSupport~ObserverSupport#observe} and +* [unobserve()]{@link module:enyo/ObserverSupport~ObserverSupport#unobserve} to dynamically +* register and unregister observers as needed. For example: +* +* ```javascript +* var +* Object = require('enyo/CoreObject').Object; +* +* var object = new Object({value: true}); +* var observer = function (was, is) { +* enyo.log('value was "' + was + '" but now it is "' + is + '"'); +* }; +* +* object.observe('value', observer); +* object.set('value', false); // -> value was "true" but now it is "false" +* object.unobserve('value', observer); +* object.set('value', true); // no output because there is no observer +* ``` +* +* Be sure to read the documentation for these API methods; proper usage of +* these methods is important for avoiding common pitfalls and memory leaks. +* +* @mixin +* @public +*/ +var ObserverSupport = { + + /** + * @private + */ + name: "ObserverSupport", + + /** + * @private + */ + _observing: true, + + /** + * @private + */ + _observeCount: 0, + + /** + * @private + */ + _notificationQueue: null, + + /** + * @private + */ + _notificationQueueEnabled: true, + + /** + * Determines whether `_observing` is enabled. If + * [stopNotifications()]{@link module:enyo/ObserverSupport~ObserverSupport#stopNotifications} has + * been called, then this will return `false`. + * + * @see {@link module:enyo/ObserverSupport~ObserverSupport#stopNotifications} + * @see {@link module:enyo/ObserverSupport~ObserverSupport#startNotifications} + * @returns {Boolean} Whether or not the callee is observing. + */ + isObserving: function () { + return this._observing; + }, + + /** + * Returns an immutable list of [observers]{@link module:enyo/ObserverSupport~ObserverSupport~Observer} + * for the given `path`, or all observers for the callee. + * + * @param {String} [path] - Path or property path for which + * [observers]{@link module:enyo/ObserverSupport~ObserverSupport~Observer} will be returned. If not + * specified, all observers for the callee will be returned. + * + * @returns {module:enyo/ObserverSupport~ObserverSupport~Observer[]} The immutable + * [array]{@glossary Array} of observers. + * @public + */ + getObservers: function (path) { + var euid = this.euid || (this.euid = utils.uid('o')), + ret, + loc; + + loc = observerTable[euid] || (observerTable[euid] = ( + this._observers? Object.create(this._observers): {} + )); + + if (!path) return loc; + + ret = loc[path]; + + // if the special property exists... + if (loc['*']) ret = ret ? ret.concat(loc['*']) : loc['*'].slice(); + return ret; + }, + + /** + * @private + */ + getChains: function () { + return this._observerChains || (this._observerChains = {}); + }, + + /** + * @deprecated + * @alias {@link module:enyo/ObserverSupport~ObserverSupport#observe} + * @public + */ + addObserver: function () { + // @NOTE: In this case we use apply because of internal variable use of parameters + return addObserver.apply(this, arguments); + }, + + /** + * Registers an [observer]{@link module:enyo/ObserverSupport~ObserverSupport~Observer} to be + * [notified]{@link module:enyo/ObserverSupport~ObserverSupport#notify} when the given property has + * been changed. It is important to note that it is possible to register the + * same observer multiple times (although this is never the intention), so + * care should be taken to avoid that scenario. It is also important to + * understand how observers are stored and unregistered + * ([unobserved]{@link module:enyo/ObserverSupport~ObserverSupport#unobserve}). The `ctx` (context) + * parameter is stored with the observer reference. **If used when + * registering, it should also be used when unregistering.** + * + * @see {@link module:enyo/ObserverSupport~ObserverSupport#unobserve} + * @param {String} path - The property or property path to observe. + * @param {module:enyo/ObserverSupport~ObserverSupport~Observer} fn - The + * [observer]{@link module:enyo/ObserverSupport~ObserverSupport~Observer} method that responds to changes. + * @param {*} [ctx] - The `this` (context) under which to execute the observer. + * + * @returns {this} The callee for chaining. + * @public + */ + observe: function () { + // @NOTE: In this case we use apply because of internal variable use of parameters + return addObserver.apply(this, arguments); + }, + + /** + * @deprecated + * @alias {@link module:enyo/ObserverSupport~ObserverSupport#unobserve} + * @public + */ + removeObserver: function (path, fn, ctx) { + return removeObserver(this, path, fn); + }, + + /** + * Unregisters an [observer]{@link module:enyo/ObserverSupport~ObserverSupport~Observer}. If a `ctx` + * (context) was supplied to [observe()]{@link module:enyo/ObserverSupport~ObserverSupport#observe}, + * then it should also be supplied to this method. + * + * @see {@link module:enyo/ObserverSupport~ObserverSupport#observe} + * @param {String} path - The property or property path to unobserve. + * @param {module:enyo/ObserverSupport~ObserverSupport~Observer} fn - The + * [observer]{@link module:enyo/ObserverSupport~ObserverSupport~Observer} method that responds to changes. + * @param {*} [ctx] - The `this` (context) under which to execute the observer. + * + * @returns {this} The callee for chaining. + * @public + */ + unobserve: function (path, fn, ctx) { + return removeObserver(this, path, fn, ctx); + }, + + /** + * Removes all [observers]{@link module:enyo/ObserverSupport~ObserverSupport~Observer} from the + * callee. If a `path` parameter is provided, observers will only be removed + * from that path (or property). + * + * @param {String} [path] - A property or property path from which to remove all + * [observers]{@link module:enyo/ObserverSupport~ObserverSupport~Observer}. + * @returns {this} The callee for chaining. + */ + removeAllObservers: function (path) { + var euid = this.euid + , loc = euid && observerTable[euid]; + + if (loc) { + if (path) { + loc[path] = null; + } else { + observerTable[euid] = null; + } + } + + return this; + }, + + /** + * @deprecated + * @alias module:enyo/ObserverSupport~ObserverSupport#notify + * @public + */ + notifyObservers: function (path, was, is, opts) { + return notifyObservers(this, path, was, is, opts); + }, + + /** + * Triggers any [observers]{@link module:enyo/ObserverSupport~ObserverSupport~Observer} for the + * given `path`. The previous and current values must be supplied. This + * method is typically called automatically, but it may also be called + * forcibly by [setting]{@link module:enyo/CoreObject~Object#set} a value with the + * `force` option. + * + * @param {String} path - The property or property path to notify. + * @param {*} was - The previous value. + * @param {*} is - The current value. + * @returns {this} The callee for chaining. + */ + notify: function (path, was, is, opts) { + return notifyObservers(this, path, was, is, opts); + }, + + /** + * Stops all [notifications]{@link module:enyo/ObserverSupport~ObserverSupport#notify} from + * propagating. By default, all notifications will be queued and flushed once + * [startNotifications()]{@link module:enyo/ObserverSupport~ObserverSupport#startNotifications} + * has been called. Setting the optional `noQueue` flag will also disable the + * queue, or you can use the + * [disableNotificationQueue()]{@link module:enyo/ObserverSupport~ObserverSupport#disableNotificationQueue} and + * [enableNotificationQueue()]{@link module:enyo/ObserverSupport~ObserverSupport#enableNotificationQueue} + * API methods. `startNotifications()` will need to be called the same number + * of times that this method has been called. + * + * @see {@link module:enyo/ObserverSupport~ObserverSupport#startNotifications} + * @see {@link module:enyo/ObserverSupport~ObserverSupport#disableNotificationQueue} + * @see {@link module:enyo/ObserverSupport~ObserverSupport#enableNotificationQueue} + * @param {Boolean} [noQueue] - If `true`, this will also disable the notification queue. + * @returns {this} The callee for chaining. + */ + stopNotifications: function (noQueue) { + this._observing = false; + this._observeCount++; + noQueue && this.disableNotificationQueue(); + return this; + }, + + /** + * Starts [notifications]{@link module:enyo/ObserverSupport~ObserverSupport#notify} if they have + * been [disabled]{@link module:enyo/ObserverSupport~ObserverSupport#stopNotifications}. If the + * notification queue was not disabled, this will automatically flush the + * queue of all notifications that were encountered while stopped. This + * method must be called the same number of times that + * [stopNotifications()]{@link module:enyo/ObserverSupport~ObserverSupport#stopNotifications} was + * called. + * + * @see {@link module:enyo/ObserverSupport~ObserverSupport#stopNotifications} + * @see {@link module:enyo/ObserverSupport~ObserverSupport#disableNotificationQueue} + * @see {@link module:enyo/ObserverSupport~ObserverSupport#enableNotificationQueue} + * @param {Boolean} [queue] - If `true` and the notification queue is disabled, + * the queue will be re-enabled. + * @returns {this} The callee for chaining. + */ + startNotifications: function (queue) { + this._observeCount && this._observeCount--; + this._observeCount === 0 && (this._observing = true); + queue && this.enableNotificationQueue(); + this.isObserving() && flushQueue(this); + return this; + }, + + /** + * Re-enables the notification queue, if it was disabled. + * + * @see {@link module:enyo/ObserverSupport~ObserverSupport#disableNotificationQueue} + * @returns {this} The callee for chaining. + */ + enableNotificationQueue: function () { + this._notificationQueueEnabled = true; + return this; + }, + + /** + * If the notification queue is enabled (the default), it will be disabled + * and any notifications in the queue will be removed. + * + * @see {@link module:enyo/ObserverSupport~ObserverSupport#enableNotificationQueue} + * @returns {this} The callee for chaining. + */ + disableNotificationQueue: function () { + this._notificationQueueEnabled = false; + this._notificationQueue = null; + return this; + }, + + /** + * @private + */ + constructor: kind.inherit(function (sup) { + return function () { + var chains, chain, path, entries, i; + + // if there are any observers that need to create dynamic chains + // we look for and instance those now + if (this._observerChains) { + chains = this._observerChains; + this._observerChains = {}; + for (path in chains) { + entries = chains[path]; + for (i = 0; (chain = entries[i]); ++i) this.observe(path, chain.method); + } + } + + sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + destroy: kind.inherit(function (sup) { + return function () { + var chains = this._observerChains, + path, + chain; + + sup.apply(this, arguments); + + if (chains) { + for (path in chains) { + chain = chains[path]; + chain.destroy(); + } + + this._observerChains = null; + } + }; + }) + +}; + +module.exports = ObserverSupport; + +/** +* Hijack the original so we can add additional default behavior. +* +* @private +*/ +var sup = kind.concatHandler; + +// @NOTE: It seems like a lot of work but it really won't happen that much and the more +// we push to kind-time the better for initialization time + +/** @private */ +kind.concatHandler = function (ctor, props, instance) { + + sup.call(this, ctor, props, instance); + + if (props === ObserverSupport) return; + + var proto = ctor.prototype || ctor + , observers = proto._observers? Object.create(proto._observers): null + , incoming = props.observers + , chains = proto._observerChains && Object.create(proto._observerChains); + + if (!observers) { + if (proto.kindName) observers = {}; + else return; + } + + if (incoming && !(incoming instanceof Array)) { + (function () { + var tmp = [], deps, name; + // here is the slow iteration over the properties... + for (name in props.observers) { + // points to the dependencies of the computed method + deps = props.observers[name]; + // create a single entry now for the method/computed with all dependencies + tmp.push({method: name, path: deps}); + } + incoming = tmp; + }()); + // we need to ensure we don't modify the fixed array of a mixin or reused object + // because it could wind up inadvertantly adding the same entry multiple times + } else if (incoming) incoming = incoming.slice(); + + // this scan is required to figure out what auto-observers might be present + for (var key in props) { + if (key.slice(-7) == "Changed") { + incoming || (incoming = []); + incoming.push({method: key, path: key.slice(0, -7)}); + } + } + + var addObserverEntry = function (path, method) { + var obs; + // we have to make sure that the path isn't a chain because if it is we add it + // to the chains instead + if (path.indexOf(".") > -1) { + if (!chains) chains = {}; + obs = chains[path] || (chains[path] = []); + obs.push({method: method}); + } else { + if (observers[path] && !observers.hasOwnProperty(path)) observers[path] = observers[path].slice(); + obs = observers[path] || (observers[path] = []); + if (!obs.find(function (ln) { return ln.method == method; })) obs.push({method: method}); + } + }; + + if (incoming) { + incoming.forEach(function (ln) { + // first we determine if the path itself is an array of paths to observe + if (ln.path && ln.path instanceof Array) ln.path.forEach(function (en) { addObserverEntry(en, ln.method); }); + else addObserverEntry(ln.path, ln.method); + }); + } + + // we clear the key so it will not be added to the prototype + // delete props.observers; + // we update the properties to whatever their new values may be + proto._observers = observers; + proto._observerChains = chains; +}; + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./ObserverChain':'enyo/ObserverChain'}],'enyo/CoreObject':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/CoreObject~Object} kind. +* @module enyo/CoreObject +*/ + +var + kind = require('./kind'), + logger = require('./logger'), + utils = require('./utils'); + +var + MixinSupport = require('./MixinSupport'), + ObserverSupport = require('./ObserverSupport'), + BindingSupport = require('./BindingSupport'); + +// ComputedSupport is applied to all kinds at creation time but must be require()'d somewhere to be +// included in builds. This is that somewhere. +require('./ComputedSupport'); + +/** +* Used by all [objects]{@link module:enyo/CoreObject~Object} and [subkinds]{@glossary subkind} when using the +* {@link module:enyo/CoreObject~Object#log}, {@link module:enyo/CoreObject~Object#warn} and {@link module:enyo/CoreObject~Object#error} methods. +* +* @private +*/ +function log (method, args) { + if (logger.shouldLog(method)) { + try { + throw new Error(); + } catch(err) { + logger._log(method, [args.callee.caller.displayName + ': '] + .concat(utils.cloneArray(args))); + logger.log(err.stack); + } + } +} + +/** +* {@link module:enyo/CoreObject~Object} lies at the heart of the Enyo framework's implementations of property +* publishing, computed properties (via the [ComputedSupport]{@link module:enyo/ComputedSupport} +* {@glossary mixin}), and data binding (via the {@link module:enyo/BindingSupport~BindingSupport} mixin and +* {@link module:enyo/Binding~Binding} object). It also provides several utility [functions]{@glossary Function} +* for its [subkinds]{@glossary subkind}. +* +* @class Object +* @mixes module:enyo/MixinSupport +* @mixes module:enyo/ObserverSupport +* @mixes module:enyo/BindingSupport +* @public +*/ +var CoreObject = module.exports = kind( + /** @lends module:enyo/CoreObject~Object.prototype */ { + + /** + * @private + */ + name: 'enyo.Object', + + /** + * @private + */ + kind: null, + + /** + * @private + */ + + + /** + * Will be `true` if the [destroy()]{@link module:enyo/CoreObject~Object#destroy} method has been called; + * otherwise, `false`. + * + * @readonly + * @type {Boolean} + * @default false + * @public + */ + destroyed: false, + + /** + * @private + */ + mixins: [MixinSupport, ObserverSupport, BindingSupport], + + /** + * @private + */ + constructor: function (props) { + this.importProps(props); + }, + + /** + * Imports the values from the given [object]{@glossary Object}. Automatically called + * from the [constructor]{@link module:enyo/CoreObject~Object#constructor}. + * + * @param {Object} props - If provided, the [object]{@glossary Object} from which to + * retrieve [keys/values]{@glossary Object.keys} to mix in. + * @returns {this} The callee for chaining. + * @public + */ + importProps: function (props) { + var key; + + if (props) { + kind.concatHandler(this, props, true); + // if props is a default hash this is significantly faster than + // requiring the hasOwnProperty check every time + if (!props.kindName) { + for (key in props) { + kind.concatenated.indexOf(key) === -1 && (this[key] = props[key]); + } + } else { + for (key in props) { + if (kind.concatenated.indexOf(key) === -1 && props.hasOwnProperty(key)) { + this[key] = props[key]; + } + } + } + } + + return this; + }, + + /** + * Calls the [destroy()]{@link module:enyo/CoreObject~Object#destroy} method for the named {@link module:enyo/CoreObject~Object} + * property. + * + * @param {String} name - The name of the property to destroy, if possible. + * @returns {this} The callee for chaining. + * @public + */ + destroyObject: function (name) { + if (this[name] && this[name].destroy) { + this[name].destroy(); + } + this[name] = null; + + return this; + }, + + /** + * Sends a log message to the [console]{@glossary console}, prepended with the name + * of the {@glossary kind} and method from which `log()` was invoked. Multiple + * {@glossary arguments} are coerced to {@glossary String} and + * [joined with spaces]{@glossary Array.join}. + * + * ```javascript + * var kind = require('enyo/kind'), + * Object = require('enyo/CoreObject'); + * kind({ + * name: 'MyObject', + * kind: Object, + * hello: function() { + * this.log('says', 'hi'); + * // shows in the console: MyObject.hello: says hi + * } + * }); + * ``` + * @public + */ + log: function () { + var acc = arguments.callee.caller, + nom = ((acc ? acc.displayName : '') || '(instance method)') + ':', + args = Array.prototype.slice.call(arguments); + args.unshift(nom); + logger.log('log', args); + }, + + /** + * Same as [log()]{@link module:enyo/CoreObject~Object#log}, except that it uses the + * console's [warn()]{@glossary console.warn} method (if it exists). + * + * @public + */ + warn: function () { + log('warn', arguments); + }, + + /** + * Same as [log()]{@link module:enyo/CoreObject~Object#log}, except that it uses the + * console's [error()]{@glossary console.error} method (if it exists). + * + * @public + */ + error: function () { + log('error', arguments); + }, + + /** + * Retrieves the value for the given path. The value may be retrieved as long as the given + * path is resolvable relative to the given {@link module:enyo/CoreObject~Object}. See + * [getPath()]{@link module:enyo/utils#getPath} for complete details. + * + * This method is backwards-compatible and will automatically call any existing getter + * method that uses the "getProperty" naming convention. (Moving forward, however, Enyo code + * should use [computed properties]{@link module:enyo/ComputedSupport} instead of relying on the + * getter naming convention.) + * + * @param {String} path - The path from which to retrieve a value. + * @returns {*} The value for the given path or [undefined]{@glossary undefined} if + * the path could not be completely resolved. + * @public + */ + get: function () { + return utils.getPath.apply(this, arguments); + }, + + /** + * Updates the value for the given path. The value may be set as long as the + * given path is resolvable relative to the given {@link module:enyo/CoreObject~Object}. See + * [setPath()]{@link module:enyo/utils#setPath} for complete details. + * + * @param {String} path - The path for which to set the given value. + * @param {*} value - The value to set. + * @param {Object} [opts] - An options hash. + * @returns {this} The callee for chaining. + * @public + */ + set: function () { + return utils.setPath.apply(this, arguments); + }, + + /** + * Binds a [callback]{@glossary callback} to this [object]{@link module:enyo/CoreObject~Object}. + * If the object has been destroyed, the bound method will be aborted cleanly, + * with no value returned. + * + * This method should generally be used instead of {@link module:enyo/utils#bind} for running + * code in the context of an instance of {@link module:enyo/CoreObject~Object} or one of its + * [subkinds]{@glossary subkind}. + * + * @public + */ + bindSafely: function () { + var args = Array.prototype.slice.call(arguments); + args.unshift(this); + return utils.bindSafely.apply(null, args); + }, + + /** + * An abstract method (primarily) that sets the [destroyed]{@link module:enyo/CoreObject~Object#destroyed} + * property to `true`. + * + * @returns {this} The callee for chaining. + * @public + */ + destroy: function () { + + // Since JS objects are never truly destroyed (GC'd) until all references are + // gone, we might have some delayed action on this object that needs access + // to this flag. + // Using this.set to make the property observable + return this.set('destroyed', true); + } +}); + +/** +* @private +*/ +CoreObject.concat = function (ctor, props) { + var pubs = props.published, + cpy, + prop; + + if (pubs) { + cpy = ctor.prototype || ctor; + for (prop in pubs) { + // need to make sure that even though a property is 'published' + // it does not overwrite any computed properties + if (props[prop] && typeof props[prop] == 'function') continue; + addGetterSetter(prop, pubs[prop], cpy); + } + } +}; + +/** +* This method creates a getter/setter for a published property of an {@link module:enyo/CoreObject~Object}, but is +* deprecated. It is maintained for purposes of backwards compatibility. The preferred method is +* to mark public and protected (private) methods and properties using documentation or other +* means and rely on the [get]{@link module:enyo/CoreObject~Object#get} and [set]{@link module:enyo/CoreObject~Object#set} methods of +* {@link module:enyo/CoreObject~Object} instances. +* +* @private +*/ +function addGetterSetter (prop, value, proto) { + + // so we don't need to re-execute this over and over and over... + var cap = utils.cap(prop), + getName = 'get' + cap, + setName = 'set' + cap, + getters = proto._getters || (proto._getters = {}), + setters = proto._setters || (proto._setters = {}), + fn; + + // we assign the default value from the published block to the prototype + // so it will be initialized properly + proto[prop] = value; + + // check for a supplied getter and if there isn't one we create one otherwise + // we mark the supplied getter in the tracking object so the global getPath will + // know about it + if (!(fn = proto[getName]) || typeof fn != 'function') { + fn = proto[getName] = function () { + return utils.getPath.fast.call(this, prop); + }; + + // and we mark it as generated + fn.generated = true; + } else if (fn && typeof fn == 'function' && !fn.generated) getters[prop] = getName; + + // we need to do the same thing for the setters + if (!(fn = proto[setName]) || typeof fn != 'function') { + fn = proto[setName] = function (val) { + return utils.setPath.fast.call(this, prop, val); + }; + + // and we mark it as generated + fn.generated = true; + } else if (fn && typeof fn == 'function' && !fn.generated) setters[prop] = setName; +} + +},{'./kind':'enyo/kind','./logger':'enyo/logger','./utils':'enyo/utils','./MixinSupport':'enyo/MixinSupport','./ObserverSupport':'enyo/ObserverSupport','./BindingSupport':'enyo/BindingSupport','./ComputedSupport':'enyo/ComputedSupport'}],'enyo/AnimationSupport/Core':[function (module,exports,global,require,request){ +require('enyo'); + +var + kind = require('../kind'), + animation = require('../animation'), + utils = require('../utils'), + tween = require('./Tween'); + +var + CoreObject = require('../CoreObject'); + +/** +* This module returns the Loop singleton +* Core module is responsible for handling all animations happening in Enyo. +* The responsibilities of this module is to; +* - Trigger vendor specific rAF. +* - Knowing all elements which have requested for animation. +* - Tween animation frames for each characters. +* +* @module enyo/Core +*/ +module.exports = kind.singleton({ + /** @lends module:enyo/Core */ + + /** + * @private + */ + name: 'enyo.Core', + /** + * @private + */ + kind: CoreObject, + + /** + * @private + */ + chracs: [], + + /** + * @private + */ + evnts: [], + + /** + * @private + */ + req: 0, + + /** + * @private + */ + running: false, + + /** + * Core base API to start animation functionalities. + * The purpose of this method is to check if the animation is already started or not + * otherwise wake up core to handle animation for a character. + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * @parameter charc- Animation character + * + * @public + */ + trigger: function (charc) { + if (!charc.animating) { + this.chracs.push(charc); + } + if (!this.running) { + this.running = true; + this.start(); + } + }, + + /** + * Core public API to check if core is handling animation for particular + * document element. + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * @parameter charc- Animation character + * + * @public + */ + exists: function (eventTarget) { + for (var i = 0; i < this.chracs.length; i++) { + if (this.chracs[i].hasNode() === eventTarget) { // Already Animating + return this.chracs[i]; + } + } + }, + + /** + * Animator public API to remove animation happening on a particular + * document element. + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * @parameter charc- Animation character + * + * @public + */ + remove: function (curr) { + this.chracs.splice(this.chracs.indexOf(curr), 1); + }, + + /** + * Animator public API to register character with event + * + * @parameter charc- Animation character + * + * @public + */ + register: function (charc) { + this.evnts.push(charc); + + /*if (!this.running) { + this.running = true; + this.start(); + }*/ + this.start(); + }, + + /** + * @private + */ + start: function () { + this.req = animation.requestAnimationFrame(this.bindSafely(this.loop)); + }, + + /** + * @private + */ + cancel: function () { + animation.cancelRequestAnimationFrame(this.req); + }, + + /** + * @private + */ + loop: function () { + var i, curr, + len = this.chracs.length, + ts = utils.perfNow(); + + if (len <= 0) { + this.cancel(); + this.running = false; + return; + } + + for (i = 0; i < len; i++) { + curr = this.chracs[i]; + if (curr && curr.ready()) { + tween.update(curr, ts); + if (!curr._lastTime || ts >= curr._lastTime) { + tween.complete(curr); + curr.completed(curr); + if(!curr.active) { + this.remove(curr); + } + } + } + } + + len = this.evnts.length; + for (i = 0; i < len; i++) { + if (typeof this.evnts[i].commitAnimation === 'function') { + this.evnts[i].commitAnimation(); + } + } + this.start(); + }, + + /** + * @private + */ + dummy: function () { + animation.requestAnimationFrame(function() {}); + } +}); +},{'../kind':'enyo/kind','../animation':'enyo/animation','../utils':'enyo/utils','./Tween':'enyo/AnimationSupport/Tween','../CoreObject':'enyo/CoreObject'}],'enyo/jobs':[function (module,exports,global,require,request){ +require('enyo'); + +var + utils = require('./utils'), + kind = require('./kind'); + +var CoreObject = require('./CoreObject'); + +/** +* The {@link module:enyo/jobs} singleton provides a mechanism for queueing tasks +* (i.e., functions) for execution in order of priority. The execution of the +* current job stack may be blocked programmatically by setting a priority +* level (run level) below which no jobs are executed. +* +* At the moment, only {@link module:enyo/Animator~Animator} uses this interface, setting a +* priority of 4, which blocks all low priority tasks from executing during +* animations. To maintain backward compatibility, jobs are assigned a priority +* of 5 by default; thus they are not blocked by animations. +* +* Normally, application code will not use `enyo.jobs` directly, but will +* instead use the [job()]{@link module:enyo/Component~Component#job} method of +* {@link module:enyo/Component~Component}. +* +* @module enyo/jobs +* @public +*/ +module.exports = kind.singleton( + /** @lends module:enyo/jobs */ { + + kind: CoreObject, + + /** + * @private + */ + published: /** @lends module:enyo/jobs~jobs */ { + + /** + * The current priority level. + * + * @type {Number} + * @default 0 + * @public + */ + priorityLevel: 0 + }, + + /** + * Prioritized by index. + * + * @private + */ + _jobs: [ [], [], [], [], [], [], [], [], [], [] ], + + /** + * @private + */ + _priorities: {}, + + /** + * @private + */ + _namedJobs: {}, + + /** + * @private + */ + _magicWords: { + 'low': 3, + 'normal': 5, + 'high': 7 + }, + + /** + * Adds a [job]{@link module:enyo/job} to the job queue. If the current priority + * level is higher than this job's priority, this job gets deferred until the + * job level drops; if it is lower, this job is run immediately. + * + * @param {Function} job - The actual {@glossary Function} to execute as the + * [job]{@link module:enyo/job}. + * @param {Number} priority - The priority of the job. + * @param {String} nom - The name of the job for later reference. + * @public + */ + add: function (job, priority, nom) { + priority = priority || 5; + + // magic words: low = 3, normal = 5, high = 7 + priority = utils.isString(priority) ? this._magicWords[priority] : priority; + + // if a job of the same name exists, remove it first (replace it) + if(nom){ + this.remove(nom); + this._namedJobs[nom] = priority; + } + + // if the job is of higher priority than the current priority level then + // there's no point in queueing it + if(priority >= this.priorityLevel){ + job(); + } else { + this._jobs[priority - 1].push({fkt: job, name: nom}); + } + }, + + /** + * Will remove the named [job]{@link module:enyo/job} from the queue. + * + * @param {String} nom - The name of the [job]{@link module:enyo/job} to remove. + * @returns {Array} An {@glossary Array} that will contain the removed job if + * it was found, or empty if it was not found. + * @public + */ + remove: function (nom) { + var jobs = this._jobs[this._namedJobs[nom] - 1]; + if(jobs){ + for(var j = jobs.length-1; j >= 0; j--){ + if(jobs[j].name === nom){ + return jobs.splice(j, 1); + } + } + } + }, + + /** + * Adds a new priority level at which jobs will be executed. If it is higher than the + * highest current priority, the priority level rises. Newly added jobs below that priority + * level are deferred until the priority is removed (i.e., unregistered). + * + * @param {Number} priority - The priority value to register. + * @param {String} id - The name of the priority. + * @public + */ + registerPriority: function(priority, id) { + this._priorities[id] = priority; + this.setPriorityLevel( Math.max(priority, this.priorityLevel) ); + }, + + /** + * Removes a priority level. If the removed priority was previously the + * highest priority, the priority level drops to the next highest priority + * and queued jobs with a higher priority are executed. + * + * @param {String} id - The name of the priority level to remove. + * @public + */ + unregisterPriority: function (id) { + var highestPriority = 0; + + // remove priority + delete this._priorities[id]; + + // find new highest current priority + for( var i in this._priorities ){ + highestPriority = Math.max(highestPriority, this._priorities[i]); + } + + this.setPriorityLevel( highestPriority ); + }, + + /** + * Tries to run next job if priority level has dropped. + * + * @type {module:enyo/ObserverSupport~ObserverSupport~Observer} + * @private + */ + priorityLevelChanged: function (was) { + if(was > this.priorityLevel){ + this._doJob(); + } + }, + + /** + * Finds and executes the job of highest priority; in this way, all jobs with priority + * greater than or equal to the current level are run, in order of their priority (highest + * to lowest). + * + * @private + */ + _doJob: function () { + var job; + // find the job of highest priority above the current priority level + // and remove from the job list + for (var i = 9; i >= this.priorityLevel; i--){ + if (this._jobs[i].length) { + job = this._jobs[i].shift(); + break; + } + } + + // allow other events to pass through + if (job) { + job.fkt(); + delete this._namedJobs[job.name]; + setTimeout(utils.bind(this, '_doJob'), 10); + } + } +}); + +},{'./utils':'enyo/utils','./kind':'enyo/kind','./CoreObject':'enyo/CoreObject'}],'enyo/Store':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Store~Store} kind. +* @module enyo/Store +*/ + +var + kind = require('./kind'), + utils = require('./utils'); + +var + ModelList = require('./ModelList'), + EventEmitter = require('./EventEmitter'), + CoreObject = require('./CoreObject'); + +/** +* Only necessary because of the order in which mixins are applied. +* +* @class +* @private +*/ +var BaseStore = kind({ + kind: CoreObject, + mixins: [EventEmitter] +}); + +/** +* This method should determine whether the given [model]{@link module:enyo/Model~Model} +* should be included in the filtered set for the [find()]{@link module:enyo/Store~Store#find} +* method. +* +* @callback enyo.Store~Filter +* @param {module:enyo/Model~Model} model - The [model]{@link module:enyo/Model~Model} to filter. +* @returns {Boolean} `true` if the model meets the filter requirements; +* otherwise, `false`. +*/ + +/** +* The configuration options for the [find()]{@link module:enyo/Store~Store#find} method. +* +* @typedef {Object} enyo.Store~FindOptions +* @property {Boolean} all=true - Whether or not to include more than one match for the +* filter method. If `true`, an array of matches is returned; otherwise, a single match. +* @property {Object} context - If provided, it will be used as the `this` (context) of +* the filter method. +*/ + +/** +* An anonymous kind used internally for the singleton {@link module:enyo/Store~Store}. +* +* @class Store +* @mixes module:enyo/EventEmitter +* @extends module:enyo/CoreObject~Object +* @protected +*/ +var Store = kind( + /** @lends module:enyo/Store~Store.prototype */ { + + name: 'enyo.Store', + + /** + * @private + */ + kind: BaseStore, + + /** + * Finds a [model (or models)]{@link module:enyo/Model~Model} of a certain [kind]{@glossary kind}. + * It uses the return value from a filter method to determine whether a particular + * model will be included. Set the optional `all` flag to `true` to ensure that + * the method looks for all matches; otherwise, it will return the first positive + * match. + * + * @see {@glossary Array.find} + * @param {module:enyo/Model~Model} ctor - The constructor for the [kind]{@glossary kind} of + * [model]{@link module:enyo/Model~Model} to be filtered. + * @param {module:enyo/Store~Store~Filter} fn - The filter method. + * @param {module:enyo/Store~Store~FindOptions} [opts] - The options parameter. + * @returns {(module:enyo/Model~Model|module:enyo/Model~Model[]|undefined)} If the `all` flag is `true`, + * returns an array of models; otherwise, returns the first model that returned + * that returned `true` from the filter method. Returns `undefined` if `all` is + * `false` and no match could be found. + * @public + */ + find: function (ctor, fn, opts) { + var kindName = ctor.prototype.kindName, + list = this.models[kindName], + options = {all: true, context: this}; + + // allows the method to be called with a constructor only and will return an + // immutable copy of the array of all models of that type or an empty array + if (arguments.length == 1 || typeof fn != 'function') { + return list ? list.slice() : []; + } + + // ensure we use defaults with any provided options + opts = opts ? utils.mixin({}, [options, opts]) : options; + + if (list) return opts.all ? list.filter(fn, opts.context) : list.find(fn, opts.context); + + // if it happens it could not find a list for the requested kind we fudge the return + // so it can keep on executing + else return opts.all ? [] : undefined; + }, + + /** + * This method is an alias for [find()]{@link module:enyo/Store~Store#find}. + * + * @deprecated + * @public + */ + findLocal: function () { + return this.find.apply(this, arguments); + }, + + /** + * @private + */ + add: function (models, opts) { + var ctor = models && (models instanceof Array ? models[0].ctor : models.ctor), + kindName = ctor && ctor.prototype.kindName, + list = kindName && this.models[kindName], + added, + i; + + // if we were able to find the list then we go ahead and attempt to add the models + if (list) { + added = list.add(models); + // if we successfully added models and this was a default operation (not being + // batched by a collection or other feature) we emit the event needed primarily + // by relational models but could be useful other places + if (added.length && (!opts || !opts.silent)) { + for (i = 0; i < added.length; ++i) { + this.emit(ctor, 'add', {model: added[i]}); + } + } + } + + return this; + }, + + /** + * @private + */ + remove: function (models, opts) { + var ctor = models && (models instanceof Array ? models[0].ctor : models.ctor), + kindName = ctor && ctor.prototype.kindName, + list = kindName && this.models[kindName], + removed, + i; + + // if we were able to find the list then we go ahead and attempt to remove the models + if (list) { + removed = list.remove(models); + // if we successfully removed models and this was a default operation (not being + // batched by a collection or other feature) we emit the event. Needed primarily + // by relational models but could be useful other places + if (removed.length && (!opts || !opts.silent)) { + for (i = 0; i < removed.length; ++i) { + this.emit(ctor, 'remove', {model: removed[i]}); + } + } + } + + return this; + }, + + /** + * Determines, from the given parameters, whether the [store]{@link module:enyo/Store~Store} + * has a specific [model]{@link module:enyo/Model~Model}. + * + * @param {(Function|module:enyo/Model~Model)} ctor Can be the constructor for an {@link module:enyo/Model~Model} + * or a model instance. Must be a constructor unless a model instance is passed as the + * optional `model` parameter. + * @param {(String|Number|module:enyo/Model~Model)} [model] If the `ctor` parameter is a + * constructor, this may be a [Number]{@glossary Number} or a [String]{@glossary String} + * representing a [primaryKey]{@link module:enyo/Model~Model#primaryKey} for the given model, or an + * instance of a model. + * @returns {Boolean} Whether or not the [store]{@link module:enyo/Store~Store} has the given + * [model]{@link module:enyo/Model~Model}. + * @public + */ + has: function (ctor, model) { + var list; + + if (!model) { + model = ctor; + ctor = model.ctor; + } + + list = this.models[ctor.prototype.kindName]; + return list ? list.has(model) : false; + }, + + /** + * @private + */ + resolve: function (ctor, model) { + var list = this.models[ctor && ctor.prototype.kindName]; + return list? list.resolve(model): undefined; + }, + + /** + * @private + */ + constructor: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + + this._scopeListeners = []; + + // all future sub-kinds of enyo.Model that are processed will automatically + // create/add their entries to this object in their concat method + this.models = { + 'enyo.Model': new ModelList() + }; + }; + }), + + /** + * @private + */ + scopeListeners: function (scope, e) { + return !scope ? this._scopeListeners : this._scopeListeners.filter(function (ln) { + return ln.scope === scope ? !e ? true : ln.event == e : false; + }); + }, + + /** + * @private + */ + on: kind.inherit(function (sup) { + return function (ctor, e, fn, ctx) { + if (typeof ctor == 'function') { + this.scopeListeners().push({ + scope: ctor, + event: e, + method: fn, + ctx: ctx || this + }); + + return this; + } + + return sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + off: kind.inherit(function (sup) { + return function (ctor, e, fn) { + var listeners, + idx; + + if (typeof ctor == 'function') { + listeners = this.scopeListeners(ctor); + if (listeners.length) { + idx = listeners.findIndex(function (ln) { + return ln.event == e && ln.method === fn; + }); + + // if it found the entry we remove it + if (idx >= 0) listeners.splice(idx, 1); + } + return this; + } + }; + }), + + /** + * @private + */ + emit: kind.inherit(function (sup) { + return function (ctor, e) { + var listeners, + args; + + if (typeof ctor == 'function') { + listeners = this.scopeListeners(ctor, e); + + if (listeners.length) { + args = utils.toArray(arguments).slice(1); + args.unshift(this); + listeners.forEach(function (ln) { + ln.method.apply(ln.ctx, args); + }); + return true; + } + return false; + } + + return sup.apply(this, arguments); + }; + }) +}); + +/** +* A runtime database for working with [models]{@link module:enyo/Model~Model}. It is primarily used +* internally by data layer [kinds]{@glossary kind} ({@link module:enyo/Model~Model}, +* {@link module:enyo/Collection~Collection}, and {@link module:enyo/RelationalModel~RelationalModel}). +* +* @see module:enyo/Model~Model +* @see module:enyo/Collection~Collection +* @see module:enyo/RelationalModel~RelationalModel +* @type enyo.Store +* @memberof enyo +* @public +*/ +module.exports = new Store(); + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./ModelList':'enyo/ModelList','./EventEmitter':'enyo/EventEmitter','./CoreObject':'enyo/CoreObject'}],'enyo/AnimationSupport/Fadeable':[function (module,exports,global,require,request){ +var + kind = require('../kind'), + animation = require('./Core'); + +/** + * Interface to achieve fade animation + * + * @module enyo/AnimationSupport/Fadeable + * @public + */ +module.exports = { + + /** + * @private + */ + name: 'Fadeable', + + /** + * To start animation + */ + animate: true, + + /** + * @private + */ + fadableValue: 0, + + /** + * @public + * Make the character invisible + */ + invisible: function() { + this.addAnimation({ + opacity: 0 + }); + }, + + /** + * @public + * Make the character transparent + * @default 0.5 + * @parameter value - set transparency value + */ + transparent: function(value) { + value = value || 0.5; + this.addAnimation({ + opacity: value + }); + }, + + /** + * @public + * Make the character visible + */ + opaque: function() { + this.addAnimation({ + opacity: 1 + }); + }, + + /** + * @public + * Fade element based on event trigger + */ + fadeByDelta: function(deltaValue) { + if (deltaValue !== 0) { + this.fadableValue = this.fadableValue + deltaValue * 0.1; + if (this.fadableValue <= 0) { + this.fadableValue = 0; + } else if (this.fadableValue >= 1) { + this.fadableValue = 1; + } + } + this.addAnimation({ + opacity: this.fadableValue + }); + }, + + /** + * @public + * Bubble the fadeable event + */ + triggerEvent: function(e) { + console.log("TODO: Trigger the fadeable event" + e); + //this.doFadeStart(); + } +}; + +},{'../kind':'enyo/kind','./Core':'enyo/AnimationSupport/Core'}],'enyo/AnimationSupport/Slideable':[function (module,exports,global,require,request){ +var + kind = require('../kind'), + animation = require('./Core'); + +/** + * Interface to achieve slide animation + * + * @module enyo/AnimationSupport/Slideable + * @public + */ +module.exports = { + + /** + * @private + */ + name: 'Slideable', + + /** + * To start animation + */ + animate: true, + + /** + * @public + * slide animation in left direction + * @parameter: slideDistance - distance in pixels to slide in left direction + */ + left: function(slideDistance) { + this.slide((-1 * slideDistance), 0, 0); + }, + + /** + * @public + * slide animation in right direction + * @parameter: slideDistance - distance in pixels to slide in right direction + */ + right: function(slideDistance) { + this.slide(slideDistance, 0, 0); + }, + + /** + * @public + * slide animation upward + * @parameter: slideDistance - distance in pixels to slide upward + */ + up: function(slideDistance) { + this.slide(0, (-1 * slideDistance), 0); + }, + + /** + * @public + * slide animation downward + * @parameter: slideDistance - distance in pixels to slide downward + */ + down: function(slideDistance) { + this.slide(0, slideDistance, 0); + }, + + /** + * @public + * slide animation in custom direction + * @parameter: x - css property to slide in x-axis direction + * @parameter: y - css property to slide in y-axis direction + * @parameter: z - css property to slide in z-axis direction + */ + slide: function(x, y, z) { + x = x || 0; + y = y || 0; + z = z || 0; + switch (this.direction) { + case "horizontal": + this.addAnimation({ + translate: x + "," + 0 + "," + 0 + }); + break; + case "vertical": + this.addAnimation({ + translate: 0 + "," + y + "," + 0 + }); + break; + default: + this.addAnimation({ + translate: x + "," + y + "," + z + }); + } + } +}; + +},{'../kind':'enyo/kind','./Core':'enyo/AnimationSupport/Core'}],'enyo/AnimationSupport/KeyFrame':[function (module,exports,global,require,request){ +require('enyo'); + +var + kind = require('../kind'), + animation = require('./Core'), + utils = require('../utils'), + CoreObject = require('../CoreObject'); + +/** +* This module returns the Loop singleton +* @module enyo/KeyFrame +*/ +var keyFrame = module.exports = kind.singleton({ + /** @lends module:enyo/KeyFrame */ + + /** + * @private + */ + name: 'enyo.KeyFrame', + /** + * @private + */ + kind: CoreObject, + + /** + * KeyFrame base API to perform animation on any document element + * repersented as a Character. The purpose of this method is to add a new + * character to Animation Core based on animation properties passed as + * parameter to this function and also to manage the frames allocated to + * each of individual poses. + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * @parameter charc- Character responsible for animation. + * keyframe- Key frame Animation propeties represented as CSS objects. + * like: {0: {"rotateX": "0"}, 50: {"rotateX": "90"}, 100: {"rotateX": "180"}} + * @public + */ + animate: function (charc, proto) { + var prop, + cb = proto.completed, + keyframe = proto.keyFrame; + + charc.keyProps = []; + charc.keyTime = []; + charc.currentIndex = 0; + for (prop in keyframe) { + charc.keyTime.push(prop); + charc.keyProps.push(keyframe[prop]); + } + charc.keyframeCallback = cb; + charc.initialTime = utils.perfNow(); + charc.totalDuration = proto.duration; + charc.completed = this.bindSafely(this.reframe); + + this.keyFraming(charc); + }, + + /** + * KeyFrame's public API to reverse an animation. + * The purpose of this method is to find the animating character based on + * the DOM provided and reversing a keyframe animation by interchanging its intial + * state with final state and final state with current state + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * @parameter dom- Document element on which animation will be reversed. + * + * @public + */ + reverse: function (dom) { + var charc = animation.exists(dom), + finalState, duration; + if (charc) { + finalState = charc._startAnim; + duration = utils.perfNow() - charc.initialTime; + animation.remove(charc); + + charc.setAnimation(finalState); + charc.setInitial(charc.currentState); + charc.setDuration(duration); + charc.totalDuration = duration; + charc.keyProps = []; + charc.keyTime = []; + charc.animating = false; + animation.trigger(charc); + } + }, + + trigger: function (charc) { + animation.trigger(charc); + } +}); + +/** +* @private +*/ +keyFrame.keyFraming = function (charc, callback) { + var index = charc.currentIndex || 0, + old = charc.keyTime[index -1], + next = charc.keyTime[index], + total = charc.totalDuration, + time = charc.currentIndex ? total * ((next - old)/100) : "0"; + + charc.setAnimation(charc.keyProps[index]); + charc.setInitial(charc.currentState); + charc.setDuration(time); + charc.animating = false; + charc.currentIndex = index; + animation.trigger(charc); +}; + +/** +* @private +*/ +keyFrame.reframe = function (charc) { + charc.currentIndex++; + if (charc.currentIndex < charc.keyTime.length) { + this.keyFraming(charc); + charc.start(); + } else { + //Tigerring callback function at end of animation + charc.keyframeCallback && charc.keyframeCallback(this); + } +}; + +},{'../kind':'enyo/kind','./Core':'enyo/AnimationSupport/Core','../utils':'enyo/utils','../CoreObject':'enyo/CoreObject'}],'enyo/Component':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Component~Component} kind. +* @module enyo/Component +*/ + +var + kind = require('./kind'), + utils = require('./utils'), + logger = require('./logger'); + +var + CoreObject = require('./CoreObject'), + ApplicationSupport = require('./ApplicationSupport'), + ComponentBindingSupport = require('./ComponentBindingSupport'), + Jobs = require('./jobs'); + +var + kindPrefix = {}, + unnamedCounter = 0; + +/** +* @callback module:enyo/Component~Component~EventHandler +* @param {module:enyo/Component~Component} sender - The [component]{@link module:enyo/Component~Component} that most recently +* propagated the {@glossary event}. +* @param {Object} event - An [object]{@glossary Object} containing +* event information. +* @returns {Boolean} A value indicating whether the event has been +* handled or not. If `true`, then bubbling is stopped. +*/ + +/** +* A [hash]{@glossary Object} of references to all the [components]{@link module:enyo/Component~Component} +* owned by this component. This property is updated whenever a new +* component is added; the new component may be accessed via its +* [name]{@link module:enyo/Component~Component#name} property. We may also observe changes on +* properties of components referenced by the `$` property. +* +* Component access via the `$` hash: +* ```javascript +* var Component = require('enyo/Component'); +* var c = new Component({ +* name: 'me', +* components: [ +* {kind: 'Component', name: 'other'} +* ] +* }); +* +* // We can now access 'other' on the $ hash of 'c', via c.$.other +* ``` +* +* Observing changes on a component referenced by the `$` property: +* ```javascript +* var c = new Component({ +* name: 'me', +* components: [ +* {kind: 'Component', name: 'other'} +* ] +* }); +* +* c.addObserver('$.other.active', function() { +* // do something to respond to the "active" property of "other" changing +* }) +* +* c.$.other.set('active', true); // this will trigger the observer to run its callback +* ``` +* +* @name $ +* @type {Object} +* @default null +* @memberof module:enyo/Component~Component.prototype +* @readonly +* @public +*/ + +/** +* If `true`, this [component's]{@link module:enyo/Component~Component} [owner]{@link module:enyo/Component~Component#owner} will +* have a direct name reference to the owned component. +* +* @example +* var Component = require('enyo/Component'); +* var c = new Component({ +* name: 'me', +* components: [ +* {kind: 'Component', name: 'other', publish: true} +* ] +* }); +* +* // We can now access 'other' directly, via c.other +* +* @name publish +* @type {Boolean} +* @default undefined +* @memberOf module:enyo/Component~Component.prototype +* @public +*/ + +/** +* If `true`, the [layout]{@glossary layout} strategy will adjust the size of this +* [component]{@link module:enyo/Component~Component} to occupy the remaining available space. +* +* @name fit +* @type {Boolean} +* @default undefined +* @memberOf module:enyo/Component~Component.prototype +* @public +*/ + +/** +* {@link module:enyo/Component~Component} is the fundamental building block for Enyo applications. +* Components are designed to fit together, allowing complex behaviors to +* be fashioned from smaller bits of functionality. +* +* Component [constructors]{@glossary constructor} take a single +* argument (sometimes called a [component configuration]{@glossary configurationBlock}), +* a JavaScript [object]{@glossary Object} that defines various properties to be initialized on the +* component. For example: +* +* ```javascript +* // create a new component, initialize its name property to 'me' +* var Component = require('enyo/Component'); +* var c = new Component({ +* name: 'me' +* }); +* ``` +* +* When a component is instantiated, items configured in its +* `components` property are instantiated, too: +* +* ```javascript +* // create a new component, which itself has a component +* var c = new Component({ +* name: 'me', +* components: [ +* {kind: Component, name: 'other'} +* ] +* }); +* ``` +* +* In this case, when `me` is created, `other` is also created, and we say that `me` owns `other`. +* In other words, the [owner]{@link module:enyo/Component~Component#owner} property of `other` equals `me`. +* Notice that you can specify the [kind]{@glossary kind} of `other` explicitly in its +* configuration block, to tell `me` what constructor to use to create `other`. +* +* To move a component, use the `setOwner()` method to change the +* component's owner. If you want a component to be unowned, use `setOwner(null)`. +* +* If you make changes to `Component`, be sure to add or update the appropriate +* {@linkplain https://github.com/enyojs/enyo/tree/master/tools/test/core/tests unit tests}. +* +* For more information, see the documentation on +* [Components]{@linkplain $dev-guide/key-concepts/components.html} in the +* Enyo Developer Guide. +* +* @class Component +* @extends module:enyo/CoreObject~Object +* @mixes module:enyo/ApplicationSupport~ApplicationSupport +* @mixes module:enyo/ComponentBindingSupport~ComponentBindingSupport +* @public +*/ +var Component = module.exports = kind( + /** @lends module:enyo/Component~Component.prototype */ { + + name: 'enyo.Component', + + /** + * @private + */ + kind: CoreObject, + + /** + * @private + */ + + + /** + * @private + */ + cachedBubble: true, + + /** + * @private + */ + cachePoint: false, + + /** + * @private + */ + published: + /** @lends module:enyo/Component~Component.prototype */ { + + /** + * A unique name for the [component]{@link module:enyo/Component~Component} within its + * [owner]{@link module:enyo/Component~Component#owner}. This is used to set the access name in the + * owner's [$ hash]{@link module:enyo/Component~Component#$}. If not + * specified, a default name will be provided based on the name of the + * [object's]{@link module:enyo/CoreObject~Object} [kind]{@glossary kind}, with a numeric + * suffix appended if more than one instance exists in the owner. + * + * @type {String} + * @default '' + * @public + */ + name: '', + + /** + * A unique id for the [component]{@link module:enyo/Component~Component}, usually automatically generated + * based on its position within the component hierarchy, although + * it may also be directly specified. {@link module:enyo/Control~Control} uses this `id` value for the + * DOM [id]{@link module:enyo/Control~Control#id} attribute. + * + * @type {String} + * @default '' + * @public + */ + id: '', + + /** + * The [component]{@link module:enyo/Component~Component} that owns this component. + * It is usually defined implicitly at creation time based on the + * [createComponent()]{@link module:enyo/Component~Component#createComponent} call or + * the `components` hash. + * + * @type {module:enyo/Component~Component} + * @default null + * @public + */ + owner: null, + + /** + * This can be a [hash]{@glossary Object} of features to apply to + * [chrome]{@glossary chrome} [components]{@link module:enyo/Component~Component} of the base + * [kind]{@glossary kind}. They are matched by [name]{@link module:enyo/Component~Component#name} + * (if the component you wish to modify does not have a name, this will not work). + * You can modify any properties of the component except for methods. Setting a + * value for `componentOverrides` at runtime will have no effect. + * + * @type {Object} + * @default null + * @public + */ + componentOverrides: null + }, + + /** + * @private + */ + handlers: {}, + + /** + * @private + */ + mixins: [ApplicationSupport, ComponentBindingSupport], + + /** + * @private + */ + toString: function () { + return this.id + ' [' + this.kindName + ']'; + }, + + /** + * @method + * @private + */ + constructor: kind.inherit(function (sup) { + return function (props) { + // initialize instance objects + this._componentNameMap = {}; + this.$ = {}; + this.cachedBubbleTarget = {}; + sup.apply(this, arguments); + }; + }), + + /** + * @method + * @private + */ + constructed: kind.inherit(function (sup) { + return function (props) { + // perform initialization + this.create(props); + sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + create: function () { + // stop and queue all of the notifications happening synchronously to allow + // responders to only do single passes on work traversing the tree + this.stopNotifications(); + this.ownerChanged(); + this.initComponents(); + // release the kraken! + this.startNotifications(); + }, + + /** + * @private + */ + initComponents: function () { + // The _components_ property in kind declarations is renamed to + // _kindComponents_ by the Component subclass mechanism. This makes it + // easy for the developer to distinguish kindComponents from the components + // in _this.components_, without having to worry about the actual difference. + // + // Specifically, the difference is that kindComponents are constructed as + // owned by this control (whereas components in _this.components_ are not). + // In addition, kindComponents are marked with the _isChrome: true_ flag. + this.createChrome(this.kindComponents); + this.createClientComponents(this.components); + }, + + /** + * @private + */ + createChrome: function (comps) { + this.createComponents(comps, {isChrome: true}); + }, + + /** + * @private + */ + createClientComponents: function (comps) { + this.createComponents(comps, {owner: this.getInstanceOwner()}); + }, + + /** + * @private + */ + getInstanceOwner: function () { + return (!this.owner || this.owner.notInstanceOwner) ? this : this.owner; + }, + + /** + * Removes this [component]{@link module:enyo/Component~Component} from its + * [owner]{@link module:enyo/Component~Component#owner} (setting `owner` to `null`) + * and does any necessary cleanup. The component is flagged with + * `destroyed: true`. Usually, the component will be suitable for garbage + * collection after being destroyed, unless user code keeps a reference + * to it. + * + * @returns {this} The callee for chaining. + * @method + * @public + */ + destroy: kind.inherit(function (sup) { + return function () { + this.destroyComponents(); + this.setOwner(null); + sup.apply(this, arguments); + this.stopAllJobs(); + return this; + }; + }), + + /** + * Destroys all owned [components]{@link module:enyo/Component~Component}. + * + * @returns {this} The callee for chaining. + * @public + */ + destroyComponents: function () { + var comps = this.getComponents(), + comp, + i; + + for (i = 0; i < comps.length; ++i) { + comp = comps[i]; + // @todo: previous comment said list might be stale and ownership may have caused + // components to be destroyed as a result of some inner-container...look into this + // because that seems incorrect or avoidable + if (!comp.destroyed) comp.destroy(); + } + + return this; + }, + + /** + * @private + */ + makeId: function() { + var delim = '_', pre = this.owner && this.owner.getId(), + baseName = this.name || ('@@' + (++unnamedCounter)); + return (pre ? pre + delim : '') + baseName; + }, + + /** + * @private + */ + ownerChanged: function (was) { + if (was && was.removeComponent) was.removeComponent(this); + if (this.owner && this.owner.addComponent) this.owner.addComponent(this); + if (!this.id) this.id = this.makeId(); + }, + + /** + * @private + */ + nameComponent: function (comp) { + var pre = prefixFromKindName(comp.kindName), + last = this._componentNameMap[pre] || 0, + nom; + + do { + nom = pre + (++last > 1 ? String(last) : ''); + } while (this.$[nom]); + + this._componentNameMap[pre] = Number(last); + /*jshint -W093 */ + return (comp.name = nom); + }, + + /** + * Adds a [component]{@link module:enyo/Component~Component} to the list of components + * owned by the current component (i.e., [this.$]{@link module:enyo/Component~Component#$}). + * + * @param {module:enyo/Component~Component} comp - The [component]{@link module:enyo/Component~Component} to add. + * @returns {this} The callee for chaining. + * @public + */ + addComponent: function (comp) { + var nom = comp.get('name'); + + // if there is no name we have to come up with a generic name + if (!nom) nom = this.nameComponent(comp); + + // if there already was a component by that name we issue a warning + // @todo: if we're going to name rules being violated we need to normalize this approach + // and ensure we have one for every warning/error we throw + if (this.$[nom]) this.warn( + 'Duplicate component name ' + nom + ' in owner ' + this.id + ' violates ' + + 'unique-name-under-owner rule, replacing existing component in the hash and ' + + 'continuing, but this is an error condition and should be fixed.' + ); + + this.$[nom] = comp; + this.notify('$.' + nom, null, comp); + + // if the component has the `publish` true property then we also create a reference to + // it directly on the owner (this) + if (comp.publish) { + this[nom] = comp; + + // and to ensure that bindings are aware we have to notify them as well + this.notify(nom, null, comp); + } + + return this; + }, + + /** + * Removes the passed-in [component]{@link module:enyo/Component~Component} from those known + * to be owned by this component. The component will be removed from the + * [$ hash]{@link module:enyo/Component~Component#$}, and from the [owner]{@link module:enyo/Component~Component#owner} + * directly if [publish]{@link module:enyo/Component~Component#publish} is set to `true`. + * + * @param {module:enyo/Component~Component} comp - The component to remove. + * @returns {this} The callee for chaining. + * @public + */ + removeComponent: function (comp) { + var nom = comp.get('name'); + + // remove it from the hash if it existed + delete this.$[nom]; + + // if it was published remove it from the component proper + if (comp.publish) delete this[nom]; + + return this; + }, + + /** + * Returns an [array]{@glossary Array} of owned [components]{@link module:enyo/Component~Component}; in + * other words, converts the [$ hash]{@link module:enyo/Component~Component#$} into an array + * and returns the array. + * + * @returns {module:enyo/Component~Component[]} The [components]{@link module:enyo/Component~Component} found in the + * [$ hash]{@link module:enyo/Component~Component#$}. + * @public + */ + getComponents: function () { + return utils.values(this.$); + }, + + /** + * @private + */ + adjustComponentProps: function (props) { + if (this.defaultProps) utils.mixin(props, this.defaultProps, {ignore: true}); + props.kind = props.kind || props.isa || this.defaultKind; + props.owner = props.owner || this; + }, + + /** + * @private + */ + _createComponent: function (props, ext) { + var def = ext ? utils.mixin({}, [ext, props]) : utils.clone(props); + + // always adjust the properties according to the needs of the kind and parent kinds + this.adjustComponentProps(def); + + // pass along for the final stage + return Component.create(def); + }, + + /** + * Creates and returns a [component]{@link module:enyo/Component~Component} as defined by the combination of + * a base and an additional property [hash]{@glossary Object}. The properties provided + * in the standard property hash override those provided in the + * additional property hash. + * + * The created component passes through initialization machinery + * provided by the creating component, which may supply special + * handling. Unless the [owner]{@link module:enyo/Component~Component#owner} is explicitly specified, the new + * component will be owned by the instance on which this method is called. + * + * @example + * // Create a new component named 'dynamic', owned by 'this' + * // (will be available as this.$.dynamic). + * this.createComponent({name: 'dynamic'}); + * + * @example + * // Create a new component named 'another' owned by 'other' + * // (will be available as other.$.another). + * this.createComponent({name: 'another'}, {owner: other}); + * + * @param {Object} props - The declarative [kind]{@glossary kind} definition. + * @param {Object} ext - Additional properties to be applied (defaults). + * @returns {module:enyo/Component~Component} The instance created with the given parameters. + * @public + */ + createComponent: function (props, ext) { + // createComponent and createComponents both delegate to the protected method + // (_createComponent), allowing overrides to customize createComponent and + // createComponents separately. + return this._createComponent(props, ext); + }, + + /** + * Creates [components]{@link module:enyo/Component~Component} as defined by the [arrays]{@glossary Array} + * of base and additional property [hashes]{@glossary Object}. The standard and + * additional property hashes are combined as described in + * [createComponent()]{@link module:enyo/Component~Component#createComponent}. + * + * @example + * // ask foo to create components 'bar' and 'zot', but set the owner of + * // both components to 'this'. + * this.$.foo.createComponents([ + * {name: 'bar'}, + * {name: 'zot'} + * ], {owner: this}); + * + * @param {Object[]} props The array of {@link module:enyo/Component~Component} definitions to be created. + * @param {Object} ext - Additional properties to be supplied as defaults for each. + * @returns {module:enyo/Component~Component[]} The array of [components]{@link module:enyo/Component~Component} that were + * created. + * @public + */ + createComponents: function (props, ext) { + var comps = [], + comp, + i; + + if (props) { + for (i = 0; i < props.length; ++i) { + comp = props[i]; + comps.push(this._createComponent(comp, ext)); + } + } + + return comps; + }, + + /** + * @private + */ + getBubbleTarget: function (nom, event) { + if (event.delegate) return this.owner; + else { + return ( + this.bubbleTarget + || (this.cachedBubble && this.cachedBubbleTarget[nom]) + || this.owner + ); + } + }, + + /** + * Bubbles an {@glossary event} up an [object]{@glossary Object} chain, + * starting with `this`. + * + * A handler for an event may be specified. See {@link module:enyo/Component~Component~EventHandler} + * for complete details. + * + * @param {String} nom - The name of the {@glossary event} to bubble. + * @param {Object} [event] - The event [object]{@glossary Object} to be passed along + * while bubbling. + * @param {module:enyo/Component~Component} [sender=this] - The {@link module:enyo/Component~Component} responsible for + * bubbling the event. + * @returns {Boolean} `false` if unhandled or uninterrupted; otherwise, `true`. + * @public + */ + bubble: function (nom, event, sender) { + if (!this._silenced) { + event = event || {}; + event.lastHandledComponent = null; + event.bubbling = true; + // deliberately done this way + if (event.originator == null) event.originator = sender || this; + return this.dispatchBubble(nom, event, sender || this); + } + return false; + }, + + /** + * Bubbles an {@glossary event} up an [object]{@glossary Object} chain, + * starting **above** `this`. + * + * A handler for an event may be specified. See {@link module:enyo/Component~Component~EventHandler} + * for complete details. + * + * @param {String} nom - The name of the {@glossary event}. + * @param {Object} [event] - The event properties to pass along while bubbling. + * @returns {Boolean} `false` if unhandled or uninterrupted; otherwise, `true`. + * @public + */ + bubbleUp: function (nom, event) { + var next; + + if (!this._silenced) { + event = event || {}; + event.bubbling = true; + next = this.getBubbleTarget(nom, event); + if (next) { + // use delegate as sender if it exists to preserve illusion + // that event is dispatched directly from that, but we still + // have to bubble to get decorations + return next.dispatchBubble(nom, event, event.delegate || this); + } + } + return false; + }, + + /** + * Sends an {@glossary event} to a named [delegate]{@glossary delegate}. + * This [object]{@glossary Object} may dispatch an event to + * itself via a [handler]{@link module:enyo/Component~Component~EventHandler}, or to its + * [owner]{@link module:enyo/Component~Component#owner} via an event property, e.g.: + * + * handlers { + * // 'tap' events dispatched to this.tapHandler + * ontap: 'tapHandler' + * } + * + * // 'tap' events dispatched to 'tapHandler' delegate in this.owner + * ontap: 'tapHandler' + * + * @private + */ + dispatchEvent: function (nom, event, sender) { + var delegate, + ret; + + if (!this._silenced) { + // if the event has a delegate associated with it we grab that + // for reference + // NOTE: This is unfortunate but we can't use a pooled object here because + // we don't know where to release it + delegate = (event || (event = {})).delegate; + + // bottleneck event decoration w/ optimization to avoid call to empty function + if (this.decorateEvent !== Component.prototype.decorateEvent) { + this.decorateEvent(nom, event, sender); + } + + // first, handle any delegated events intended for this object + if (delegate && delegate.owner === this) { + // the most likely case is that we have a method to handle this + if (this[nom] && 'function' === typeof this[nom]) { + return this.dispatch(nom, event, sender); + } + // but if we don't, just stop the event from going further + return false; + } + + // for non-delgated events, try the handlers block if possible + if (!delegate) { + var bHandler = this.handlers && this.handlers[nom]; + var bDelegatedFunction = this[nom] && utils.isString(this[nom]); + var cachePoint = this.cachePoint || bHandler || bDelegatedFunction || this.id === "master" ; + + if (event.bubbling) { + if (event.lastHandledComponent && cachePoint) { + event.lastHandledComponent.cachedBubbleTarget[nom] = this; + event.lastHandledComponent = null; + } + if (!event.lastHandledComponent && this.id !== "master") { + event.lastHandledComponent = this; + } + } + if (bHandler && this.dispatch(bHandler, event, sender)) { + return true; + } + if (bDelegatedFunction) { + // we dispatch it up as a special delegate event with the + // component that had the delegation string property stored in + // the 'delegate' property + event.delegate = this; + ret = this.bubbleUp(this[nom], event, sender); + delete event.delegate; + return ret; + } + } + } + return false; + }, + + /** + * Internal - try dispatching {@glossary event} to self; if that fails, + * [bubble it up]{@link module:enyo/Component~Component#bubbleUp} the tree. + * + * @private + */ + dispatchBubble: function (nom, event, sender) { + if (!this._silenced) { + // Try to dispatch from here, stop bubbling on truthy return value + if (this.dispatchEvent(nom, event, sender)) { + return true; + } + // Bubble to next target + return this.bubbleUp(nom, event, sender); + } + return false; + }, + + /** + * @private + */ + decorateEvent: function (nom, event, sender) { + // an event may float by us as part of a dispatchEvent chain + // both call this method so intermediaries can decorate inEvent + }, + + /** + * @private + */ + stopAllJobs: function () { + var job; + + if (this.__jobs) for (job in this.__jobs) this.stopJob(job); + }, + + /** + * Dispatches the {@glossary event} to named [delegate]{@glossary delegate} `nom`, + * if it exists. [Subkinds]{@glossary subkind} may re-route dispatches. Note that + * both 'handlers' events and events delegated from owned controls arrive here. + * If you need to handle these types of events differently, you may also need to + * override [dispatchEvent()]{@link module:enyo/Component~Component#dispatchEvent}. + * + * @param {String} nom - The method name to dispatch the {@glossary event}. + * @param {Object} [event] - The event [object]{@glossary Object} to pass along. + * @param {module:enyo/Component~Component} [sender=this] - The originator of the event. + * @public + */ + dispatch: function (nom, event, sender) { + var fn; + + if (!this._silenced) { + fn = nom && this[nom]; + if (fn && typeof fn == 'function') { + // @todo: deprecate sender + return fn.call(this, sender || this, event); + } + } + return false; + }, + + /** + * Triggers the [handler]{@link module:enyo/Component~Component~EventHandler} for a given + * {@glossary event} type. + * + * @example + * myControl.triggerHandler('ontap'); + * + * @param {String} nom - The name of the {@glossary event} to trigger. + * @param {Object} [event] - The event object to pass along. + * @param {module:enyo/Component~Component} [sender=this] - The originator of the event. + * @returns {Boolean} `false` if unhandled or uninterrupted, `true` otherwise. + * @public + */ + triggerHandler: function () { + return this.dispatchEvent.apply(this, arguments); + }, + + /** + * Sends a message to myself and all of my [components]{@link module:enyo/Component~Component}. + * You can stop a waterfall into components owned by a receiving object + * by returning a truthy value from the {@glossary event} + * [handler]{@link module:enyo/Component~Component~EventHandler}. + * + * @param {String} nom - The name of the {@glossary event} to waterfall. + * @param {Object} [event] - The event [object]{@glossary Object} to pass along. + * @param {module:enyo/Component~Component} [sender=this] - The originator of the event. + * @returns {this} The callee for chaining. + * @public + */ + waterfall: function(nom, event, sender) { + if (!this._silenced) { + event = event || {}; + event.bubbling = false; + + // give the locals an opportunity to interrupt the event + if (this.dispatchEvent(nom, event, sender)) return true; + + // otherwise carry on + this.waterfallDown(nom, event, sender || this); + } + + return this; + }, + + /** + * Sends a message to all of my [components]{@link module:enyo/Component~Component}, but not myself. You can + * stop a [waterfall]{@link module:enyo/Component~Component#waterfall} into [components]{@link module:enyo/Component~Component} + * owned by a receiving [object]{@glossary Object} by returning a truthy value from the + * {@glossary event} [handler]{@link module:enyo/Component~Component~EventHandler}. + * + * @param {String} nom - The name of the {@glossary event}. + * @param {Object} [event] - The event [object]{@glossary Object} to pass along. + * @param {module:enyo/Component~Component} [sender=this] - The event originator. + * @returns {this} The callee for chaining. + * @public + */ + waterfallDown: function(nom, event, sender) { + var comp; + event = event || {}; + event.bubbling = false; + + if (!this._silenced) { + for (comp in this.$) this.$[comp].waterfall(nom, event, sender || this); + } + + return this; + }, + + /** + * @private + */ + _silenced: false, + + /** + * @private + */ + _silenceCount: 0, + + /** + * Sets a flag that disables {@glossary event} propagation for this + * [component]{@link module:enyo/Component~Component}. Also increments an internal counter that tracks + * the number of times the [unsilence()]{@link module:enyo/Component~Component#unsilence} method must + * be called before event propagation will continue. + * + * @returns {this} The callee for chaining. + * @public + */ + silence: function () { + this._silenced = true; + this._silenceCount += 1; + + return this; + }, + + /** + * Determines if the [object]{@glossary Object} is currently + * [silenced]{@link module:enyo/Component~Component#_silenced}, which will prevent propagation of + * [events]{@glossary event} (of any kind). + * + * @returns {Boolean} `true` if silenced; otherwise, `false`. + * @public + */ + isSilenced: function () { + return this._silenced; + }, + + /** + * Allows {@glossary event} propagation for this [component]{@link module:enyo/Component~Component} + * if the internal silence counter is `0`; otherwise, decrements the counter by one. + * For event propagation to resume, this method must be called one time each call to + * [silence()]{@link module:enyo/Component~Component#silence}. + * + * @returns {Boolean} `true` if the {@link module:enyo/Component~Component} is now unsilenced completely; + * `false` if it remains silenced. + * @public + */ + unsilence: function () { + if (0 !== this._silenceCount) --this._silenceCount; + if (0 === this._silenceCount) this._silenced = false; + return !this._silenced; + }, + + /** + * Creates a new [job]{@link module:enyo/job} tied to this instance of the + * [component]{@link module:enyo/Component~Component}. If the component is + * [destroyed]{@link module:enyo/Component~Component#destroy}, any jobs associated with it + * will be stopped. + * + * If you start a job with the same name as a pending job, + * the original job will be stopped; this can be useful for resetting + * timeouts. + * + * You may supply a priority level (1-10) at which the job should be + * executed. The default level is `5`. Setting the priority lower than `5` (or setting it to + * the string `"low"`) will defer the job if an animation is in progress, + * which can help to avoid stuttering. + * + * @param {String} nom - The name of the [job]{@link module:enyo/job} to start. + * @param {(Function|String)} job - Either the name of a method or a + * [function]{@glossary Function} to execute as the requested job. + * @param {Number} wait - The number of milliseconds to wait before starting + * the job. + * @param {Number} [priority=5] The priority value to be associated with this + * job. + * @returns {this} The callee for chaining. + * @public + */ + startJob: function (nom, job, wait, priority) { + var jobs = (this.__jobs = this.__jobs || {}); + priority = priority || 5; + // allow strings as job names, they map to local method names + if (typeof job == 'string') job = this[job]; + // stop any existing jobs with same name + this.stopJob(nom); + jobs[nom] = setTimeout(this.bindSafely(function() { + Jobs.add(this.bindSafely(job), priority, nom); + }), wait); + + return this; + }, + + /** + * Stops a [component]{@link module:enyo/Component~Component}-specific [job]{@link module:enyo/job} before it has + * been activated. + * + * @param {String} nom - The name of the [job]{@link module:enyo/job} to be stopped. + * @returns {this} The callee for chaining. + * @public + */ + stopJob: function (nom) { + var jobs = (this.__jobs = this.__jobs || {}); + if (jobs[nom]) { + clearTimeout(jobs[nom]); + delete jobs[nom]; + } + Jobs.remove(nom); + }, + + /** + * Executes the specified [job]{@link module:enyo/job} immediately, then prevents + * any other calls to `throttleJob()` with the same job name from running for + * the specified amount of time. + * + * @param {String} nom - The name of the [job]{@link module:enyo/job} to throttle. + * @param {(Function|String)} job - Either the name of a method or a + * [function]{@glossary Function} to execute as the requested job. + * @param {Number} wait - The number of milliseconds to wait before executing the + * job again. + * @returns {this} The callee for chaining. + * @public + */ + throttleJob: function (nom, job, wait) { + var jobs = (this.__jobs = this.__jobs || {}); + // if we still have a job with this name pending, return immediately + if (!jobs[nom]) { + // allow strings as job names, they map to local method names + if (typeof job == 'string') job = this[job]; + job.call(this); + jobs[nom] = setTimeout(this.bindSafely(function() { + this.stopJob(nom); + }), wait); + } + return this; + } +}); + +Component.prototype.defaultKind = Component; + +/** +* @private +*/ +kind.setDefaultCtor(Component); + +/** +* Creates new instances from [config]{@glossary configurationBlock} +* [objects]{@glossary Object}. This method looks up the proper +* [constructor]{@glossary constructor} based on the provided [kind]{@glossary kind} +* attribute. +* +* @name module:enyo/Compoment~Component.create +* @param {Object} props - The properties that define the [kind]{@glossary kind}. +* @returns {*} An instance of the requested [kind]{@glossary kind}. +* @public +*/ +Component.create = function (props) { + var theKind, + Ctor; + + if (!props.kind && props.hasOwnProperty('kind')) throw new Error( + 'enyo.create: Attempt to create a null kind. Check dependencies for [' + props.name + ']' + ); + + theKind = props.kind || props.isa || kind.getDefaultCtor(); + Ctor = kind.constructorForKind(theKind); + + if (!Ctor) { + logger.error('No constructor found for kind ' + theKind); + Ctor = Component; + } + + return new Ctor(props); +}; + +/** +* @name module:enyo/Component~Component.subclass +* @static +* @private +*/ +Component.subclass = function (ctor, props) { + // Note: To reduce API surface area, sub-components are declared only as + // 'components' in both kind and instance declarations. + // + // However, 'components' from kind declarations must be handled separately + // at creation time. + // + // We rename the property here to avoid having + // to interrogate the prototype at creation time. + // + var proto = ctor.prototype; + // + if (props.components) { + proto.kindComponents = props.components; + delete proto.components; + } else { + // Feature to mixin overrides of super-kind component properties from named hash + // (only applied when the sub-kind doesn't supply its own components block) + if (props.componentOverrides) { + proto.kindComponents = Component.overrideComponents( + proto.kindComponents, + props.componentOverrides, + proto.defaultKind + ); + } + } +}; + +/** +* @name module:enyo/Component~Component.concat +* @static +* @private +*/ +Component.concat = function (ctor, props) { + var proto = ctor.prototype || ctor, + handlers; + if (props.handlers) { + handlers = proto.handlers ? utils.clone(proto.handlers) : {}; + proto.handlers = utils.mixin(handlers, props.handlers); + delete props.handlers; + } + if (props.events) Component.publishEvents(proto, props); +}; + +/** +* @name module:enyo/Component~Component.overrideComponents +* @static +* @private +*/ +Component.overrideComponents = function (components, overrides, defaultKind) { + var omitMethods = function (k, v) { + var isMethod = + // If it's a function, then it's a method (unless it's + // a constructor passed as value for 'kind') + (utils.isFunction(v) && (k !== 'kind')) || + // If it isInherited(), then it's also a method (since + // Inherited is an object wrapper for a function) + kind.isInherited(v); + + return !isMethod; + }; + components = utils.clone(components); + for (var i=0; i= 0) ? nom.slice(last+1) : nom; + pre = pre.charAt(0).toLowerCase() + pre.slice(1); + kindPrefix[nom] = pre; + } + + return pre; +} + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./logger':'enyo/logger','./CoreObject':'enyo/CoreObject','./ApplicationSupport':'enyo/ApplicationSupport','./ComponentBindingSupport':'enyo/ComponentBindingSupport','./jobs':'enyo/jobs'}],'enyo/Model':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Model~Model} kind. +* @module enyo/Model +*/ + +var + kind = require('./kind'), + utils = require('./utils'); + +var + ObserverSupport = require('./ObserverSupport'), + ComputedSupport = require('./ComputedSupport'), + BindingSupport = require('./BindingSupport'), + EventEmitter = require('./EventEmitter'), + StateSupport = require('./StateSupport'), + ModelList = require('./ModelList'), + Source = require('./Source'), + States = require('./States'), + Store = require('./Store'); + +/** +* This is only necessary because of the order in which mixins are applied. +* +* @class +* @private +*/ +var BaseModel = kind({ + kind: null, + mixins: [ObserverSupport, ComputedSupport, BindingSupport, EventEmitter, StateSupport] +}); + +/** +* The event emitted when [attributes]{@link module:enyo/Model~Model#attributes} have been modified. +* The event [object]{@glossary Object} will consist of key/value pairs of attributes +* that changed and their new values. +* +* @event module:enyo/Model~Model#change +* @type {Object} +* @public +*/ + +/** +* The default configurable [options]{@link module:enyo/Model~Model#options} used in certain API methods +* of {@link module:enyo/Model~Model}. +* +* @typedef {Object} module:enyo/Model~Model~Options +* @property {Boolean} silent=false - Keep events and notifications from being emitted. +* @property {Boolean} commit=false - Immediately [commit]{@link module:enyo/Model~Model#commit} changes +* after they have occurred. Also note that, if `true`, when the [model]{@link module:enyo/Model~Model} +* is [destroyed]{@link module:enyo/Model~Model#destroy}, it will also be destroyed via any +* [sources]{@link module:enyo/Model~Model#source} it has. +* @property {Boolean} parse=false - During initialization, [parse]{@link module:enyo/Model~Model#parse} +* any given [attributes]{@link module:enyo/Model~Model#attributes}; after +* [fetching]{@link module:enyo/Model~Model#fetch}, parse the data before calling +* [set()]{@link module:enyo/Model~Model#set}. +* @property {Boolean} fetch=false - Automatically call [fetch()]{@link module:enyo/Model~Model#fetch} +* during initialization. +*/ + +/** +* The configurable options for [fetch()]{@link module:enyo/Model~Model#fetch}, +* [commit()]{@link module:enyo/Model~Model#commit}, and [destroy()]{@link module:enyo/Model~Model#destroy}. +* +* @typedef {module:enyo/Model~Model~Options} module:enyo/Model~Model~ActionOptions +* @property {module:enyo/Model~Model~Success} success - The callback executed upon successful +* completion. +* @property {module:enyo/Model~Model~Error} error - The callback executed upon a failed attempt. +*/ + +/** +* @callback module:enyo/Model~Model~Success +* @param {module:enyo/Model~Model} model - The [model]{@link module:enyo/Model~Model} that is returning successfully. +* @param {module:enyo/Model~Model~ActionOptions} opts - The original options passed to the action method +* that is returning successfully. +* @param {*} res - The result, if any, returned by the [source]{@link module:enyo/Source~Source} that +* executed it. +* @param {String} source - The name of the [source]{@link module:enyo/Model~Model#source} that has +* returned successfully. +*/ + +/** +* @callback module:enyo/Model~Model~Error +* @param {module:enyo/Model~Model} model - The model that is returning an error. +* @param {String} action - The name of the action that failed, one of `'FETCHING'`, +* `'COMMITTING'`, or `'DESTROYING'`. +* @param {module:enyo/Model~Model~Options} opts - The original options passed to the action method +* that is returning an error. +* @param {*} res - The result, if any, returned by the [source]{@link module:enyo/Source~Source} that +* executed it. +* @param {String} source - The name of the [source]{@link module:enyo/Model~Model#source} that has +* returned an error. +*/ + +/** +* An [object]{@glossary Object} used to represent and maintain state. Usually, +* an {@link module:enyo/Model~Model} is used to expose data to the view layer. It keeps logic +* related to the data (retrieving it, updating it, storing it, etc.) out of the +* view, and the view can automatically update based on changes in the model. +* Models have the ability to work with other data layer [kinds]{@glossary kind} +* to provide more sophisticated implementations. +* +* Models have [bindable]{@link module:enyo/BindingSupport~BindingSupport} +* [attributes]{@link module:enyo/Model~Model#attributes}. Models differs from other +* bindable kinds in that attribute values are proxied from an internal +* [hash]{@glossary Object} instead of being set on the target properties +* directly. +* +* @see module:enyo/Store~Store +* @see module:enyo/Collection~Collection +* @see module:enyo/RelationalModel~RelationalModel +* @see module:enyo/ModelController~ModelController +* @class Model +* @mixes module:enyo/ObserverSupport~ObserverSupport +* @mixes module:enyo/ComputedSupport~ComputedSupport +* @mixes module:enyo/BindingSupport~BindingSupport +* @mixes module:enyo/EventEmitter +* @mixes module:enyo/StateSupport~StateSupport +* @public +*/ +var Model = module.exports = kind( + /** @lends module:enyo/Model~Model.prototype */ { + + name: 'enyo.Model', + + /** + * @private + */ + kind: BaseModel, + + /** + * @private + */ + + + /** + * Used by various [sources]{@link module:enyo/Model~Model#source} as part of the + * [URI]{@glossary URI} from which they may be [fetched]{@link module:enyo/Model~Model#fetch}, + * [committed]{@link module:enyo/Model~Model#commit}, or [destroyed]{@link module:enyo/Model~Model#destroy}. + * Some sources may use this property in other ways. + * + * @see module:enyo/Model~Model#getUrl + * @see module:enyo/Source~Source + * @see module:enyo/AjaxSource~AjaxSource + * @see module:enyo/JsonpSource~JsonpSource + * @type {String} + * @default '' + * @public + */ + url: '', + + /** + * Implement this method to be used by [sources]{@link module:enyo/Model~Model#source} to + * dynamically derive the [URI]{@glossary URI} from which they may be + * [fetched]{@link module:enyo/Model~Model#fetch}, [committed]{@link module:enyo/Model~Model#commit}, + * or [destroyed]{@link module:enyo/Model~Model#destroy}. Some sources may use this + * property in other ways. Note that, if this method is implemented, the + * [url]{@link module:enyo/Model~Model#url} will not be used. + * + * @see module:enyo/Model~Model#url + * @see module:enyo/Source~Source + * @see module:enyo/AjaxSource~AjaxSource + * @see module:enyo/JsonpSource~JsonpSource + * @type {Function} + * @default null + * @virtual + * @public + */ + getUrl: null, + + /** + * The [hash]{@glossary Object} of properties proxied by this [model]{@link module:enyo/Model~Model}. + * If defined on a [subkind]{@glossary subkind}, it may be assigned default values and + * all instances will share its default structure. If no attributes are defined, an + * empty [hash]{@glossary Object} will be assigned during initialization. It is not + * necessary to pre-define the structure of a model; depending on the model's complexity, + * pre-defining the structure may possibly hinder performance. + * + * It should also be noted that calls to [get()]{@link module:enyo/Model~Model#get} or + * [set()]{@link module:enyo/Model~Model#set} will access and modify this property. This includes + * the values to which (or from which) [bindings]{@link module:enyo/BindingSupport~BindingSupport} are bound. + * + * @type {Object} + * @default null + * @public + */ + attributes: null, + + /** + * The [source(s)]{@link module:enyo/Source~Source} to use when [fetching]{@link module:enyo/Model~Model#fetch}, + * [committing]{@link module:enyo/Model~Model#commit}, or [destroying]{@link module:enyo/Model~Model#destroy}. + * Any method that uses sources may override this default value in its configuration + * options. This value may be a [string]{@glossary String}, an + * [Array]{@glossary Array} of strings, an instance of {@link module:enyo/Source~Source}, or an + * array of `enyo/Source` instances. + * + * @see module:enyo/Source~Source + * @see module:enyo/Model~Model#fetch + * @see module:enyo/Model~Model#commit + * @see module:enyo/Model~Model#destroy + * @type {(String|String[]|module:enyo/Source~Source|module:enyo/Source~Source[])} + * @default null + * @public + */ + source: null, + + /** + * These [keys]{@glossary Object.keys} will be the only + * [attributes]{@link module:enyo/Model~Model#attributes} included if the + * [model]{@link module:enyo/Model~Model} is [committed]{@link module:enyo/Model~Model#commit}. This + * directly modifies the result of calling [raw()]{@link module:enyo/Model~Model#raw}. If + * not defined, all keys from the [attributes]{@link module:enyo/Model~Model#attributes} + * [hash]{@glossary Object} will be used. + * + * @see module:enyo/Model~Model#raw + * @see module:enyo/Model~Model#toJSON + * @type {String[]} + * @default null + * @public + */ + includeKeys: null, + + /** + * The inheritable default configuration options. These specify the behavior of particular + * API features of {@link module:enyo/Model~Model}. Any method that uses these options may override + * the default values in its own configuration options. Note that setting an + * [options hash]{@glossary Object} on a [subkind]{@glossary subkind} will result in + * the new values' being merged with--not replacing--the + * [superkind's]{@glossary superkind} own `options`. + * + * @type {module:enyo/Model~Model~Options} + * @public + */ + options: { + silent: false, + commit: false, + parse: false, + fetch: false + }, + + /** + * The current [state(s)]{@link module:enyo/States} possessed by the [model]{@link module:enyo/Model~Model}. + * There are limitations as to which state(s) the model may possess at any given time. + * By default, a model is [NEW]{@link module:enyo/States#NEW} and [CLEAN]{@link module:enyo/States#CLEAN}. + * Note that this is **not** a [bindable]{@link module:enyo/BindingSupport~BindingSupport} property. + * + * @see module:enyo/States~States + * @see {@link module:enyo/StateSupport~StateSupport} + * @type {module:enyo/States~States} + * @readonly + * @public + */ + status: States.NEW | States.CLEAN, + + /** + * The unique attribute by which the [model]{@link module:enyo/Model~Model} may be indexed. The + * attribute's value must be unique across all instances of the specific model + * [kind]{@glossary kind} + * + * @type {String} + * @default 'id' + * @public + */ + primaryKey: 'id', + + /** + * Inspects and restructures incoming data prior to [setting]{@link module:enyo/Model~Model#set} it on + * the [model]{@link module:enyo/Model~Model}. While this method may be called directly, it is most + * often used via the [parse]{@link module:enyo/Model~Model~Options#parse} option and executed + * automatically, either during initialization or when [fetched]{@link module:enyo/Model~Model#fetch} + * (or, in some cases, both). This is a virtual method and must be provided to suit a + * given implementation's needs. + * + * @see module:enyo/Model~Model~Options#parse + * @param {*} data - The incoming data that may need to be restructured or reduced prior to + * being [set]{@link module:enyo/Model~Model#set} on the [model]{@link module:enyo/Model~Model}. + * @returns {Object} The [hash]{@glossary Object} to apply to the + * model via [set()]{@link module:enyo/Model~Model#set}. + * @virtual + * @public + */ + parse: function (data) { + return data; + }, + + /** + * Returns an [Object]{@glossary Object} that represents the underlying data structure + * of the [model]{@link module:enyo/Model~Model}. This is dependent on the current + * [attributes]{@link module:enyo/Model~Model#attributes} as well as the + * [includeKeys]{@link module:enyo/Model~Model#includeKeys}. + * [Computed properties]{@link module:enyo/ComputedSupport} are **never** included. + * + * @see module:enyo/Model~Model#includeKeys + * @see module:enyo/Model~Model#attributes + * @returns {Object} The formatted [hash]{@glossary Object} representing the underlying + * data structure of the [model]{@link module:enyo/Model~Model}. + * @public + */ + raw: function () { + var inc = this.includeKeys + , attrs = this.attributes + , keys = inc || Object.keys(attrs) + , cpy = inc? utils.only(inc, attrs): utils.clone(attrs); + keys.forEach(function (key) { + var ent = this.get(key); + if (typeof ent == 'function') cpy[key] = ent.call(this); + else if (ent && ent.raw) cpy[key] = ent.raw(); + else cpy[key] = ent; + }, this); + return cpy; + }, + + /** + * Returns the [JSON]{@glossary JSON} serializable [raw()]{@link module:enyo/Model~Model#raw} output + * of the [model]{@link module:enyo/Model~Model}. Will automatically be executed by + * [JSON.parse()]{@glossary JSON.parse}. + * + * @see module:enyo/Model~Model#raw + * @returns {Object} The return value of [raw()]{@link module:enyo/Model~Model#raw}. + * @public + */ + toJSON: function () { + + // @NOTE: Because this is supposed to return a JSON parse-able object + return this.raw(); + }, + + /** + * Restores an [attribute]{@link module:enyo/Model~Model#attributes} to its previous value. If no + * attribute is specified, all previous values will be restored. + * + * @see module:enyo/Model~Model#set + * @see module:enyo/Model~Model#previous + * @param {String} [prop] - The [attribute]{@link module:enyo/Model~Model#attributes} to + * [restore]{@link module:enyo/Model~Model#restore}. If not provided, all attributes will be + * restored to their previous values. + * @returns {this} The callee for chaining. + * @public + */ + restore: function (prop) { + + // we ensure that the property is forcibly notified (when possible) to ensure that + // bindings or other observers will know it returned to that value + if (prop) this.set(prop, this.previous[prop], {force: true}); + else this.set(this.previous); + + return this; + }, + + /** + * Commits the [model]{@link module:enyo/Model~Model} to a [source or sources]{@link module:enyo/Model~Model#source}. + * A model cannot be [committed]{@link module:enyo/Model~Model#commit} if it is in an + * [error]{@link module:enyo/States#ERROR} ({@link module:enyo/StateSupport~StateSupport#isError}) or + * [busy]{@link module:enyo/States#BUSY} ({@link module:enyo/StateSupport~StateSupport#isBusy}) + * [state]{@link module:enyo/Model~Model#status}. While executing, it will add the + * [COMMITTING]{@link module:enyo/States#COMMITTING} flag to the model's + * [status]{@link module:enyo/Model~Model#status}. Once it has completed execution, it will + * remove this flag (even if it fails). + * + * @see module:enyo/Model~Model#committed + * @see module:enyo/Model~Model#status + * @param {module:enyo/Model~Model~ActionOptions} [opts] - Optional configuration options. + * @returns {this} The callee for chaining. + * @public + */ + commit: function (opts) { + var options, + source, + it = this; + + // if the current status is not one of the error or busy states we can continue + if (!(this.status & (States.ERROR | States.BUSY))) { + + // if there were options passed in we copy them quickly so that we can hijack + // the success and error methods while preserving the originals to use later + options = opts ? utils.clone(opts, true) : {}; + + // make sure we keep track of how many sources we're requesting + source = options.source || this.source; + if (source && ((source instanceof Array) || source === true)) { + this._waiting = source.length ? source.slice() : Object.keys(Source.sources); + } + + options.success = function (source, res) { + it.committed(opts, res, source); + }; + + options.error = function (source, res) { + it.errored('COMMITTING', opts, res, source); + }; + + // set the state + this.status = this.status | States.COMMITTING; + + // now pass this on to the source to execute as it sees fit + Source.execute('commit', this, options); + } else this.errored(this.status, opts); + + return this; + }, + + /** + * Fetches the [model]{@link module:enyo/Model~Model} from a + * [source or sources]{@link module:enyo/Model~Model#source}. A model cannot be + * [fetched]{@link module:enyo/Model~Model#fetch} if it is in an + * [error]{@link module:enyo/States#ERROR} ({@link module:enyo/StateSupport~StateSupport#isError}) or + * [busy]{@link module:enyo/States#BUSY} ({@link module:enyo/StateSupport~StateSupport#isBusy}) + * [state]{@link module:enyo/Model~Model#status}. While executing, it will add the + * [FETCHING]{@link module:enyo/States#FETCHING} flag to the model's + * [status]{@link module:enyo/Model~Model#status}. Once it has completed execution, it will + * remove this flag (even if it fails). + * + * @see module:enyo/Model~Model#fetched + * @see module:enyo/Model~Model#status + * @param {module:enyo/Model~Model~ActionOptions} [opts] - Optional configuration options. + * @returns {this} The callee for chaining. + * @public + */ + fetch: function (opts) { + var options, + source, + it = this; + + // if the current status is not one of the error or busy states we can continue + if (!(this.status & (States.ERROR | States.BUSY))) { + + // if there were options passed in we copy them quickly so that we can hijack + // the success and error methods while preserving the originals to use later + options = opts ? utils.clone(opts, true) : {}; + + // make sure we keep track of how many sources we're requesting + source = options.source || this.source; + if (source && ((source instanceof Array) || source === true)) { + this._waiting = source.length ? source.slice() : Object.keys(Source.sources); + } + + options.success = function (source, res) { + it.fetched(opts, res, source); + }; + + options.error = function (source, res) { + it.errored('FETCHING', opts, res, source); + }; + + // set the state + this.status = this.status | States.FETCHING; + + // now pass this on to the source to execute as it sees fit + Source.execute('fetch', this, options); + } else this.errored(this.status, opts); + + return this; + }, + + /** + * Destroys the [model]{@link module:enyo/Model~Model}. By default, the model will only + * be [destroyed]{@glossary destroy} in the client. To execute with a + * [source or sources]{@link module:enyo/Model~Model#source}, either the + * [commit default option]{@link module:enyo/Model~Model#options} must be `true` or a + * `source` property must be explicitly provided in the `opts` parameter. + * A model cannot be destroyed (using a source) if it is in an + * [error]{@link module:enyo/States#ERROR} ({@link module:enyo/StateSupport~StateSupport#isError}) + * or [busy]{@link module:enyo/States#BUSY} ({@link module:enyo/StateSupport~StateSupport#isBusy}) + * [state]{@link module:enyo/Model~Model#status}. While executing, it will add the + * [DESTROYING]{@link module:enyo/States#DESTROYING} flag to the model's + * [status]{@link module:enyo/Model~Model#status}. Once it has completed execution, it + * will remove this flag (even if it fails). + * + * @see module:enyo/Model~Model#status + * @param {module:enyo/Model~Model~ActionOptions} [opts] - Optional configuration options. + * @returns {this} The callee for chaining. + * @public + */ + destroy: function (opts) { + var options = opts ? utils.mixin({}, [this.options, opts]) : this.options, + it = this, + idx; + + // this becomes an (potentially) async operation if we are committing this destroy + // to a source and its kind of tricky to figure out because there are several ways + // it could be flagged to do this + + if (options.commit || options.source) { + + // if the current status is not one of the error states we can continue + if (!(this.status & (States.ERROR | States.BUSY))) { + + // remap to the originals + options = opts ? utils.clone(opts, true) : {}; + + options.success = function (source, res) { + + if (it._waiting) { + idx = it._waiting.findIndex(function (ln) { + return (ln instanceof Source ? ln.name : ln) == source; + }); + if (idx > -1) it._waiting.splice(idx, 1); + if (!it._waiting.length) it._waiting = null; + } + + // continue the operation this time with commit false explicitly + if (!it._waiting) { + options.commit = options.source = null; + it.destroy(options); + } + if (opts && opts.success) opts.success(this, opts, res, source); + }; + + options.error = function (source, res) { + + if (it._waiting) { + idx = it._waiting.findIndex(function (ln) { + return (ln instanceof Source ? ln.name : ln) == source; + }); + if (idx > -1) it._waiting.splice(idx, 1); + if (!it._waiting.length) it._waiting = null; + } + + // continue the operation this time with commit false explicitly + if (!it._waiting) { + options.commit = options.source = null; + it.destroy(options); + } + + // we don't bother setting the error state if we aren't waiting because it + // will be cleared to DESTROYED and it would be pointless + else this.errored('DESTROYING', opts, res, source); + }; + + this.status = this.status | States.DESTROYING; + + Source.execute('destroy', this, options); + } else if (this.status & States.ERROR) this.errored(this.status, opts); + + // we don't allow the destroy to take place and we don't forcibly break-down + // the collection errantly so there is an opportuniy to resolve the issue + // before we lose access to the collection's content! + return this; + } + + + // we flag this early so objects that receive an event and process it + // can optionally check this to support faster cleanup in some cases + // e.g. Collection/Store don't need to remove listeners because it will + // be done in a much quicker way already + this.destroyed = true; + this.status = States.DESTROYED; + this.unsilence(true).emit('destroy'); + this.removeAllListeners(); + this.removeAllObservers(); + + // if this does not have the the batching flag (that would be set by a collection) + // then we need to do the default of removing it from the store + if (!opts || !opts.batching) this.store.remove(this); + }, + + /** + * Retrieves the value for the given property or path. If the property is a + * [computed property]{@link module:enyo/ComputedSupport}, then it will return + * that value; otherwise, it will attempt to retrieve the value from the + * [attributes hash]{@link module:enyo/Model~Model#attributes}. + * + * @param {String} path - The property to retrieve. + * @returns {*} The value for the requested property or path, or `undefined` if + * it cannot be found or does not exist. + * @public + */ + get: function (path) { + return this.isComputed(path) ? this._getComputed(path) : this.attributes[path]; + }, + + /** + * Sets the requested `path` or [hash]{@glossary Object} of properties on the + * [model]{@link module:enyo/Model~Model}. Properties are applied to the + * [attributes hash]{@link module:enyo/Model~Model#attributes} and are retrievable via + * [get()]{@link module:enyo/Model~Model#get}. If properties were updated and the `silent` + * option is not `true`, this method will emit a `change` event, as well as + * individual [notifications]{@link module:enyo/ObserverSupport~ObserverSupport.notify} for the + * properties that were modified. + * + * @fires module:enyo/Model~Model#change + * @see {@link module:enyo/ObserverSupport~ObserverSupport} + * @see {@link module:enyo/BindingSupport~BindingSupport} + * @param {(String|Object)} path - Either the property name or a [hash]{@glossary Object} + * of properties and values to set. + * @param {(*|module:enyo/Model~Options)} is If `path` is a [string]{@glossary String}, + * this should be the value to set for the given property; otherwise, it should be + * an optional hash of available [configuration options]{@link module:enyo/Model~Model~Options}. + * @param {module:enyo/Model~Options} [opts] - If `path` is a string, this should be the + * optional hash of available configuration options; otherwise, it will not be used. + * @returns {this} The callee for chaining. + * @public + */ + set: function (path, is, opts) { + if (!this.destroyed) { + + var attrs = this.attributes, + options = this.options, + changed, + incoming, + force, + silent, + key, + value, + commit, + fetched; + + // the default case for this setter is accepting an object of key->value pairs + // to apply to the model in which case the second parameter is the optional + // configuration hash + if (typeof path == 'object') { + incoming = path; + opts = opts || is; + } + + // otherwise in order to have a single path here we flub it so it will keep on + // going as expected + else { + incoming = {}; + incoming[path] = is; + } + + // to maintain backward compatibility with the old setters that allowed the third + // parameter to be a boolean to indicate whether or not to force notification of + // change even if there was any + if (opts === true) { + force = true; + opts = {}; + } + + opts = opts ? utils.mixin({}, [options, opts]) : options; + silent = opts.silent; + force = force || opts.force; + commit = opts.commit; + fetched = opts.fetched; + + for (key in incoming) { + value = incoming[key]; + + if (value !== attrs[key] || force) { + // to ensure we have an object to work with + // note that we check inside this loop so we don't have to examine keys + // later only the local variable changed + changed = this.changed || (this.changed = {}); + //store the previous attr value + this.previous[key] = attrs[key]; + //set new value + changed[key] = attrs[key] = value; + } + } + + if (changed) { + + // we add dirty as a value of the status but clear the CLEAN bit if it + // was set - this would allow it to be in the ERROR state and NEW and DIRTY + if (!fetched) this.status = (this.status | States.DIRTY) & ~States.CLEAN; + + if (!silent) this.emit('change', changed, this); + + if (commit && !fetched) this.commit(opts); + + // reset value so subsequent changes won't be added to this change-set + this.changed = null; + } + } + + return this; + }, + + /** + * A bit of hackery to facade the normal [getter]{@link module:enyo/ComputedSupport~ComputedSupport#get}. Note that + * we pass an arbitrary super-method that automatically returns `undefined`, which is + * consistent with this use case and its intended purpose. + * + * @private + */ + _getComputed: ComputedSupport.get.fn(function () { return undefined; }), + + /** + * Initializes the [model]{@link module:enyo/Model~Model}. Unlike some methods, the parameters are not + * interchangeable. If you are not using a particular (optional) parameter, pass in `null`. + * + * @param {Object} [attrs] - Optionally initialize the [model]{@link module:enyo/Model~Model} with some + * [attributes]{@link module:enyo/Model~Model#attributes}. + * @param {Object} [props] - Properties to apply directly to the [model]{@link module:enyo/Model~Model} and + * not the [attributes hash]{@link module:enyo/Model~Model#attributes}. If these properties contain an + * `options` property (a [hash]{@glossary Object}) it will be merged with existing + * [options]{@link module:enyo/Model~Model#options}. + * @param {module:enyo/Model~Model~Options} [opts] - This is a one-time [options hash]{@link module:enyo/Model~Model~Options} that + * is only used during initialization and not applied as defaults. + * @public + */ + constructor: function (attrs, props, opts) { + + // in cases where there is an options hash provided in the _props_ param + // we need to integrate it manually... + if (props && props.options) { + this.options = utils.mixin({}, [this.options, props.options]); + delete props.options; + } + + // the _opts_ parameter is a one-hit options hash it does not leave + // behind its values as default options... + opts = opts? utils.mixin({}, [this.options, opts]): this.options; + + // go ahead and mix all of the properties in + props && utils.mixin(this, props); + + var noAdd = opts.noAdd + , commit = opts.commit + , parse = opts.parse + , fetch = this.options.fetch + , defaults; + + // defaults = this.defaults && (typeof this.defaults == 'function'? this.defaults(attrs, opts): this.defaults); + defaults = this.defaults && typeof this.defaults == 'function'? this.defaults(attrs, opts): null; + + // ensure we have a unique identifier that could potentially + // be used in remote systems + this.euid = this.euid || utils.uid('m'); + + // if necessary we need to parse the incoming attributes + attrs = attrs? parse? this.parse(attrs): attrs: null; + + // ensure we have the updated attributes + this.attributes = this.attributes? defaults? utils.mixin({}, [defaults, this.attributes]): utils.clone(this.attributes, true): defaults? utils.clone(defaults, true): {}; + attrs && utils.mixin(this.attributes, attrs); + this.previous = utils.clone(this.attributes); + + // now we need to ensure we have a store and register with it + this.store = this.store || Store; + + // @TODO: The idea here is that when batch instancing records a collection + // should be intelligent enough to avoid doing each individually or in some + // cases it may be useful to have a record that is never added to a store? + if (!noAdd) this.store.add(this, opts); + + commit && this.commit(); + fetch && this.fetch(); + }, + + /** + * Overloaded. We funnel arbitrary notification updates through here, as this + * is faster than using the built-in notification updates for batch operations. + * + * @private + */ + emit: kind.inherit(function (sup) { + return function (e, props) { + if (e == 'change' && props && this.isObserving()) { + for (var key in props) this.notify(key, this.previous[key], props[key]); + } + return sup.apply(this, arguments); + }; + }), + + /** + * Overloaded to alias the (also overloaded) [emit()]{@link module:enyo/Model~Model#emit} method. + * + * @private + */ + triggerEvent: function () { + return this.emit.apply(this, arguments); + }, + + /** + * When a [fetch]{@link module:enyo/Model~Model#fetch} has completed successfully, it is returned + * to this method. This method handles special and important behavior; it should not be + * called directly and, when overloading, care must be taken to ensure that you call + * the super-method. This correctly sets the [status]{@link module:enyo/Model~Model#status} and, in + * cases where multiple [sources]{@link module:enyo/Model~Model#source} were used, it waits until + * all have responded before clearing the [FETCHING]{@link module:enyo/States#FETCHING} flag. + * If a [success]{@link module:enyo/Model~Model~Success} callback was provided, it will be called + * once for each source. + * + * @param {module:enyo/Model~Model~ActionOptions} opts - The original options passed to + * [fetch()]{@link module:enyo/Model~Model#fetch}, merged with the defaults. + * @param {*} [res] - The result provided from the given [source]{@link module:enyo/Model~Model#source}, + * if any. This will vary depending on the source. + * @param {String} source - The name of the source that has completed successfully. + * @public + */ + fetched: function (opts, res, source) { + var idx, + options = this.options; + + if (this._waiting) { + idx = this._waiting.findIndex(function (ln) { + return (ln instanceof Source ? ln.name : ln) == source; + }); + if (idx > -1) this._waiting.splice(idx, 1); + if (!this._waiting.length) this._waiting = null; + } + + // normalize options so we have values and ensure it knows it was just fetched + opts = opts ? utils.mixin({}, [options, opts]) : options; + opts.fetched = true; + + // for a special case purge to only use the result sub-tree of the fetched data for + // the model attributes + if (opts.parse) res = this.parse(res); + + // note this will not add the DIRTY state because it was fetched but also note that it + // will not clear the DIRTY flag if it was already DIRTY + if (res) this.set(res, opts); + + // clear the FETCHING and NEW state (if it was NEW) we do not set it as dirty as this + // action alone doesn't warrant a dirty flag that would need to be set in the set method + if (!this._waiting) this.status = this.status & ~(States.FETCHING | States.NEW); + + // now look for an additional success callback + if (opts.success) opts.success(this, opts, res, source); + }, + + /** + * When a [commit]{@link module:enyo/Model~Model#commit} has completed successfully, it is returned + * to this method. This method handles special and important behavior; it should not be + * called directly and, when overloading, care must be taken to ensure that you call the + * super-method. This correctly sets the [status]{@link module:enyo/Model~Model#status} and, in cases + * where multiple [sources]{@link module:enyo/Model~Model#source} were used, it waits until all have + * responded before clearing the [COMMITTING]{@link module:enyo/States#COMMITTING} flag. If a + * [success]{@link module:enyo/Model~Model~Success} callback was provided, it will be called once for + * each source. + * + * @param {module:enyo/Model~Model~ActionOptions} opts - The original options passed to + * [commit()]{@link module:enyo/Model~Model#commit}, merged with the defaults. + * @param {*} [res] - The result provided from the given [source]{@link module:enyo/Model~Model#source}, + * if any. This will vary depending on the source. + * @param {String} source - The name of the source that has completed successfully. + * @public + */ + committed: function (opts, res, source) { + var idx; + + if (this._waiting) { + idx = this._waiting.findIndex(function (ln) { + return (ln instanceof Source ? ln.name : ln) == source; + }); + if (idx > -1) this._waiting.splice(idx, 1); + if (!this._waiting.length) this._waiting = null; + } + + if (!this._waiting) { + // we need to clear the COMMITTING bit and DIRTY bit as well as ensure that the + // 'previous' hash is whatever the current attributes are + this.previous = utils.clone(this.attributes); + this.status = (this.status | States.CLEAN) & ~(States.COMMITTING | States.DIRTY); + } + + if (opts && opts.success) opts.success(this, opts, res, source); + }, + + /** + * When an action ([fetch()]{@link module:enyo/Model~Model#fetch}, [commit()]{@link module:enyo/Model~Model#commit}, + * or [destroy()]{@link module:enyo/Model~Model#destroy}) has failed, it will be passed to this method. + * This method handles special and important behavior; it should not be called directly + * and, when overloading, care must be taken to ensure that you call the super-method. + * This correctly sets the [status]{@link module:enyo/Model~Model#status} to the known + * [error state]{@link module:enyo/States#ERROR}, or to the + * [unknown error state]{@link module:enyo/States#ERROR_UNKNOWN} if it the error state could not + * be determined. If an [error callback]{@link module:enyo/Model~Model~Error} was provided, this method + * will execute it. + * + * @see {@link module:enyo/StateSupport~StateSupport#clearError} + * @param {String} action - The action (one of `'FETCHING'`, `'COMMITTING'`, or + * `'DESTROYING'`) that failed and is now in an [error state]{@link module:enyo/States#ERROR}. + * @param {module:enyo/Model~Model~ActionOptions} opts - The original options passed to the `action` + * method, merged with the defaults. + * @param {*} [res] - The result provided from the given [source]{@link module:enyo/Model~Model#source}, + * if any. This will vary depending on the source. + * @param {String} source - The name of the source that has returned an error. + * @public + */ + errored: function (action, opts, res, source) { + var stat, + idx; + + // if the error action is a status number then we don't need to update it otherwise + // we set it to the known state value + if (typeof action == 'string') { + + // all built-in errors will pass this as their values are > 0 but we go ahead and + // ensure that no developer used the 0x00 for an error code + stat = States['ERROR_' + action]; + } else stat = action; + + if (isNaN(stat) || (stat & ~States.ERROR)) stat = States.ERROR_UNKNOWN; + + // correctly set the current status and ensure we clear any busy flags + this.status = (this.status | stat) & ~States.BUSY; + + if (this._waiting) { + idx = this._waiting.findIndex(function (ln) { + return (ln instanceof Source ? ln.name : ln) == source; + }); + if (idx > -1) this._waiting.splice(idx, 1); + if (!this._waiting.length) this._waiting = null; + } + + // we need to check to see if there is an options handler for this error + if (opts && opts.error) opts.error(this, action, opts, res, source); + } + +}); + +/** +* @name module:enyo/Model~Model.concat +* @static +* @private +*/ +Model.concat = function (ctor, props) { + var proto = ctor.prototype || ctor; + + if (props.options) { + proto.options = utils.mixin({}, [proto.options, props.options]); + delete props.options; + } +}; + +/** +* @private +*/ +kind.features.push(function (ctor) { + if (ctor.prototype instanceof Model) { + !Store.models[ctor.prototype.kindName] && (Store.models[ctor.prototype.kindName] = new ModelList()); + } +}); + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./ObserverSupport':'enyo/ObserverSupport','./ComputedSupport':'enyo/ComputedSupport','./BindingSupport':'enyo/BindingSupport','./EventEmitter':'enyo/EventEmitter','./StateSupport':'enyo/StateSupport','./ModelList':'enyo/ModelList','./Source':'enyo/Source','./States':'enyo/States','./Store':'enyo/Store'}],'enyo/AnimationSupport/AnimationSupport':[function (module,exports,global,require,request){ +require('enyo'); + +var + kind = require('../kind'), + core = require('./Core'), + activator = require('./KeyFrame'), + frame = require('./Frame'), + utils = require('../utils'); + +var extend = kind.statics.extend; + +kind.concatenated.push('animation'); + +var AnimationSupport = { + + /** + * @private + */ + //name: 'AnimationSupport', + animating: false, + + active: false, + + animationState: "", + + /** + * Check if the character is suitable for animation + * @public + */ + ready: function() { + var ret = this.generated && this.animating; + if (ret && this._startTime) + ret = this._startTime <= utils.perfNow(); + + if(ret) this.set('animationState', 'started'); + return ret; + }, + + /** + * Sets current animation state for this character + * @public + */ + setInitial: function (initial) { + this._startAnim = initial; + }, + + /** + * Gets current state of animation for this character + * @parameter accelerate- Turns on/off hardware acceleration + * @public + */ + initiate: function (current) { + var dom = this.hasNode(), + prop = this.getAnimation(), + init = frame.getCompoutedProperty(dom, prop, current); + + utils.mixin(this, init); + }, + + /** + * Gets animations applied to this chracter. + * @public + */ + getAnimation: function() { + return this._prop || (this._prop = this.animate); + }, + + /** + * Adds new animation on already existing animation for this character. + * @public + */ + addAnimation: function (newProp) { + if (this._prop === undefined || this._prop == true) { + this._prop = newProp; + } else { + utils.mixin(this._prop, newProp); + } + }, + + /** + * Sets new animation for this character. + * @public + */ + setAnimation: function (newProp) { + this._prop = newProp; + }, + + /** + * Gets how long animation is active on this character + * @public + */ + getDuration: function() { + return this._duration || (this._duration = this.duration); + }, + + /** + * Sets how long animation should be active on this character + * @public + */ + setDuration: function (newDuration) { + this._duration = newDuration; + }, + + /** + * Idnetify when the character has done animating. + * This triggers "onAnimated" event on this character + * @public + */ + completed: function() { + return this.onAnimated && this.onAnimated(this); + }, + + /** + * Trigger animation for this character. + * @public + */ + start: function (active, delay) { + this._duration = parseInt(this.getDuration(), 10); + this._startTime = utils.perfNow() + (delay || 0) ; + this._lastTime = this._startTime + this._duration; + this.animating = true; + this.active = active; + this.initiate(this.currentState); + }, + + /** + * Halt existing animation of this character + * @public + */ + pause: function () { + this.animating = false; + }, + + /** + * @private + */ + rendered: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + this.initiate(); + frame.accelerate(this.hasNode(), this.matrix); + }; + }), + + /** + * @private + */ + destroy: kind.inherit(function(sup) { + return function() { + core.remove(this); + sup.apply(this, arguments); + }; + }), +}; + +module.exports = AnimationSupport; + +/** + Hijacking original behaviour as in other Enyo supports. +*/ +var sup = kind.concatHandler; + +/** +* @private +*/ +kind.concatHandler = function (ctor, props, instance) { + sup.call(this, ctor, props, instance); + if (props.animate || props.keyFrame || props.pattern) { + var proto = ctor.prototype || ctor; + extend(AnimationSupport, proto); + if (props.keyFrame && typeof props.keyFrame != 'function') { + activator.animate(proto, props); + } + if (props.animate && typeof props.animate != 'function') { + activator.trigger(proto); + } + } +}; +},{'../kind':'enyo/kind','./Core':'enyo/AnimationSupport/Core','./KeyFrame':'enyo/AnimationSupport/KeyFrame','./Frame':'enyo/AnimationSupport/Frame','../utils':'enyo/utils'}],'enyo/Signals':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Signals~Signals} kind. +* @module enyo/Signals +*/ + +var + kind = require('./kind'), + utils = require('./utils'); + +var + Component = require('./Component'); + +/** +* {@link module:enyo/Signals~Signals} is a [component]{@link module:enyo/Component~Component} used to listen +* to global messages. +* +* An object with a Signals component can listen to messages sent from anywhere +* by declaring handlers for them. +* +* DOM [events]{@glossary event} that have no node targets are broadcast as +* signals. These events include Window events, such as `onload` and +* `onbeforeunload`, as well as events that occur directly on `document`, such +* as `onkeypress` if `document` has the focus. +* +* For more information, see the documentation on [Event +* Handling]{@linkplain $dev-guide/key-concepts/event-handling.html} in the +* Enyo Developer Guide. +* +* @class Signals +* @extends module:enyo/Component~Component +* @public +*/ +var Signals = module.exports = kind( + /** @lends module:enyo/Signals~Signals.prototype */ { + + name: 'enyo.Signals', + + /** + * @private + */ + kind: Component, + + /** + * Needed because of early calls to bind DOM {@glossary event} listeners + * to the [enyo.Signals.send()]{@link module:enyo/Signals~Signals#send} call. + * + * @private + */ + + + /** + * @method + * @private + */ + create: kind.inherit(function (sup) { + return function() { + sup.apply(this, arguments); + Signals.addListener(this); + }; + }), + + /** + * @method + * @private + */ + destroy: kind.inherit(function (sup) { + return function() { + Signals.removeListener(this); + sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + notify: function (msg, load) { + this.dispatchEvent(msg, load); + }, + + /** + * @private + */ + protectedStatics: { + listeners: [], + addListener: function(listener) { + this.listeners.push(listener); + }, + removeListener: function(listener) { + utils.remove(listener, this.listeners); + } + }, + + /** + * @private + */ + statics: + /** @lends module:enyo/Signals~Signals.prototype */ { + + /** + * Broadcasts a global message to be consumed by subscribers. + * + * @param {String} msg - The message to send; usually the name of the + * {@glossary event}. + * @param {Object} load - An [object]{@glossary Object} containing any + * associated event properties to be accessed by subscribers. + * @public + */ + send: function (msg, load) { + utils.forEach(this.listeners, function(l) { + l.notify(msg, load); + }); + } + } +}); + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./Component':'enyo/Component'}],'enyo/MultipleDispatchComponent':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/MultipleDispatchComponent~MultipleDispatchComponent} kind. +* @module enyo/MultipleDispatchComponent +*/ + +var + kind = require('./kind'); + +var + Component = require('./Component'), + MultipleDispatchSupport = require('./MultipleDispatchSupport'); + +/** +* {@link module:enyo/MultipleDispatchComponent~MultipleDispatchComponent} is a purely abstract [kind] +* {@glossary kind} that simply provides a common ancestor for +* {@link module:enyo/Component~Component} [objects]{@glossary Object} that need +* the [MultipleDispatchSupport]{@link module:enyo/MultipleDispatchSupport~MultipleDispatchSupport} +* [mixin]{@glossary mixin}. +* +* @class MultipleDispatchComponent +* @extends module:enyo/Component~Component +* @mixes module:enyo/MultipleDispatchSupport~MultipleDispatchSupport +* @public +*/ +module.exports = kind( + /** @lends module:enyo/MultipleDispatchComponent~MultipleDispatchComponent */ { + + /** + * @private + */ + kind: Component, + + /** + * @private + */ + mixins: [MultipleDispatchSupport] +}); + +},{'./kind':'enyo/kind','./Component':'enyo/Component','./MultipleDispatchSupport':'enyo/MultipleDispatchSupport'}],'enyo/ScrollMath':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/ScrollMath~ScrollMath} kind. +* @module enyo/ScrollMath +*/ + +var + kind = require('./kind'), + utils = require('./utils'), + platform = require('./platform'), + animation = require('./animation'); + +var + Component = require('./Component'); + +/** +* Fires when a scrolling action starts. +* +* @event module:enyo/ScrollMath~ScrollMath#onScrollStart +* @type {Object} +* @property {Object} sender - The [component]{@link module:enyo/Component~Component} that most recently +* propagated the {@glossary event}. +* @property {module:enyo/Scroller~Scroller~ScrollEvent} event - An [object]{@glossary Object} containing +* event information. +* @private +*/ + +/** +* Fires while a scrolling action is in progress. +* +* @event module:enyo/ScrollMath~ScrollMath#onScroll +* @type {Object} +* @property {Object} sender - The [component]{@link module:enyo/Component~Component} that most recently +* propagated the {@glossary event}. +* @property {module:enyo/Scroller~Scroller~ScrollEvent} event - An [object]{@glossary Object} containing +* event information. +* @private +*/ + +/** +* Fires when a scrolling action stops. +* +* @event module:enyo/ScrollMath~ScrollMath#onScrollStop +* @type {Object} +* @property {Object} sender - The [component]{@link module:enyo/Component~Component} that most recently +* propagated the {@glossary event}. +* @property {module:enyo/Scroller~Scroller~ScrollEvent} event - An [object]{@glossary Object} containing +* event information. +* @private +*/ + +/** +* {@link module:enyo/ScrollMath~ScrollMath} implements a scrolling dynamics simulation. It is a +* helper [kind]{@glossary kind} used by other [scroller]{@link module:enyo/Scroller~Scroller} +* kinds, such as {@link module:enyo/TouchScrollStrategy~TouchScrollStrategy}. +* +* `enyo.ScrollMath` is not typically created in application code. +* +* @class ScrollMath +* @protected +*/ +module.exports = kind( + /** @lends module:enyo/ScrollMath~ScrollMath.prototype */ { + + name: 'enyo.ScrollMath', + + /** + * @private + */ + kind: Component, + + /** + * @private + */ + published: + /** @lends module:enyo/ScrollMath~ScrollMath.prototype */ { + + /** + * Set to `true` to enable vertical scrolling. + * + * @type {Boolean} + * @default true + * @private + */ + vertical: true, + + /** + * Set to `true` to enable horizontal scrolling. + * + * @type {Boolean} + * @default true + * @private + */ + horizontal: true + }, + + /** + * @private + */ + events: { + onScrollStart: '', + onScroll: '', + onScrollStop: '', + onStabilize: '' + }, + + /** + * "Spring" damping returns the scroll position to a value inside the boundaries. Lower + * values provide faster snapback. + * + * @private + */ + kSpringDamping: 0.93, + + /** + * "Drag" damping resists dragging the scroll position beyond the boundaries. Lower values + * provide more resistance. + * + * @private + */ + kDragDamping: 0.5, + + /** + * "Friction" damping reduces momentum over time. Lower values provide more friction. + * + * @private + */ + kFrictionDamping: 0.97, + + /** + * Additional "friction" damping applied when momentum carries the viewport into overscroll. + * Lower values provide more friction. + * + * @private + */ + kSnapFriction: 0.9, + + /** + * Scalar applied to `flick` event velocity. + * + * @private + */ + kFlickScalar: 15, + + /** + * Limits the maximum allowable flick. On Android > 2, we limit this to prevent compositing + * artifacts. + * + * @private + */ + kMaxFlick: platform.android > 2 ? 2 : 1e9, + + /** + * The value used in [friction()]{@link module:enyo/ScrollMath~ScrollMath#friction} to determine if the delta + * (e.g., y - y0) is close enough to zero to consider as zero. + * + * @private + */ + kFrictionEpsilon: platform.webos >= 4 ? 1e-1 : 1e-2, + + /** + * Top snap boundary, generally `0`. + * + * @private + */ + topBoundary: 0, + + /** + * Right snap boundary, generally `(viewport width - content width)`. + * + * @private + */ + rightBoundary: 0, + + /** + * Bottom snap boundary, generally `(viewport height - content height)`. + * + * @private + */ + bottomBoundary: 0, + + /** + * Left snap boundary, generally `0`. + * + * @private + */ + leftBoundary: 0, + + /** + * Animation time step. + * + * @private + */ + interval: 20, + + /** + * Flag to enable frame-based animation; if `false`, time-based animation is used. + * + * @private + */ + fixedTime: true, + + /** + * Simulation state. + * + * @private + */ + x0: 0, + + /** + * Simulation state. + * + * @private + */ + x: 0, + + /** + * Simulation state. + * + * @private + */ + y0: 0, + + /** + * Simulation state. + * + * @private + */ + y: 0, + + /** + * @method + * @private + */ + create: kind.inherit(function (sup) { + return function() { + sup.apply(this, arguments); + this.boundarySnapshot = {}; + }; + }), + + /** + * @method + * @private + */ + destroy: kind.inherit(function (sup) { + return function() { + this.stop(); + sup.apply(this, arguments); + }; + }), + + /** + * Simple Verlet integrator for simulating Newtonian motion. + * + * @private + */ + verlet: function () { + var x = this.x; + this.x += x - this.x0; + this.x0 = x; + // + var y = this.y; + this.y += y - this.y0; + this.y0 = y; + }, + + /** + * Boundary damping function. Returns damped `value` based on `coeff` on one side of + * `origin`. + * + * @private + */ + damping: function (val, origin, coeff, sign) { + var kEpsilon = 0.5; + // + // this is basically just value *= coeff (generally, coeff < 1) + // + // 'sign' and the conditional is to force the damping to only occur + // on one side of the origin. + // + var dv = val - origin; + // Force close-to-zero to zero + if (Math.abs(dv) < kEpsilon) { + return origin; + } + return val*sign > origin*sign ? coeff * dv + origin : val; + }, + + /** + * Dual-boundary damping function. Returns damped `value` based on `coeff` when exceeding + * either boundary. + * + * @private + */ + boundaryDamping: function (val, aBoundary, bBoundary, coeff) { + return this.damping(this.damping(val, aBoundary, coeff, 1), bBoundary, coeff, -1); + }, + + /** + * Simulation constraints (spring damping occurs here). + * + * @private + */ + constrain: function () { + var b = this.getDampingBoundaries(), + y = this.boundaryDamping(this.y, b.top, b.bottom, this.kSpringDamping), + x = this.boundaryDamping(this.x, b.left, b.right, this.kSpringDamping); + + if (y != this.y) { + // ensure snapping introduces no velocity, add additional friction + this.y0 = y - (this.y - this.y0) * this.kSnapFriction; + this.y = y; + } + + if (x != this.x) { + this.x0 = x - (this.x - this.x0) * this.kSnapFriction; + this.x = x; + } + }, + + /** + * The friction function. + * + * @private + */ + friction: function (ex, ex0, coeff) { + // implicit velocity + var dp = this[ex] - this[ex0]; + // let close-to-zero collapse to zero (i.e. smaller than epsilon is considered zero) + var c = Math.abs(dp) > this.kFrictionEpsilon ? coeff : 0; + // reposition using damped velocity + this[ex] = this[ex0] + c * dp; + }, + + /** + * One unit of time for simulation. + * + * @private + */ + frame: 10, + // piece-wise constraint simulation + simulate: function (t) { + while (t >= this.frame) { + t -= this.frame; + if (!this.dragging) { + this.constrain(); + } + this.verlet(); + this.friction('y', 'y0', this.kFrictionDamping); + this.friction('x', 'x0', this.kFrictionDamping); + } + return t; + }, + + /** + * @method + * @private + */ + getDampingBoundaries: function() { + return this.haveBoundarySnapshot ? + this.boundarySnapshot : + { + top : this.topBoundary, + bottom : this.bottomBoundary, + left : this.leftBoundary, + right : this.rightBoundary + }; + }, + + /** + * @method + * @private + */ + takeBoundarySnapshot: function () { + var s; + + if (!this.haveBoundarySnapshot) { + s = this.boundarySnapshot; + + s.top = this.topBoundary; + s.bottom = this.bottomBoundary; + s.left = this.leftBoundary; + s.right = this.rightBoundary; + + this.haveBoundarySnapshot = true; + } + }, + + /** + * @method + * @private + */ + discardBoundarySnapshot: function() { + this.haveBoundarySnapshot = false; + }, + + /** + * @fires module:enyo/ScrollMath~ScrollMath#onScrollStop + * @private + */ + animate: function () { + this.stop(); + // time tracking + var t0 = utils.perfNow(), t = 0; + // delta tracking + var x0, y0; + // animation handler + var fn = this.bindSafely(function() { + // wall-clock time + var t1 = utils.perfNow(); + // schedule next frame + this.job = animation.requestAnimationFrame(fn); + // delta from last wall clock time + var dt = t1 - t0; + // record the time for next delta + t0 = t1; + // user drags override animation + if (this.dragging) { + this.y0 = this.y = this.uy; + this.x0 = this.x = this.ux; + this.endX = this.endY = null; + } + // frame-time accumulator + // min acceptable time is 16ms (60fps) + t += Math.max(16, dt); + // prevent snapping to originally desired scroll position if we are in overscroll + if (this.isInOverScroll()) { + this.endX = null; + this.endY = null; + + this.takeBoundarySnapshot(); + } + else { + this.discardBoundarySnapshot(); + + // alternate fixed-time step strategy: + if (this.fixedTime) { + t = this.interval; + } + } + // consume some t in simulation + t = this.simulate(t); + // scroll if we have moved, otherwise the animation is stalled and we can stop + if (y0 != this.y || x0 != this.x) { + this.scroll(); + } else if (!this.dragging) { + // set final values + if (this.endX != null) { + this.x = this.x0 = this.endX; + } + if (this.endY != null) { + this.y = this.y0 = this.endY; + } + + this.stop(); + this.scroll(); + this.doScrollStop(); + + this.endX = null; + this.endY = null; + } + y0 = this.y; + x0 = this.x; + }); + this.job = animation.requestAnimationFrame(fn); + }, + + /** + * @private + */ + start: function () { + if (!this.job) { + this.doScrollStart(); + this.animate(); + } + }, + + /** + * @private + */ + stop: function (fire) { + var job = this.job; + if (job) { + this.job = animation.cancelRequestAnimationFrame(job); + } + if (fire) { + this.doScrollStop(); + + this.endX = undefined; + this.endY = undefined; + } + }, + + /** + * Adjusts the scroll position to be valid, if necessary (e.g., after the scroll contents + * have changed). + * + * @private + */ + stabilize: function (opts) { + var fire = !opts || opts.fire === undefined || opts.fire; + var y = Math.min(this.topBoundary, Math.max(this.bottomBoundary, this.y)); + var x = Math.min(this.leftBoundary, Math.max(this.rightBoundary, this.x)); + if (y != this.y || x != this.x) { + this.y = this.y0 = y; + this.x = this.x0 = x; + if (fire) { + this.doStabilize(); + } + } + }, + + /** + * @private + */ + startDrag: function (e) { + this.dragging = true; + // + this.my = e.pageY; + this.py = this.uy = this.y; + // + this.mx = e.pageX; + this.px = this.ux = this.x; + }, + + /** + * @private + */ + drag: function (e) { + var dy, dx, v, h; + if (this.dragging) { + v = this.canScrollY(); + h = this.canScrollX(); + + dy = v ? e.pageY - this.my : 0; + this.uy = dy + this.py; + // provides resistance against dragging into overscroll + this.uy = this.boundaryDamping(this.uy, this.topBoundary, this.bottomBoundary, this.kDragDamping); + // + dx = h ? e.pageX - this.mx : 0; + this.ux = dx + this.px; + // provides resistance against dragging into overscroll + this.ux = this.boundaryDamping(this.ux, this.leftBoundary, this.rightBoundary, this.kDragDamping); + // + this.start(); + return true; + } + }, + + /** + * @private + */ + dragDrop: function () { + if (this.dragging && !window.PalmSystem) { + var kSimulatedFlickScalar = 0.5; + this.y = this.uy; + this.y0 = this.y - (this.y - this.y0) * kSimulatedFlickScalar; + this.x = this.ux; + this.x0 = this.x - (this.x - this.x0) * kSimulatedFlickScalar; + } + this.dragFinish(); + }, + + /** + * @private + */ + dragFinish: function () { + this.dragging = false; + }, + + /** + * @private + */ + flick: function (e) { + var v; + if (this.canScrollY()) { + v = e.yVelocity > 0 ? Math.min(this.kMaxFlick, e.yVelocity) : Math.max(-this.kMaxFlick, e.yVelocity); + this.y = this.y0 + v * this.kFlickScalar; + } + if (this.canScrollX()) { + v = e.xVelocity > 0 ? Math.min(this.kMaxFlick, e.xVelocity) : Math.max(-this.kMaxFlick, e.xVelocity); + this.x = this.x0 + v * this.kFlickScalar; + } + this.start(); + }, + + /** + * TODO: Refine and test newMousewheel, remove this + * @private + */ + mousewheel: function (e) { + var dy = this.vertical ? e.wheelDeltaY || (!e.wheelDeltaX ? e.wheelDelta : 0) : 0, + dx = this.horizontal ? e.wheelDeltaX : 0, + shouldScroll = false; + if ((dy > 0 && this.y < this.topBoundary) || (dy < 0 && this.y > this.bottomBoundary)) { + this.y = this.y0 = this.y0 + dy; + shouldScroll = true; + } + if ((dx > 0 && this.x < this.leftBoundary) || (dx < 0 && this.x > this.rightBoundary)) { + this.x = this.x0 = this.x0 + dx; + shouldScroll = true; + } + this.stop(!shouldScroll); + if (shouldScroll) { + this.start(); + return true; + } + }, + + /** + * @private + */ + newMousewheel: function (e, opts) { + var rtl = opts && opts.rtl, + wdY = (e.wheelDeltaY === undefined) ? e.wheelDelta : e.wheelDeltaY, + dY = wdY, + dX = rtl ? -e.wheelDeltaX : e.wheelDeltaX, + canY = this.canScrollY(), + canX = this.canScrollX(), + shouldScroll = false, + m = 2, + // TODO: Figure out whether we need to port the configurable + // max / multiplier feature from Moonstone's implementation, + // and (if so) how + // max = 100, + scr = this.isScrolling(), + ovr = this.isInOverScroll(), + refY = (scr && this.endY !== null) ? this.endY : this.y, + refX = (scr && this.endX !== null) ? this.endX : this.x, + tY = refY, + tX = refX; + + if (ovr) { + return true; + } + + // If we're getting strictly vertical mousewheel events over a scroller that + // can only move horizontally, the user is probably using a one-dimensional + // mousewheel and would like us to scroll horizontally instead + if (dY && !dX && canX && !canY) { + dX = dY; + dY = 0; + } + + if (dY && canY) { + tY = -(refY + (dY * m)); + shouldScroll = true; + } + if (dX && canX) { + tX = -(refX + (dX * m)); + shouldScroll = true; + } + + if (shouldScroll) { + this.scrollTo(tX, tY, {allowOverScroll: true}); + return true; + } + }, + + /** + * @fires module:enyo/ScrollMath~ScrollMath#onScroll + * @private + */ + scroll: function () { + this.doScroll(); + }, + + // NOTE: Yip/Orvell method for determining scroller instantaneous velocity + // FIXME: incorrect if called when scroller is in overscroll region + // because does not account for additional overscroll damping. + + /** + * Scrolls to the specified position. + * + * @param {Number} x - The `x` position in pixels. + * @param {Number} y - The `y` position in pixels. + * @param {Object} opts - TODO: Document. When behavior == 'instant', we skip animation. + * @private + */ + scrollTo: function (x, y, opts) { + var animate = !opts || opts.behavior !== 'instant', + xSnap = this.xSnapIncrement, + ySnap = this.ySnapIncrement, + allowOverScroll = opts && opts.allowOverScroll, + maxX = Math.abs(Math.min(0, this.rightBoundary)), + maxY = Math.abs(Math.min(0, this.bottomBoundary)); + + if (typeof xSnap === 'number') { + x = xSnap * Math.round(x / xSnap); + } + + if (typeof ySnap === 'number') { + y = ySnap * Math.round(y / ySnap); + } + + if (!animate || !allowOverScroll) { + x = Math.max(0, Math.min(x, maxX)); + y = Math.max(0, Math.min(y, maxY)); + } + + if (-x == this.x && -y == this.y) return; + + if (!animate) { + this.doScrollStart(); + this.setScrollX(-x); + this.setScrollY(-y); + this.doScroll(); + this.doScrollStop(); + } + else { + if (y !== null) { + this.endY = -y; + this.y = this.y0 - (y + this.y0) * (1 - this.kFrictionDamping); + } + if (x !== null) { + this.endX = -x; + this.x = this.x0 - (x + this.x0) * (1 - this.kFrictionDamping); + } + this.start(); + } + }, + + /** + * Sets the scroll position along the x-axis. + * + * @param {Number} x - The x-axis scroll position in pixels. + * @method + * @private + */ + setScrollX: function (x) { + this.x = this.x0 = x; + }, + + /** + * Sets the scroll position along the y-axis. + * + * @param {Number} y - The y-axis scroll position in pixels. + * @method + * @private + */ + setScrollY: function (y) { + this.y = this.y0 = y; + }, + + /** + * Sets the scroll position; defaults to setting this position along the y-axis. + * + * @param {Number} pos - The scroll position in pixels. + * @method + * @private + */ + setScrollPosition: function (pos) { + this.setScrollY(pos); + }, + + canScrollX: function() { + return this.horizontal && this.rightBoundary < 0; + }, + + canScrollY: function() { + return this.vertical && this.bottomBoundary < 0; + }, + + + /** + * Determines whether or not the [scroller]{@link module:enyo/Scroller~Scroller} is actively moving. + * + * @return {Boolean} `true` if actively moving; otherwise, `false`. + * @private + */ + isScrolling: function () { + return Boolean(this.job); + }, + + /** + * Determines whether or not the [scroller]{@link module:enyo/Scroller~Scroller} is in overscroll. + * + * @return {Boolean} `true` if in overscroll; otherwise, `false`. + * @private + */ + isInOverScroll: function () { + return this.job && (this.x > this.leftBoundary || this.x < this.rightBoundary || + this.y > this.topBoundary || this.y < this.bottomBoundary); + } +}); + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./platform':'enyo/platform','./animation':'enyo/animation','./Component':'enyo/Component'}],'enyo/Collection':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Collection~Collection} kind. +* @module enyo/Collection +*/ + +var + kind = require('./kind'), + utils = require('./utils'); + +var + Component = require('./Component'), + EventEmitter = require('./EventEmitter'), + Model = require('./Model'), + ModelList = require('./ModelList'), + StateSupport = require('./StateSupport'), + Source = require('./Source'), + Store = require('./Store'), + States = require('./States'); + +/** +* This is only necessary because of the order in which mixins are applied. +* +* @class +* @private +*/ +var BaseCollection = kind({ + kind: Component, + mixins: [EventEmitter, StateSupport] +}); + +/** +* Fires when [models]{@link module:enyo/Model~Model} have been [added]{@link module:enyo/Collection~Collection#add} +* to the [collection]{@link module:enyo/Collection~Collection}. +* +* @event module:enyo/Collection~Collection#add +* @type {Object} +* @property {module:enyo/Model~Model[]} models - An [array]{@glossary Array} of +* [models]{@link module:enyo/Model~Model} that were [added]{@link module:enyo/Collection~Collection#add} to the +* [collection]{@link module:enyo/Collection~Collection}. +* @property {module:enyo/Collection~Collection} collection - A reference to the +* collection that [emitted]{@link module:enyo/EventEmitter~EventEmitter#emit} the event. +* @property {Number} index - The index in the given collection where the models were inserted. +* @public +*/ + +/** +* Fires when [models]{@link module:enyo/Model~Model} have been [removed]{@link module:enyo/Collection~Collection#remove} +* from the [collection]{@link module:enyo/Collection~Collection}. +* +* @event module:enyo/Collection~Collection#remove +* @type {Object} +* @property {module:enyo/Model~Model[]} models - An [array]{@glossary Array} of +* [models]{@link module:enyo/Model~Model} that were [removed]{@link module:enyo/Collection~Collection#remove} from the +* [collection]{@link module:enyo/Collection~Collection}. +* @property {module:enyo/Collection~Collection} collection - A reference to the +* collection that [emitted]{@link module:enyo/EventEmitter~EventEmitter#emit} the event. +* @public +*/ + +/** +* Fires when the [collection]{@link module:enyo/Collection~Collection} has been +* [sorted]{@link module:enyo/Collection~Collection#sort}. +* +* @event module:enyo/Collection~Collection#sort +* @type {Object} +* @property {module:enyo/Model~Model[]} models - An [array]{@glossary Array} of all +* [models]{@link module:enyo/Model~Model} in the correct, [sorted]{@link module:enyo/Collection~Collection#sort} order. +* @property {module:enyo/Collection~Collection} collection - A reference to the +* [collection]{@link module:enyo/Collection~Collection} that [emitted]{@link module:enyo/EventEmitter~EventEmitter#emit} the event. +* @property {Function} comparator - A reference to the +* [comparator]{@link module:enyo/Collection~Collection#comparator} that was used when +* sorting the collection. +* @public +*/ + +/** +* Fires when the [collection]{@link module:enyo/Collection~Collection} has been reset and its +* contents have been updated arbitrarily. +* +* @event module:enyo/Collection~Collection#reset +* @type {Object} +* @property {module:enyo/Model~Model[]} models - An [array]{@glossary Array} of all +* [models]{@link module:enyo/Model~Model} as they are currently. +* @property {module:enyo/Collection~Collection} collection - A reference to the +* [collection]{@link module:enyo/Collection~Collection} that [emitted]{@link module:enyo/EventEmitter~EventEmitter#emit} the event. +* @public +*/ + +/** +* The default configurable [options]{@link module:enyo/Collection~Collection#options} used by certain API +* methods of {@link module:enyo/Collection~Collection}. +* +* @typedef {Object} module:enyo/Collection~Options +* @property {Boolean} merge=true - If `true`, when data is being added to the +* [collection]{@link module:enyo/Collection~Collection} that already exists (i.e., is matched by +* [primaryKey]{@link module:enyo/Model~Model#primaryKey}), the new data values will be set +* with the current [model]{@link module:enyo/Model~Model} instance. This means that the +* existing values will be updated with the new ones by calling +* [set()]{@link module:enyo/Model~Model#set} on the model. +* @property {Boolean} silent=false - Many accessor methods of the collection +* will emit events and/or notifications. This value indicates whether or not +* those events or notifications will be suppressed at times when that behavior +* is necessary. Typically, you will not want to modify this value. +* @property {Boolean} purge=false - When [adding]{@link module:enyo/Collection~Collection#add} +* models, this flag indicates whether or not to [remove]{@link module:enyo/Collection~Collection#remove} +* (purge) the existing models that are not included in the new dataset. +* @property {Boolean} parse=false - The collection's [parse()]{@link module:enyo/Collection~Collection#parse} +* method can be executed automatically when incoming data is added via the +* [constructor()]{@link module:enyo/Collection~Collection#constructor} method, or, later, via a +* [fetch]{@link module:enyo/Collection~Collection#fetch}. You may need to examine the runtime +* configuration options of the method(s) to determine whether parsing is needed. +* In cases where parsing will always be necessary, this may be set to `true`. +* @property {Boolean} create=true - This value determines whether a new +* model will be created when data being added to the collection cannot be found +* (or the [find]{@link module:enyo/Collection~Collection#options#find} flag is `false`). Models +* that are created by a collection have their [owner]{@link module:enyo/Model~Model#owner} +* property set to the collection that instanced them. +* @property {Boolean} find=true - When data being added to the collection is not +* already a model instance, the collection will attempt to find an existing model +* by its `primaryKey`, if it exists. In most cases, this is the preferred behavior, +* but if the model [kind]{@glossary kind} being instanced does not have a +* `primaryKey`, it is unnecessary and this value may be set to `false`. +* @property {Boolean} sort=false - When adding models to the collection, the +* collection can also be sorted. If the [comparator]{@link module:enyo/Collection~Collection#comparator} +* is a [function]{@glossary Function} and this value is `true`, the comparator +* will be used to sort the entire collection. It may also be a function that +* will be used to sort the collection, instead of (or in the place of) a defined +* comparator. +* @property {Boolean} commit=false - When modifications are made to the +* collection, this flag ensures that those changes are +* [committed]{@link module:enyo/Collection~Collection#commit} according to the configuration and +* availability of a [source]{@link module:enyo/Collection~Collection#source}. This may also be +* configured per-call to methods that use it. +* @property {Boolean} destroy=false - When models are removed from the collection, +* this flag indicates whether or not they will be [destroyed]{@link module:enyo/Model~Model#destroy} +* as well. Note that this could have a significant impact if the same models are +* used in other collections. +* @property {Boolean} complete=false - When models are removed from the +* collection, this flag indicates whether or not they will also be removed from +* the [store]{@link module:enyo/Collection~Collection#store}. This is rarely necessary and can +* cause problems if the models are used in other collections. In addition, this +* value will be ignored if the [destroy]{@link module:enyo/Collection~Collection#options#destroy} +* flag is `true`. +* @property {Boolean} fetch=false - If `true`, when the collection is initialized, +* it will automatically attempt to fetch data if the +* [source]{@link module:enyo/Collection~Collection#source} and [url]{@link module:enyo/Collection~Collection#url} +* or [getUrl]{@link module:enyo/Collection~Collection#getUrl} properties are properly configured. +* @property {Boolean} modelEvents=true - If `false`, this will keep the collection from +* registering with each model for individual model events. +*/ + +/** +* The configuration options for [add()]{@link module:enyo/Collection~Collection#add}. For complete +* descriptions of the options and their default values, see +* {@link module:enyo/Collection~Collection#options}. Note that some properties have different +* meanings in different contexts. Please review the descriptions below to see +* how each property is used in this context. +* +* @typedef {module:enyo/Collection~Options} module:enyo/Collection~AddOptions +* @property {Boolean} merge - Update existing [models]{@link module:enyo/Model~Model} when found. +* @property {Boolean} purge - Remove existing models not in the new dataset. +* @property {Boolean} silent - Emit [events]{@glossary event} and notifications. +* @property {Boolean} parse - Parse the incoming dataset before evaluating. +* @property {Boolean} find - Look for an existing model. +* @property {(Boolean|Function)} sort - Sort the finalized dataset. +* @property {Boolean} commit - [Commit]{@link module:enyo/Collection~Collection#commit} changes to the +* {@link module:enyo/Collection~Collection} after completing the [add]{@link module:enyo/Collection~Collection#add} +* operation. +* @property {Boolean} create - When an existing {@link module:enyo/Model~Model} instance cannot be +* resolved, a new instance should be created. +* @property {number} index - The index at which to add the new dataset. Defaults to the +* end of the current dataset if not explicitly set or invalid. +* @property {Boolean} destroy - If `purge` is `true`, this will +* [destroy]{@link module:enyo/Model~Model#destroy} any models that are +* [removed]{@link module:enyo/Collection~Collection#remove}. +* @property {Object} modelOptions - When instancing a model, this +* [object]{@glossary Object} will be passed to the constructor as its `options` +* parameter. +*/ + +/** +* The configuration options for [remove()]{@link module:enyo/Collection~Collection#remove}. For +* complete descriptions of the options and their defaults, see +* {@link module:enyo/Collection~Options}. Note that some properties have different +* meanings in different contexts. Please review the descriptions below to see +* how each property is used in this context. +* +* @typedef {module:enyo/Collection~Options} module:enyo/Collection~RemoveOptions +* @property {Boolean} silent - Emit [events]{@glossary event} and notifications. +* @property {Boolean} commit - [Commit]{@link module:enyo/Collection~Collection#commit} changes to the +* [collection]{@link module:enyo/Collection~Collection} after completing the +* [remove]{@link module:enyo/Collection~Collection#remove} operation. +* @property {Boolean} complete - Remove the [model]{@link module:enyo/Model~Model} from the +* [store]{@link module:enyo/Collection~Collection#store} as well as the collection. +* @property {Boolean} destroy - [Destroy]{@link module:enyo/Model~Model#destroy} models +* that are removed from the collection. +*/ + +/** +* The configurable options for [fetch()]{@link module:enyo/Collection~Collection#fetch}, +* [commit()]{@link module:enyo/Collection~Collection#commit}, and [destroy()]{@link module:enyo/Collection~Collection#destroy}. +* +* @typedef {module:enyo/Collection~Options} module:enyo/Collection~ActionOptions +* @property {module:enyo/Collection~Collection~Success} success - The callback executed upon successful +* completion. +* @property {module:enyo/Collection~Collection~Error} error - The callback executed upon a failed attempt. +*/ + +/** +* @callback module:enyo/Collection~Collection~Success +* @param {module:enyo/Collection~Collection} collection - The [collection]{@link module:enyo/Collection~Collection} +* that is returning successfully. +* @param {module:enyo/Collection~ActionOptions} - opts The original options passed to the action method +* that is returning successfully. +* @param {*} - res The result, if any, returned by the [source]{@link module:enyo/Source~Source} that +* executed it. +* @param {String} source - The name of the [source]{@link module:enyo/Collection~Collection#source} that has +* returned successfully. +*/ + +/** +* @callback module:enyo/Collection~Collection~Error +* @param {module:enyo/Collection~Collection} collection - The [collection]{@link module:enyo/Collection~Collection} +* that is returning an error. +* @param {String} action - The name of the action that failed, one of `'FETCHING'`, +* `'COMMITTING'`, or `'DESTROYING'`. +* @param {module:enyo/Collection~ActionOptions} opts - The original options passed to the +* action method that is returning an error. +* @param {*} res - The result, if any, returned by the [source]{@link module:enyo/Source~Source} +* that executed it. +* @param {String} source - The name of the [source]{@link module:enyo/Collection~Collection#source} +* that has returned an error. +*/ + +/** +* A method used to compare two elements in an {@link module:enyo/Collection~Collection}. Should be +* implemented like callbacks used with [Array.sort()]{@glossary Array.sort}. +* +* @see {@glossary Array.sort} +* @see module:enyo/Collection~Collection#sort +* @see module:enyo/Collection~Collection#comparator +* @callback module:enyo/Collection~Collection~Comparator +* @param {module:enyo/Model~Model} a - The first [model]{@link module:enyo/Model~Model} to compare. +* @param {module:enyo/Model~Model} b - The second model to compare. +* @returns {Number} `-1` if `a` should have the lower index, `0` if they are the same, +* or `1` if `b` should have the lower index. +*/ + +/** +* An array-like structure designed to store instances of {@link module:enyo/Model~Model}. +* +* @class Collection +* @extends module:enyo/Component~Component +* @mixes module:enyo/StateSupport~StateSupport +* @mixes module:enyo/EventEmitter~EventEmitter +* @public +*/ +exports = module.exports = kind( + /** @lends module:enyo/Collection~Collection.prototype */ { + + name: 'enyo.Collection', + + /** + * @private + */ + kind: BaseCollection, + + /** + * @private + */ + + + /** + * Used by various [sources]{@link module:enyo/Collection~Collection#source} as part of the + * [URI]{@glossary URI} from which they may be [fetched]{@link module:enyo/Collection~Collection#fetch}, + * [committed]{@link module:enyo/Collection~Collection#commit}, or [destroyed]{@link module:enyo/Collection~Collection#destroy}. + * Some sources may use this property in other ways. + * + * @see module:enyo/Collection~Collection#getUrl + * @see module:enyo/Source~Source + * @see module:enyo/AjaxSource~AjaxSource + * @see module:enyo/JsonpSource~JsonpSource + * @type {String} + * @default '' + * @public + */ + url: '', + + /** + * Implement this method to be used by [sources]{@link module:enyo/Model~Model#source} to + * dynamically derive the [URI]{@glossary URI} from which they may be + * [fetched]{@link module:enyo/Collection~Collection#fetch}, [committed]{@link module:enyo/Collection~Collection#commit}, + * or [destroyed]{@link module:enyo/Collection~Collection#destroy}. Some + * [sources]{@link module:enyo/Collection~Collection#source} may use this property in other ways. + * Note that if this method is implemented, the [url]{@link module:enyo/Collection~Collection#url} + * property will not be used. + * + * @see module:enyo/Collection~Collection#url + * @see module:enyo/Source~Source + * @see module:enyo/AjaxSource~AjaxSource + * @see module:enyo/JsonpSource~JsonpSource + * @type {Function} + * @default null + * @virtual + * @public + */ + getUrl: null, + + /** + * The [kind]{@glossary kind) of {@link module:enyo/Model~Model} that this + * [collection]{@link module:enyo/Collection~Collection} will contain. This is important to set properly so + * that when [fetching]{@link module:enyo/Collection~Collection#fetch}, the returned data will be instanced + * as the correct model [subkind]{@glossary subkind}. + * + * @type {(module:enyo/Model~Model|String)} + * @default module:enyo/Model~Model + * @public + */ + model: Model, + + /** + * A special type of [array]{@glossary Array} used internally by + * {@link module:enyo/Collection~Collection}. The array should not be modified directly, nor + * should the property be set directly. It is used as a container by the + * collection. If [set]{@link module:enyo/Collection~Collection#set} directly, it will + * [emit]{@link module:enyo/EventEmitter~EventEmitter#emit} a [reset]{@link module:enyo/Collection~Collection#reset} + * event. + * + * @see module:enyo/Collection~Collection#modelsChanged + * @type module:enyo/ModelList~ModelList + * @default null + * @readonly + * @protected + */ + models: null, + + /** + * The current [state]{@link module:enyo/States} of the [collection]{@link module:enyo/Collection~Collection}. + * This value changes automatically and may be observed for more complex state + * monitoring. The default value is [READY]{@link module:enyo/States.READY}. + * @type module:enyo/States + * @default module:enyo/States.READY + * @readonly + * @public + * @see module:enyo/States + * @see module:enyo/StateSupport + */ + status: States.READY, + + /** + * The configurable default [options]{@link module:enyo/Collection~Options}. These values will be + * used to modify the behavior of the [collection]{@link module:enyo/Collection~Collection} unless additional + * options are passed into the methods that use them. When modifying these values in a + * [subkind]{@glossary subkind} of {@link module:enyo/Collection~Collection}, they will be merged with + * existing values. + * + * @type {module:enyo/Collection~Options} + * @public + */ + options: { + merge: true, + silent: false, + purge: false, + parse: false, + create: true, + find: true, + sort: false, + commit: false, + destroy: false, + complete: false, + fetch: false, + modelEvents: true + }, + + /** + * Modifies the structure of data so that it can be used by the + * [add()]{@link module:enyo/Collection~Collection#add} method. This method will only be used + * during initialization or after a successful [fetch]{@link module:enyo/Collection~Collection#fetch} + * if the [parse]{@link module:enyo/Collection~Options#parse} flag is set to `true`. + * It may be used for simple remapping, renaming, or complex restructuring of + * data coming from a [source]{@link module:enyo/Collection~Collection#source} that requires + * modification before it can be added to the [collection]{@link module:enyo/Collection~Collection}. + * This is a virtual method and must be implemented. + * + * @param {*} data - The incoming data passed to the + * [constructor]{@link module:enyo/Collection~Collection#constructor} or returned by a successful + * [fetch]{@link module:enyo/Collection~Collection#fetch}. + * @returns {Array} The properly formatted data to be accepted by the + * [add()]{@link module:enyo/Collection~Collection#add} method. + * @virtual + * @public + */ + parse: function (data) { + return data; + }, + + /** + * Adds data to the [collection]{@link module:enyo/Collection~Collection}. This method can add an + * individual [model]{@link module:enyo/Model~Model} or an [array]{@glossary Array} of models. + * It can splice them into the dataset at a designated index or remove models + * from the existing dataset that are not included in the new one. + * See {@link module:enyo/Collection~AddOptions} for detailed information on the + * configuration options available for this method. This method is heavily + * optimized for batch operations on arrays of models. For better performance, + * ensure that loops do not consecutively call this method but instead + * build an array to pass as the first parameter. + * + * @fires module:enyo/Collection~Collection#add + * @param {(Object|Object[]|module:enyo/Model~Model|module:enyo/Model~Model[])} models The data to add to the + * {@link module:enyo/Collection~Collection} that can be a [hash]{@glossary Object}, an array of + * hashes, an {@link module:enyo/Model~Model} instance, or and array of `Model` instances. + * Note that if the [parse]{@link module:enyo/Collection~Collection#options#parse} configuration + * option is `true`, it will use the returned value as this parameter. + * @param {module:enyo/Collection~AddOptions} [opts] - The configuration options that modify + * the behavior of this method. The default values will be merged with these options + * before evaluating. + * @returns {module:enyo/Model~Model[]} The models that were added, if any. + * @public + */ + add: function (models, opts) { + var loc = this.models + , len = this.length + , ctor = this.model + , options = this.options + , pkey = ctor.prototype.primaryKey + , idx = len + , removedBeforeIdx = 0 + , added, keep, removed, model, attrs, found, id; + + // for backwards compatibility with earlier api standards we allow the + // second paramter to be the index and third param options when + // necessary + !isNaN(opts) && (idx = opts); + arguments.length > 2 && (opts = arguments[2]); + + // normalize options so we have values + opts = opts? utils.mixin({}, [options, opts]): options; + + // our flags + var merge = opts.merge + , purge = opts.purge + , silent = opts.silent + , parse = opts.parse + , find = opts.find + , sort = opts.sort + , commit = opts.commit + , create = opts.create !== false + , modelOpts = opts.modelOptions + , index = opts.index; + + idx = !isNaN(index) ? Math.max(0, Math.min(len, index)) : idx; + + /*jshint -W018 */ + sort && !(typeof sort == 'function') && (sort = this.comparator); + /*jshint +W018 */ + + // for a special case purge to remove records that aren't in the current + // set being added + + if (parse) models = this.parse(models); + + // we treat all additions as an array of additions + !(models instanceof Array) && (models = [models]); + + for (var i=0, end=models.length; i -1) it._waiting.splice(idx, 1); + if (!it._waiting.length) it._waiting = null; + } + + // continue the operation this time with commit false explicitly + if (!it._waiting) { + options.commit = options.source = null; + it.destroy(options); + } + if (opts && opts.success) opts.success(this, opts, res, source); + }; + + options.error = function (source, res) { + + if (it._waiting) { + idx = it._waiting.findIndex(function (ln) { + return (ln instanceof Source ? ln.name : ln) == source; + }); + if (idx > -1) it._waiting.splice(idx, 1); + if (!it._waiting.length) it._waiting = null; + } + + // continue the operation this time with commit false explicitly + if (!it._waiting) { + options.commit = options.source = null; + it.destroy(options); + } + + // we don't bother setting the error state if we aren't waiting because + // it will be cleared to DESTROYED and it would be pointless + else this.errored('DESTROYING', opts, res, source); + }; + + this.set('status', (this.status | States.DESTROYING) & ~States.READY); + + Source.execute('destroy', this, options); + } else if (this.status & States.ERROR) this.errored(this.status, opts); + + // we don't allow the destroy to take place and we don't forcibly break-down + // the collection errantly so there is an opportuniy to resolve the issue + // before we lose access to the collection's content! + return this; + } + + if (this.length && options.destroy) this.empty(options); + + // set the final resting state of this collection + this.set('status', States.DESTROYED); + + sup.apply(this, arguments); + }; + }), + + /** + * This is a virtual method that, when provided, will be used for sorting during + * [add()]{@link module:enyo/Collection~Collection#add} when the `sort` flag is `true` or when the + * [sort()]{@link module:enyo/Collection~Collection#sort} method is called without a passed-in + * [function]{@glossary Function} parameter. + * + * @see module:enyo/Collection~Collection~Comparator + * @type {module:enyo/Collection~Collection~Comparator} + * @default null + * @virtual + * @method + * @public + */ + comparator: null, + + /** + * Used during [add()]{@link module:enyo/Collection~Collection#add} when `create` is `true` and + * the data is a [hash]{@glossary Object}. + * + * @private + */ + prepareModel: function (attrs, opts) { + var Ctor = this.model + , options = this.options + , model; + + attrs instanceof Ctor && (model = attrs); + if (!model) { + opts = opts || {}; + opts.noAdd = true; + model = new Ctor(attrs, null, opts); + } + + if (options.modelEvents) model.on('*', this._modelEvent, this); + + return model; + }, + + /** + * When a [commit]{@link module:enyo/Collection~Collection#commit} has completed successfully, it is returned + * to this method. This method handles special and important behavior; it should not be + * called directly and, when overloading, care must be taken to ensure that the + * super-method is called. This correctly sets the [status]{@link module:enyo/Collection~Collection#status} + * and, in cases where multiple [sources]{@link module:enyo/Collection~Collection#source} were used, it waits + * until all have responded before clearing the [COMMITTING]{@link module:enyo/States.COMMITTING} + * flag. If a [success]{@link module:enyo/Collection~Collection~Success} callback was provided, it will be + * called once for each source. + * + * @param {module:enyo/Collection~Collection~ActionOptions} opts - The original options passed to + * [commit()]{@link module:enyo/Collection~Collection#commit}, merged with the defaults. + * @param {*} [res] - The result provided from the given + * [source]{@link module:enyo/Collection~Collection#source}, if any. This will vary depending + * on the source. + * @param {String} source - The name of the source that has completed successfully. + * @public + */ + committed: function (opts, res, source) { + var idx; + + if (this._waiting) { + idx = this._waiting.findIndex(function (ln) { + return (ln instanceof Source ? ln.name : ln) == source; + }); + if (idx > -1) this._waiting.splice(idx, 1); + if (!this._waiting.length) this._waiting = null; + } + + if (opts && opts.success) opts.success(this, opts, res, source); + + // clear the state + if (!this._waiting) { + this.set('status', (this.status | States.READY) & ~States.COMMITTING); + } + }, + + /** + * When a [fetch]{@link module:enyo/Collection~Collection#fetch} has completed successfully, it is returned + * to this method. This method handles special and important behavior; it should not be + * called directly and, when overloading, care must be taken to ensure that you call the + * super-method. This correctly sets the [status]{@link module:enyo/Collection~Collection#status} and, in + * cases where multiple [sources]{@link module:enyo/Collection~Collection#source} were used, it waits until + * all have responded before clearing the [FETCHING]{@link module:enyo/States.FETCHING} flag. If + * a [success]{@link module:enyo/Collection~Collection~Success} callback was provided, it will be called + * once for each source. + * + * @param {module:enyo/Collection~Collection~ActionOptions} opts - The original options passed to + * [fetch()]{@link module:enyo/Collection~Collection#fetch}, merged with the defaults. + * @param {*} [res] - The result provided from the given + * [source]{@link module:enyo/Collection~Collection#source}, if any. This will vary depending + * on the source. + * @param {String} source - The name of the source that has completed successfully. + * @public + */ + fetched: function (opts, res, source) { + var idx; + + if (this._waiting) { + idx = this._waiting.findIndex(function (ln) { + return (ln instanceof Source ? ln.name : ln) == source; + }); + if (idx > -1) this._waiting.splice(idx, 1); + if (!this._waiting.length) this._waiting = null; + } + + // if there is a result we add it to the collection passing it any per-fetch options + // that will override the defaults (e.g. parse) we don't do that here as it will + // be done in the add method -- also note we reassign the result to whatever was + // actually added and pass that to any other success callback if there is one + if (res) res = this.add(res, opts); + + // now look for an additional success callback + if (opts && opts.success) opts.success(this, opts, res, source); + + // clear the state + if (!this._waiting) { + this.set('status', (this.status | States.READY) & ~States.FETCHING); + } + }, + + /** + * If an error is encountered while [fetching]{@link module:enyo/Collection~Collection#fetch}, + * [committing]{@link module:enyo/Collection~Collection#commit}, or [destroying]{@link module:enyo/Collection~Collection#destroy} + * the [collection]{@link module:enyo/Collection~Collection}, this method will be called. By + * default, it updates the collection's [status]{@link module:enyo/Collection~Collection#status} + * property and then checks to see if there is a provided + * [error handler]{@link module:enyo/Collection~Collection~Error}. If the error handler + * exists, it will be called. + * + * @param {String} action - The name of the action that failed, + * one of `'FETCHING'` or `'COMMITTING'`. + * @param {module:enyo/Collection~Collection~ActionOptions} opts - The options hash originally + * passed along with the original action. + * @param {*} [res] - The result of the requested `action`; varies depending on the + * requested [source]{@link module:enyo/Collection~Collection#source}. + * @param {String} source - The name of the source that has returned an error. + * @public + */ + errored: function (action, opts, res, source) { + var stat; + + // if the error action is a status number then we don't need to update it otherwise + // we set it to the known state value + if (typeof action == 'string') { + + // all built-in errors will pass this as their values are > 0 but we go ahead and + // ensure that no developer used the 0x00 for an error code + stat = States['ERROR_' + action]; + } else stat = action; + + if (isNaN(stat) || !(stat & States.ERROR)) stat = States.ERROR_UNKNOWN; + + // if it has changed give observers the opportunity to respond + this.set('status', (this.status | stat) & ~States.READY); + + // we need to check to see if there is an options handler for this error + if (opts && opts.error) opts.error(this, action, opts, res, source); + }, + + /** + * Overloaded version of the method to call [set()]{@link module:enyo/Collection~Collection#set} + * instead of simply assigning the value. This allows it to + * [notify observers]{@link module:enyo/ObserverSupport} and thus update + * [bindings]{@link module:enyo/BindingSupport#binding} as well. + * + * @see {@link module:enyo/StateSupport~StateSupport#clearError} + * @public + */ + clearError: function () { + return this.set('status', States.READY); + }, + + /** + * @private + */ + _modelEvent: function (model, e) { + switch (e) { + case 'change': + this.emit('change', {model: model}); + break; + case 'destroy': + this.remove(model); + break; + } + }, + + /** + * Responds to changes to the [models]{@link module:enyo/Collection~Collection#models} property. + * + * @see module:enyo/Collection~Collection#models + * @fires module:enyo/Collection~Collection#reset + * @type {module:enyo/ObserverSupport~ObserverSupport~Observer} + * @public + */ + modelsChanged: function (was, is, prop) { + var models = this.models.copy(), + len = models.length; + + if (len != this.length) this.set('length', len); + + this.emit('reset', {models: models, collection: this}); + }, + + /** + * Initializes the [collection]{@link module:enyo/Collection~Collection}. + * + * @param {(Object|Object[]|module:enyo/Model~Model[])} [recs] May be an [array]{@glossary Array} + * of either [models]{@link module:enyo/Model~Model} or [hashes]{@glossary Object} used to + * initialize the [collection]{@link module:enyo/Collection~Collection}, or an [object]{@glossary Object} + * equivalent to the `props` parameter. + * @param {Object} [props] - A hash of properties to apply directly to the + * collection. + * @param {Object} [opts] - A hash. + * @method + * @public + */ + constructor: kind.inherit(function (sup) { + return function (recs, props, opts) { + // opts = opts? (this.options = enyo.mixin({}, [this.options, opts])): this.options; + + // if properties were passed in but not a records array + props = recs && !(recs instanceof Array)? recs: props; + if (props === recs) recs = null; + // initialize our core records + // this.models = this.models || new ModelList(); + !this.models && (this.set('models', new ModelList())); + + // this is backwards compatibility + if (props && props.records) { + recs = recs? recs.concat(props.records): props.records.slice(); + delete props.records; + } + + if (props && props.models) { + recs = recs? recs.concat(props.models): props.models.slice(); + delete props.models; + } + + if (props && props.options) { + this.options = utils.mixin({}, [this.options, props.options]); + delete props.options; + } + + opts = opts? utils.mixin({}, [this.options, opts]): this.options; + + // @TODO: For now, while there is only one property we manually check for it + // if more options arrise that should be configurable this way it may need to + // be modified + opts.fetch && (this.options.fetch = opts.fetch); + + this.length = this.models.length; + this.euid = utils.uid('c'); + + sup.call(this, props); + + typeof this.model == 'string' && (this.model = kind.constructorForKind(this.model)); + this.store = this.store || Store; + recs && recs.length && this.add(recs, opts); + }; + }), + + /** + * @method + * @private + */ + constructed: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + + // automatically attempt a fetch after initialization is complete + if (this.options.fetch) this.fetch(); + }; + }) + +}); + +/** +* @name module:enyo/Collection~Collection.concat +* @static +* @private +*/ +exports.concat = function (ctor, props) { + var proto = ctor.prototype || ctor; + + if (props.options) { + proto.options = utils.mixin({}, [proto.options, props.options]); + delete props.options; + } +}; + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./Component':'enyo/Component','./EventEmitter':'enyo/EventEmitter','./Model':'enyo/Model','./ModelList':'enyo/ModelList','./StateSupport':'enyo/StateSupport','./Source':'enyo/Source','./Store':'enyo/Store','./States':'enyo/States'}],'enyo/master':[function (module,exports,global,require,request){ +require('enyo'); + +var + utils = require('./utils'); +var + Component = require('./Component'), + Signals = require('./Signals'); + +/** +* Default owner assigned to ownerless [UiComponents]{@link module:enyo/UiComponent~UiComponent}, +* to allow such UiComponents to be notified of important system events like window resize. +* +* NOTE: Ownerless [UiComponents]{@link module:enyo/UiComponent~UiComponent} will not be garbage collected unless +* explicitly destroyed, as they will be referenced by `master`. +* +* @module enyo/master +* @private +*/ +var master = module.exports = new Component({ + name: 'master', + notInstanceOwner: true, + eventFlags: {showingOnly: true}, // don't waterfall these events into hidden controls + getId: function () { + return ''; + }, + isDescendantOf: utils.nop, + bubble: function (nom, event) { + //enyo.log('master event: ' + nom); + if (nom == 'onresize') { + // Resize is special; waterfall this message. + // This works because master is a Component, so it waterfalls + // to its owned Components (i.e., master has no children). + master.waterfallDown('onresize', this.eventFlags); + master.waterfallDown('onpostresize', this.eventFlags); + } else { + // All other top-level events are sent only to interested Signal + // receivers. + Signals.send(nom, event); + } + } +}); + +},{'./utils':'enyo/utils','./Component':'enyo/Component','./Signals':'enyo/Signals'}],'enyo/Controller':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Controller~Controller} kind. +* @module enyo/Controller +*/ + +var + kind = require('./kind'), + utils = require('./utils'); + +var + MultipleDispatchComponent = require('./MultipleDispatchComponent'); + +/** +* {@link module:enyo/Controller~Controller} is the base [kind]{@glossary kind} for all +* controllers in Enyo. An abstract kind, `enyo.Controller` is a +* [delegate]{@glossary delegate}/[component]{@link module:enyo/Component~Component} that +* is designed to be a proxy for information. +* +* @class Controller +* @extends module:enyo/MultipleDispatchComponent~MultipleDispatchComponent +* @public +*/ +module.exports = kind( + /** @lends module:enyo/Controller~Controller.prototype */ { + + name: 'enyo.Controller', + + /** + * @private + */ + kind: MultipleDispatchComponent, + + /** + * Set this flag to `true` to make this [controller]{@link module:enyo/Controller~Controller} + * available globally, when instanced. When set to `true`, even the + * [owner]{@link module:enyo/Component~Component#owner} (if any) cannot + * [destroy]{@link module:enyo/Component~Component#destroy} it. + * + * @type {Boolean} + * @default false + * @public + */ + global: false, + + /** + * The default source of information for all instances of {@link module:enyo/Controller~Controller} + * and its [subkinds]{@glossary subkind}. In some cases, this will be a + * [computed property]{@link module:enyo/ComputedSupport} to facilitate overloading. + * It may contain any type of data. + * + * @type {*} + * @default null + * @public + */ + data: null, + + /** + * @method + * @private + */ + constructor: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + // don't attempt to set a controller globally without a name + if (this.global && this.name) { + utils.setPath.call(global, this.name, this); + } + }; + }), + _isController: true +}); + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./MultipleDispatchComponent':'enyo/MultipleDispatchComponent'}],'enyo/dispatcher':[function (module,exports,global,require,request){ +/** +* Contains dispatcher methods +* @module enyo/dispatcher +* @private +*/ +require('enyo'); + +var + logger = require('./logger'), + master = require('./master'), + utils = require('./utils'), + platform = require('./platform'); + +var + Dom = require('./dom'); + +/** + * An [object]{@glossary Object} describing the the last known coordinates of the cursor or + * user-interaction point in touch environments. + * + * @typedef {Object} module:enyo/dispatcher~CursorCoordinates + * @property {Number} clientX - The horizontal coordinate within the application's client area. + * @property {Number} clientY - The vertical coordinate within the application's client area. + * @property {Number} pageX - The X coordinate of the cursor relative to the viewport, including any + * scroll offset. + * @property {Number} pageY - The Y coordinate of the cursor relative to the viewport, including any + * scroll offset. + * @property {Number} screenX - The X coordinate of the cursor relative to the screen, not including + * any scroll offset. + * @property {Number} screenY - The Y coordinate of the cursor relative to the screen, not including + * any scroll offset. + */ + +/** +* @private +*/ + +/** +* @private +*/ +var dispatcher = module.exports = dispatcher = { + + $: {}, + + /** + * These events come from document + * + * @private + */ + events: ["mousedown", "mouseup", "mouseover", "mouseout", "mousemove", "mousewheel", + "click", "dblclick", "change", "keydown", "keyup", "keypress", "input", + "paste", "copy", "cut", "webkitTransitionEnd", "transitionend", "webkitAnimationEnd", "animationend", + "webkitAnimationStart", "animationstart", "webkitAnimationIteration", "animationiteration"], + + /** + * These events come from window + * + * @private + */ + windowEvents: ["resize", "load", "unload", "message", "hashchange", "popstate", "focus", "blur"], + + /** + * Feature plugins (aka filters) + * + * @private + */ + features: [], + + /** + * @private + */ + connect: function() { + var d = dispatcher, i, n; + for (i=0; (n=d.events[i]); i++) { + d.listen(document, n); + } + for (i=0; (n=d.windowEvents[i]); i++) { + // Chrome Packaged Apps don't like "unload" + if(n === "unload" && + (typeof global.chrome === "object") && + global.chrome.app) { + continue; + } + + d.listen(window, n); + } + }, + + /** + * @private + */ + listen: function(inListener, inEventName, inHandler) { + if (inListener.addEventListener) { + this.listen = function(inListener, inEventName, inHandler) { + inListener.addEventListener(inEventName, inHandler || dispatch, false); + }; + } else { + //enyo.log("IE8 COMPAT: using 'attachEvent'"); + this.listen = function(inListener, inEvent, inHandler) { + inListener.attachEvent("on" + inEvent, function(e) { + e.target = e.srcElement; + if (!e.preventDefault) { + e.preventDefault = this.iePreventDefault; + } + return (inHandler || dispatch)(e); + }); + }; + } + this.listen(inListener, inEventName, inHandler); + }, + + /** + * @private + */ + stopListening: function(inListener, inEventName, inHandler) { + if (inListener.addEventListener) { + this.stopListening = function(inListener, inEventName, inHandler) { + inListener.removeEventListener(inEventName, inHandler || dispatch, false); + }; + } else { + //enyo.log("IE8 COMPAT: using 'detachEvent'"); + this.stopListening = function(inListener, inEvent, inHandler) { + inListener.detachEvent("on" + inEvent, inHandler || dispatch); + }; + } + this.stopListening(inListener, inEventName, inHandler); + }, + + /** + * Fires an event for Enyo to listen for. + * + * @private + */ + dispatch: function(e) { + // Find the control who maps to e.target, or the first control that maps to an ancestor of e.target. + var c = this.findDispatchTarget(e.target) || this.findDefaultTarget(); + // Cache the original target + e.dispatchTarget = c; + // support pluggable features return true to abort immediately or set e.preventDispatch to avoid processing. + for (var i=0, fn; (fn=this.features[i]); i++) { + if (fn.call(this, e) === true) { + return; + } + } + if (c && !e.preventDispatch) { + return this.dispatchBubble(e, c); + } + }, + + /** + * Takes an event target and finds the corresponding Enyo control. + * + * @private + */ + findDispatchTarget: function(inNode) { + var t, n = inNode; + // FIXME: Mozilla: try/catch is here to squelch "Permission denied to access property xxx from a non-chrome context" + // which appears to happen for scrollbar nodes in particular. It's unclear why those nodes are valid targets if + // it is illegal to interrogate them. Would like to trap the bad nodes explicitly rather than using an exception block. + try { + while (n) { + if ((t = this.$[n.id])) { + // there could be multiple nodes with this id, the relevant node for this event is n + // we don't push this directly to t.node because sometimes we are just asking what + // the target 'would be' (aka, calling findDispatchTarget from handleMouseOverOut) + t.eventNode = n; + break; + } + n = n.parentNode; + } + } catch(x) { + logger.log(x, n); + } + return t; + }, + + /** + * Returns the default Enyo control for events. + * + * @private + */ + findDefaultTarget: function() { + return master; + }, + + /** + * @private + */ + dispatchBubble: function(e, c) { + var type = e.type; + type = e.customEvent ? type : "on" + type; + return c.bubble(type, e, c); + } +}; + +/** +* Called in the context of an event. +* +* @private +*/ +dispatcher.iePreventDefault = function() { + try { + this.returnValue = false; + } + catch(e) { + // do nothing + } +}; + +/** +* @private +*/ +function dispatch (inEvent) { + return dispatcher.dispatch(inEvent); +} + +/** +* @private +*/ +dispatcher.bubble = function(inEvent) { + // '|| window.event' clause needed for IE8 + var e = inEvent || global.event; + if (e) { + // We depend on e.target existing for event tracking and dispatching. + if (!e.target) { + e.target = e.srcElement; + } + dispatcher.dispatch(e); + } +}; + +// This string is set on event handlers attributes for DOM elements that +// don't normally bubble (like onscroll) so that they can participate in the +// Enyo event system. +dispatcher.bubbler = "enyo.bubble(arguments[0])"; + +// The code below helps make Enyo compatible with Google Packaged Apps +// Content Security Policy(http://developer.chrome.com/extensions/contentSecurityPolicy.html), +// which, among other things, forbids the use of inline scripts. +// We replace online scripting with equivalent means, leaving enyo.bubbler +// for backward compatibility. +(function() { + var bubbleUp = function() { + dispatcher.bubble(arguments[0]); + }; + + /** + * Makes given events bubble on a specified Enyo control. + * + * @private + */ + dispatcher.makeBubble = function() { + var args = Array.prototype.slice.call(arguments, 0), + control = args.shift(); + + if((typeof control === "object") && (typeof control.hasNode === "function")) { + utils.forEach(args, function(event) { + if(this.hasNode()) { + dispatcher.listen(this.node, event, bubbleUp); + } + }, control); + } + }; + + /** + * Removes the event listening and bubbling initiated by + * [enyo.makeBubble()]{@link enyo.makeBubble} on a specific control. + * + * @private + */ + dispatcher.unmakeBubble = function() { + var args = Array.prototype.slice.call(arguments, 0), + control = args.shift(); + + if((typeof control === "object") && (typeof control.hasNode === "function")) { + utils.forEach(args, function(event) { + if(this.hasNode()) { + dispatcher.stopListening(this.node, event, bubbleUp); + } + }, control); + } + }; +})(); + +/** +* @private +*/ +// FIXME: we need to create and initialize dispatcher someplace else to allow overrides +Dom.requiresWindow(dispatcher.connect); + +/** +* Generates a tapped event for a raw-click event. +* +* @private +*/ +dispatcher.features.push( + function (e) { + if ("click" === e.type) { + if (e.clientX === 0 && e.clientY === 0 && !e.detail) { + // this allows the click to dispatch as well + // but note the tap event will fire first + var cp = utils.clone(e); + cp.type = "tap"; + cp.preventDefault = utils.nop; + dispatcher.dispatch(cp); + } + } + } +); + +/** +* Instead of having multiple `features` pushed and handled in separate methods +* for these events, we handle them uniformly here to expose the last known +* interaction coordinates as accurately as possible. +* +* @private +*/ +var _xy = {}; +dispatcher.features.push( + function (e) { + if ( + (e.type == "mousemove") || + (e.type == "tap") || + (e.type == "click") || + (e.type == "touchmove") + ) { + _xy.clientX = e.clientX; + _xy.clientY = e.clientY; + // note only ie8 does not support pageX/pageY + _xy.pageX = e.pageX; + _xy.pageY = e.pageY; + // note ie8 and opera report these values incorrectly + _xy.screenX = e.screenX; + _xy.screenY = e.screenY; + } + } +); + +/** +* Retrieves the last known coordinates of the cursor or user-interaction point +* in touch environments. Returns an immutable object with the `clientX`, +* `clientY`, `pageX`, `pageY`, `screenX`, and `screenY` properties. It is +* important to note that IE8 and Opera have improper reporting for the +* `screenX` and `screenY` properties (they both use CSS pixels as opposed to +* device pixels) and IE8 has no support for the `pageX` and `pageY` properties, +* so they are facaded. +* +* @returns {module:enyo/dispatcher~CursorCoordinates} An [object]{@glossary Object} describing the +* the last known coordinates of the cursor or user-interaction point in touch environments. +* @public +*/ +dispatcher.getPosition = function () { + var p = utils.clone(_xy); + // if we are in ie8 we facade the _pageX, pageY_ properties + if (platform.ie < 9) { + var d = (document.documentElement || document.body.parentNode || document.body); + p.pageX = (p.clientX + d.scrollLeft); + p.pageY = (p.clientY + d.scrollTop); + } + return p; +}; + + +/** +* Key mapping feature: Adds a `keySymbol` property to key [events]{@glossary event}, +* based on a global key mapping. Use +* [enyo.dispatcher.registerKeyMap()]{@link enyo.dispatcher.registerKeyMap} to add +* keyCode-to-keySymbol mappings via a simple hash. This method may be called +* multiple times from different libraries to mix different maps into the global +* mapping table; if conflicts arise, the last-in wins. +* +* ``` +* enyo.dispatcher.registerKeyMap({ +* 415 : 'play', +* 413 : 'stop', +* 19 : 'pause', +* 412 : 'rewind', +* 417 : 'fastforward' +* }); +* ``` +* +* @private +*/ +dispatcher.features.push(function(e) { + if ((e.type === 'keydown') || (e.type === 'keyup') || (e.type === 'keypress')) { + e.keySymbol = this.keyMap[e.keyCode]; + // Dispatch key events to be sent via Signals + var c = this.findDefaultTarget(); + if (e.dispatchTarget !== c) { + this.dispatchBubble(e, c); + } + } +}); + +utils.mixin(dispatcher, { + keyMap: {}, + registerKeyMap: function(map) { + utils.mixin(this.keyMap, map); + } +}); + + +/** +* Event modal capture feature. Capture events to a specific control via +* [enyo.dispatcher.capture(inControl, inShouldForward)]{@linkcode enyo.dispatcher.capture}; +* release events via [enyo.dispatcher.release()]{@link enyo.dispatcher.release}. +* +* @private +*/ +dispatcher.features.push(function(e) { + if (this.captureTarget) { + var c = e.dispatchTarget; + var eventName = (e.customEvent ? '' : 'on') + e.type; + var handlerName = this.captureEvents[eventName]; + var handlerScope = this.captureHandlerScope || this.captureTarget; + var handler = handlerName && handlerScope[handlerName]; + var shouldCapture = handler && !(c && c.isDescendantOf && c.isDescendantOf(this.captureTarget)); + if (shouldCapture) { + var c1 = e.captureTarget = this.captureTarget; + // NOTE: We do not want releasing capture while an event is being processed to alter + // the way the event propagates. Therefore decide if the event should forward + // before the capture target receives the event (since it may release capture). + e.preventDispatch = handler && handler.apply(handlerScope, [c1, e]) && !this.autoForwardEvents[e.type]; + } + } +}); + +// +// NOTE: This object is a plug-in; these methods should +// be called on `enyo.dispatcher`, and not on the plug-in itself. +// +utils.mixin(dispatcher, { + + /** + * @private + */ + autoForwardEvents: {leave: 1, resize: 1}, + + /** + * @private + */ + captures: [], + + /** + * Captures [events]{@glossary event} for `inTarget`, where `inEvents` is specified as a + * hash of event names mapped to callback handler names to be called on `inTarget` (or, + * optionally, `inScope`). The callback is called when any of the captured events are + * dispatched outside of the capturing control. Returning `true` from the callback stops + * dispatch of the event to the original `dispatchTarget`. + * + * @private + */ + capture: function(inTarget, inEvents, inScope) { + var info = {target: inTarget, events: inEvents, scope: inScope}; + this.captures.push(info); + this.setCaptureInfo(info); + }, + + /** + * Removes the specified target from the capture list. + * + * @private + */ + release: function(inTarget) { + for (var i = this.captures.length - 1; i >= 0; i--) { + if (this.captures[i].target === inTarget) { + this.captures.splice(i,1); + this.setCaptureInfo(this.captures[this.captures.length-1]); + break; + } + } + }, + + /** + * Sets the information for a captured {@glossary event}. + * + * @private + */ + setCaptureInfo: function(inInfo) { + this.captureTarget = inInfo && inInfo.target; + this.captureEvents = inInfo && inInfo.events; + this.captureHandlerScope = inInfo && inInfo.scope; + } +}); + + +(function () { + /** + * Dispatcher preview feature + * + * Allows {@link module:enyo/Control~Control} ancestors of the {@glossary event} target + * a chance (eldest first) to react by implementing `previewDomEvent`. + * + * @private + */ + var fn = 'previewDomEvent'; + var preview = + /** @lends enyo.dispatcher.features */ { + + /** + * @private + */ + feature: function(e) { + preview.dispatch(e, e.dispatchTarget); + }, + + /** + * @returns {(Boolean|undefined)} Handlers return `true` to abort preview and prevent default + * event processing. + * + * @private + */ + dispatch: function(evt, control) { + var i, l, + lineage = this.buildLineage(control); + for (i=0; (l=lineage[i]); i++) { + if (l[fn] && l[fn](evt) === true) { + evt.preventDispatch = true; + return; + } + } + }, + + /** + * We ascend, making a list of Enyo [controls]{@link module:enyo/Control~Control}. + * + * Note that a control is considered to be its own ancestor. + * + * @private + */ + buildLineage: function(control) { + var lineage = [], + c = control; + while (c) { + lineage.unshift(c); + c = c.parent; + } + return lineage; + } + }; + + dispatcher.features.push(preview.feature); +})(); + +},{'./logger':'enyo/logger','./master':'enyo/master','./utils':'enyo/utils','./platform':'enyo/platform','./dom':'enyo/dom'}],'enyo/AnimationSupport/AnimationInterfaceSupport':[function (module,exports,global,require,request){ +require('enyo'); + +var + kind = require('../kind'), + animator = require('./Core'), + frame = require('./Frame'), + utils = require('../utils'), + dispatcher = require('../dispatcher'); + +var extend = kind.statics.extend; + +kind.concatenated.push('animation'); + +var AnimationInterfaceSupport = { + + /** + * @private + */ + patterns: [], + + /** + * @private + */ + checkX: 0, + + /** + * @private + */ + checkY: 0, + + /** + * @private + */ + deltaX: 0, + + /** + * @private + */ + deltaY: 0, + + /** + * @private + */ + translateX: 0, + + /** + * @private + */ + translateY: 0, + + /** + * @private + */ + scrollValue: 0, + + /** + * @private + */ + deltaValueX: 0, + + /** + * @private + */ + deltaValueY: 0, + + /** + * @private + */ + checkDragStartX: 0, + + /** + * @private + */ + checkDragStartY: 0, + + /** + * @private + */ + deltaDragValueX: 0, + + /** + * @private + */ + deltaDragValueY: 0, + + /** + * @private + */ + setAnimateOne: 0, + + /** + * @private + */ + setAnimateTwo: 0, + + /** + * @private + */ + setAnimateThree: 0, + + /** + * @private + */ + eventArray: [ + "dragstart", + "dragend", + "drag", + "flick", + "down", + "move", + "scroll", + "mousewheel", + "touchstart", + "touchmove", + "touchend" + ], + + /** + * @public + */ + initialize: function() { + var i, eventArrayLength = this.eventArray.length; + for (i = 0; i < eventArrayLength; i++) { + dispatcher.listen(this.node, this.eventArray[i], this.bindSafely(this.detectTheEvent)); + } + }, + + /** + * @public + */ + detectTheEvent: function(inSender, inEvent) { + var eventType = inSender.type; + switch (eventType) { + case "dragstart": + this.touchDragStart(inSender, inEvent, inSender.pageX, inSender.pageY); + break; + case "drag": + this.touchDragMove(inSender, inEvent, inSender.pageX, inSender.pageY); + break; + case "dragend": + this.touchDragEnd(inSender, inEvent); + break; + case "flick": + this.handleMyEvent(inSender, inEvent); + break; + case "down": + this.handleMyEvent(inSender, inEvent); + break; + case "move": + this.handleMyEvent(inSender, inEvent); + break; + case "scroll": + this.scrollEvent(inSender, inEvent); + break; + case "mousewheel": + this.mousewheelEvent(inSender, inEvent); + break; + case "touchstart": + this.touchDragStart(inSender, inEvent, inSender.targetTouches[0].pageX, inSender.targetTouches[0].pageY); + break; + case "touchmove": + this.touchDragMove(inSender, inEvent, inSender.targetTouches[0].pageX, inSender.targetTouches[0].pageY); + break; + case "touchend": + this.touchDragEnd(inSender, inEvent); + break; + default: + this.handleMyEvent(inSender, inEvent); + } + }, + + /** + * @public + */ + touchDragStart: function(inSender, inEvent, x, y) { + this.checkX = x; + this.checkY = y; + }, + + /** + * @public + */ + touchDragMove: function(inSender, inEvent, x, y) { + var currentX = x, + currentY = y; + + if (currentX != 0 || currentY != 0) { + this.deltaValueX = this.checkX - currentX; + + this.checkX = currentX; // set the initial position to the current position while moving + + this.deltaValueY = this.checkY - currentY; + + this.checkY = currentY; // set the initial position to the current position while moving + + //call commonTasks function with delta values + this.translateX = this.translateX + this.deltaValueX; + this.translateY = this.translateY + this.deltaValueY; + + this.setAnimateOne = this.translateX; + this.setAnimateTwo = this.translateX; + this.setAnimateThree = this.translateY; + + } + + }, + + /** + * @public + */ + touchDragEnd: function(inSender, inEvent, x, y) { + this.checkX = 0; + this.checkY = 0; + this.deltaValueX = 0; + this.deltaValueY = 0; + }, + + /** + * @public + */ + scrollEvent: function(inSender, inEvent) { + var delta = inSender.deltaY, + scrollTop = inSender.target.scrollTop, + scrollLeft = inSender.target.scrollLeft; + + if (this.scrollValue === 0) { + this.scrollValue = inSender.target.scrollTop; + } + + delta = inSender.target.scrollTop - this.scrollValue; + + this.deltaX = scrollLeft - this.deltaX; + this.deltaY = scrollTop - this.deltaY; + this.scrollValue = scrollTop; + + this.translateX = this.translateX + this.deltaX; + this.translateY = this.translateY + this.deltaY; + + //call commonTasks function with delta values + this.setAnimateOne = delta; + this.setAnimateTwo = this.translateX; + this.setAnimateThree = this.translateY; + + + this.deltaX = scrollLeft; + this.deltaY = scrollTop; + }, + + /** + * @public + */ + mousewheelEvent: function(inSender, inEvent) { + var delta = inSender.deltaY, + deltaX = inSender.wheelDeltaX, + deltaY = inSender.wheelDeltaY; + + this.translateX = this.translateX + deltaX; + this.translateY = this.translateY + deltaY; + + //call commonTasks function with delta values + this.setAnimateOne = delta; + this.setAnimateTwo = (-1 * (this.translateX)); + this.setAnimateThree = (-1 * (this.translateY)); + if (patterns[0].name === "Slideable") { + this.setAnimateTwo = this.setAnimateThree; + } + + + }, + + /** + * @public + */ + commonTasks: function(delta, deltax, deltay) { + var patternsLength = patterns.length; + if (delta !== 0) { + delta = delta / Math.abs(delta); + } + //Call specific interface + for (var i = 0; i < patternsLength; i++) { + if (patterns[i].name === "Fadeable") { + patterns[i].fadeByDelta.call(this, delta); + } else if (patterns[i].name === "Flippable") { + patterns[i].doFlip.call(this, delta); + } else if (patterns[i].name === "Slideable") { + if (this.parallax === true) { + for (var j = 0; j < this.children.length; j++) { + var current = this.children[j]; + animator.trigger(current); + patterns[i].slide.call(current, (-1 * deltax / current.speed), (-1 * deltay / current.speed), 0); + current.start(true); + } + } else { + patterns[i].slide.call(this, (-1 * deltax), (-1 * deltay), 0); + } + } + if (patterns[i].name !== "Slideable") { + this.setAnimateOne = 0; + this.setAnimateTwo = 0; + this.setAnimateThree = 0; + } + } + this.start(true); + }, + + /** + * @public + */ + handleMyEvent: function(inSender, inEvent) { + /*TODO:*/ + }, + + /** + * @public + */ + commitAnimation: function(x, y, z) { + var i, len; + + if (patterns && Object.prototype.toString.call(patterns) === "[object Array]") { + len = patterns.length; + for (i = 0; i < len; i++) { + if (typeof patterns[i].triggerEvent === 'function') { + //patterns[i].triggerEvent(); + + } + this.commonTasks(this.setAnimateOne, this.setAnimateTwo, this.setAnimateThree); + + } + } + }, + + /** + * @private + */ + rendered: kind.inherit(function(sup) { + return function() { + sup.apply(this, arguments); + this.initialize(); + }; + }) +}; + +module.exports = AnimationInterfaceSupport; + +/** + Hijacking original behaviour as in other Enyo supports. +*/ +var sup = kind.concatHandler; + +/** + * @private + */ +kind.concatHandler = function(ctor, props, instance) { + sup.call(this, ctor, props, instance); + var aPattern = props.pattern; + if (aPattern && Object.prototype.toString.call(aPattern) === "[object Array]") { + var proto = ctor.prototype || ctor; + extend(AnimationInterfaceSupport, proto); + + patterns = aPattern; + var len = patterns.length; + for (var i = 0; i < len; i++) { + extend(patterns[i], proto); + } + animator.register(proto); + } +}; + +},{'../kind':'enyo/kind','./Core':'enyo/AnimationSupport/Core','./Frame':'enyo/AnimationSupport/Frame','../utils':'enyo/utils','../dispatcher':'enyo/dispatcher'}],'enyo/AccessibilitySupport':[function (module,exports,global,require,request){ +/** +* Mixin for adding WAI-ARIA attributes to controls +* +* @module enyo/AccessibilitySupport +*/ + +var + dispatcher = require('../dispatcher'), + kind = require('../kind'), + platform = require('../platform'), + utils = require('../utils'); + +var defaultObservers = [ + {from: 'accessibilityDisabled', method: function () { + this.setAriaAttribute('aria-hidden', this.accessibilityDisabled ? 'true' : null); + }}, + {from: 'accessibilityLive', method: function () { + var live = this.accessibilityLive === true && 'assertive' || this.accessibilityLive || null; + this.setAriaAttribute('aria-live', live); + }}, + {path: ['accessibilityAlert', 'accessibilityRole'], method: function () { + var role = this.accessibilityAlert && 'alert' || this.accessibilityRole || null; + this.setAriaAttribute('role', role); + }}, + {path: ['content', 'accessibilityHint', 'accessibilityLabel', 'tabIndex'], method: function () { + var focusable = this.accessibilityLabel || this.content || this.accessibilityHint || false, + prefix = this.accessibilityLabel || this.content || null, + label = this.accessibilityHint && prefix && (prefix + ' ' + this.accessibilityHint) || + this.accessibilityHint || + this.accessibilityLabel || + null; + + this.setAriaAttribute('aria-label', label); + + // A truthy or zero tabindex will be set directly + if (this.tabIndex || this.tabIndex === 0) { + this.setAriaAttribute('tabindex', this.tabIndex); + } + // The webOS browser will only read nodes with a non-null tabindex so if the node has + // readable content, make it programmably focusable. + else if (focusable && this.tabIndex === undefined && platform.webos) { + this.setAriaAttribute('tabindex', -1); + } + // Otherwise, remove it + else { + this.setAriaAttribute('tabindex', null); + } + }} +]; + +/** +* Prevents browser-initiated scrolling contained controls into view when those controls are +* explicitly focus()'ed. +* +* @private +*/ +function preventScroll (node) { + if (node) { + dispatcher.listen(node, 'scroll', function () { + node.scrollTop = 0; + node.scrollLeft = 0; + }); + } +} + +function updateAriaAttributes (all) { + var i, l, obs; + + for (i = 0, l = this._ariaObservers.length; i < l; i++) { + obs = this._ariaObservers[i]; + if ((all || obs.pending) && obs.method) { + obs.method(); + obs.pending = false; + } + } +} + +function registerAriaUpdate (obj) { + var fn; + if (!obj.pending) { + obj.pending = true; + fn = this.bindSafely(updateAriaAttributes); + if (!this.accessibilityDefer) { + fn(); + } else { + this.startJob('updateAriaAttributes', fn, 16); + } + } +} + +function toAriaAttribute (from, to) { + var value = this[from]; + this.setAriaAttribute(to, value === undefined ? null : value); +} + +function staticToAriaAttribute (to, value) { + this.setAriaAttribute(to, value); +} + +function initAriaObservers (control) { + var conf = control._ariaObservers, + i, l, fn; + + control._ariaObservers = []; + for (i = 0, l = defaultObservers.length; i < l; i++) { + initAriaObserver(control, defaultObservers[i]); + } + if (conf) { + for (i = 0, l = conf.length; i < l; i++) { + initAriaObserver(control, conf[i]); + } + } + + // setup disabled observer and kickoff first run of observers + fn = updateAriaAttributes.bind(control, true); + control.addObserver('accessibilityDisabled', fn); + fn(); +} + +function initAriaObserver (control, c) { + var + // path can either source from 'path' or 'from' (for binding-style configs) + path = c.path || c.from, + + // method is either: + // 'method', if it exists, or + // staticToAriaAttribute if 'to' and 'value' exist - static binding-style config, or + // toAriaAttribute if a 'to' path exists - binding-style config + method = c.method && control.bindSafely(c.method) || + !path && c.to && c.value !== undefined && control.bindSafely(staticToAriaAttribute, c.to, c.value) || + c.to && control.bindSafely(toAriaAttribute, path, c.to) || + null, + + // import the relevant and pre-validated parts into the instance-level config + config = { + path: path, + method: method, + pending: false + }, + + // pre-bind the register method as it's used multiple times when 'path' is an array + fn = registerAriaUpdate.bind(control, config), + + // iterator + l; + + control._ariaObservers.push(config); + if (utils.isArray(path)) { + for (l = path.length - 1; l >= 0; --l) { + control.addObserver(path[l], fn); + } + } + else if (path) { + control.addObserver(path, fn); + } +} + +/** +* @mixin +*/ +var AccessibilitySupport = { + + /** + * @private + */ + name: 'enyo.AccessibilitySupport', + + /** + * AccessibilityLabel is used for accessibility voice readout. + * If accessibilityLabel is set, screen reader reads the label when control is focused. + * + * @type {String} + * @default '' + * @public + */ + accessibilityLabel: '', + + /** + * AccessibilityHint is used for additional information of control. + * If accessibilityHint is set and content exists, screen reader + * reads accessibilityHint with content when control is focused. + * + * @type {String} + * @default '' + * @public + */ + accessibilityHint: '', + + /** + * The `role` of the control. May be superceded by a truthy `accessibilityAlert` value. + * + * @type {String} + * @default '' + * @public + */ + accessibilityRole: '', + + /** + * AccessibilityAlert is for alert message or page description. + * If accessibilityAlert is true, aria role will be set to "alert" and + * screen reader will automatically reads content or accessibilityLabel + * regardless focus. + * Note that if you use accessibilityAlert, previous role will be + * replaced with "alert" role. + * + * Range: [`true`, `false`] + * - true: screen reader automatically reads label regardless focus. + * - false: screen reader reads label with focus. + * + * @type {Boolean} + * @default false + * @public + */ + accessibilityAlert: false, + + /** + * AccessibilityLive is for dynamic content which updates without a page reload. + * If AccessibilityLive is true, screen reader will read content or accessibilityLabel + * when it changed. + * + * Range: [`true`, `false`] + * - true: screen reader reads content when it changed. + * - false: screen reader reads content with focus. + * + * @type {Boolean} + * @default false + * @public + */ + accessibilityLive: false, + + /** + * AccessibilityDisabled prevents VoiceReadout. + * If accessibilityDisabled is true, screen reader doesn't read any label for the control. + * Note that this is not working on HTML form elements which can get focus without tabindex. + * + * Range: [`true`, `false`] + * + * @type {Boolean} + * @default false + * @public + */ + accessibilityDisabled: false, + + /** + * When true, `onscroll` events will be observed and scrolling prevented by resetting the + * `scrollTop` and `scrollLeft` of the node. This prevents inadvertent layout issues introduced + * by the browser scrolling contained controls into view when `focus()`'ed. + * + * @type {Boolean} + * @default false + * @public + */ + accessibilityPreventScroll: false, + + /** + * Sets the `tabindex` of the control. When `undefined` on webOS, it will be set to -1 to enable + * screen reading. A value of `null` (or `undefined` on non-webOS) ensures that no `tabindex` is + * set. + * + * @type {Number} + * @default undefined + * @public + */ + + /** + * @method + * @private + */ + create: kind.inherit(function (sup) { + return function (props) { + sup.apply(this, arguments); + initAriaObservers(this); + }; + }), + + /** + * If accessibilityDisabled is `false`, sets the node attribute. Otherwise, removes it. + * + * @param {String} name Attribute name + * @param {String} value Attribute value + * @public + */ + setAriaAttribute: function (name, value) { + // if the control is disabled, don't set any aria properties except aria-hidden + if (this.accessibilityDisabled && name != 'aria-hidden') { + value = null; + } + // if the value is defined and non-null, cast it to a String + else if (value !== undefined && value !== null) { + value = String(value); + } + this.setAttribute(name, value); + }, + + /** + * @private + */ + rendered: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + if (this.accessibilityPreventScroll) { + preventScroll(this.hasNode()); + } + }; + }) +}; + +var sup = kind.concatHandler; +kind.concatHandler = function (ctor, props, instance) { + sup.call(this, ctor, props, instance); + + var proto = ctor.prototype || ctor, + ariaObservers = proto._ariaObservers && proto._ariaObservers.slice(), + incoming = props.ariaObservers; + + if (incoming && incoming instanceof Array) { + if (ariaObservers) { + ariaObservers.push.apply(ariaObservers, incoming); + } else { + ariaObservers = incoming.slice(); + } + } + + proto._ariaObservers = ariaObservers; +}; + +module.exports = AccessibilitySupport; +},{'../dispatcher':'enyo/dispatcher','../kind':'enyo/kind','../platform':'enyo/platform','../utils':'enyo/utils'}],'enyo/Control/fullscreen':[function (module,exports,global,require,request){ +var + dispatcher = require('../dispatcher'), + utils = require('../utils'), + ready = require('../ready'), + Signals = require('../Signals'); + +/** +* Normalizes and provides fullscreen support for [controls]{@link module:enyo/Control~Control}, +* based on the [fullscreen]{@glossary fullscreen} API. +* +* @module enyo/Control/fullscreen +* @public +*/ +module.exports = function (Control) { + var floatingLayer = Control.floatingLayer; + var fullscreen = { + + /** + * Reference to the current fullscreen [control]{@link module:enyo/Control~Control}. + * + * @private + */ + fullscreenControl: null, + + /** + * Reference to the current fullscreen element (fallback for platforms + * without native support). + * + * @private + */ + fullscreenElement: null, + + /** + * Reference to that [control]{@link module:enyo/Control~Control} that requested fullscreen. + * + * @private + */ + requestor: null, + + /** + * Native accessor used to get reference to the current fullscreen element. + * + * @private + */ + elementAccessor: + ('fullscreenElement' in document) ? 'fullscreenElement' : + ('mozFullScreenElement' in document) ? 'mozFullScreenElement' : + ('webkitFullscreenElement' in document) ? 'webkitFullscreenElement' : + null, + + /** + * Native accessor used to request fullscreen. + * + * @private + */ + requestAccessor: + ('requestFullscreen' in document.documentElement) ? 'requestFullscreen' : + ('mozRequestFullScreen' in document.documentElement) ? 'mozRequestFullScreen' : + ('webkitRequestFullscreen' in document.documentElement) ? 'webkitRequestFullscreen' : + null, + + /** + * Native accessor used to cancel fullscreen. + * + * @private + */ + cancelAccessor: + ('cancelFullScreen' in document) ? 'cancelFullScreen' : + ('mozCancelFullScreen' in document) ? 'mozCancelFullScreen' : + ('webkitCancelFullScreen' in document) ? 'webkitCancelFullScreen' : + null, + + /** + * Determines whether the platform supports the [fullscreen]{@glossary fullscreen} API. + * + * @returns {Boolean} Returns `true` if platform supports all of the + * [fullscreen]{@glossary fullscreen} API, `false` otherwise. + * @public + */ + nativeSupport: function() { + return (this.elementAccessor !== null && this.requestAccessor !== null && this.cancelAccessor !== null); + }, + + /** + * Normalizes `getFullscreenElement()`. + * + * @public + */ + getFullscreenElement: function() { + return (this.nativeSupport()) ? document[this.elementAccessor] : this.fullscreenElement; + }, + + /** + * Returns current fullscreen [control]{@link module:enyo/Control~Control}. + * + * @public + */ + getFullscreenControl: function() { + return this.fullscreenControl; + }, + + /** + * Normalizes `requestFullscreen()`. + * + * @public + */ + requestFullscreen: function(ctl) { + if (this.getFullscreenControl() || !(ctl.hasNode())) { + return false; + } + + this.requestor = ctl; + + // Only use native request if platform supports all of the API + if (this.nativeSupport()) { + ctl.hasNode()[this.requestAccessor](); + } else { + this.fallbackRequestFullscreen(); + } + + return true; + }, + + /** + * Normalizes `cancelFullscreen()`. + * + * @public + */ + cancelFullscreen: function() { + if (this.nativeSupport()) { + document[this.cancelAccessor](); + } else { + this.fallbackCancelFullscreen(); + } + }, + + /** + * Fallback support for setting fullscreen element (done by browser on platforms with + * native support). + * + * @private + */ + setFullscreenElement: function(node) { + this.fullscreenElement = node; + }, + + /** + * Sets current fullscreen [control]{@link module:enyo/Control~Control}. + * + * @private + */ + setFullscreenControl: function(ctl) { + this.fullscreenControl = ctl; + }, + + /** + * Fallback fullscreen request for platforms without fullscreen support. + * + * @private + */ + fallbackRequestFullscreen: function() { + var control = this.requestor; + + if (!control) { + return; + } + + // Get before node to allow us to exit floating layer to the proper position + control.prevAddBefore = control.parent.controlAtIndex(control.indexInContainer() + 1); + + // Render floating layer if we need to + if (!floatingLayer.hasNode()) { + floatingLayer.render(); + } + + control.addClass('enyo-fullscreen'); + control.appendNodeToParent(floatingLayer.hasNode()); + control.resize(); + + this.setFullscreenControl(control); + this.setFullscreenElement(control.hasNode()); + }, + + /** + * Fallback cancel fullscreen for platforms without fullscreen support. + * + * @private + */ + fallbackCancelFullscreen: function() { + var control = this.fullscreenControl, + beforeNode, + parentNode + ; + + if (!control) { + return; + } + + // Find beforeNode based on _this.addBefore_ and _this.prevAddBefore_ + beforeNode = (control.prevAddBefore) ? control.prevAddBefore.hasNode() : null; + parentNode = control.parent.hasNode(); + control.prevAddBefore = null; + + control.removeClass('enyo-fullscreen'); + + if (!beforeNode) { + control.appendNodeToParent(parentNode); + } else { + control.insertNodeInParent(parentNode, beforeNode); + } + + control.resize(); + + this.setFullscreenControl(null); + this.setFullscreenElement(null); + }, + + /** + * Listens for fullscreen change {@glossary event} and broadcasts it as a + * normalized event. + * + * @private + */ + detectFullscreenChangeEvent: function() { + this.setFullscreenControl(this.requestor); + this.requestor = null; + + // Broadcast change + Signals.send('onFullscreenChange'); + } + }; + + /** + * Normalizes platform-specific fullscreen change [events]{@glossary event}. + * + * @private + */ + ready(function() { + // no need for IE8 fallback, since it won't ever send this event + if (document.addEventListener) { + document.addEventListener('webkitfullscreenchange', utils.bind(fullscreen, 'detectFullscreenChangeEvent'), false); + document.addEventListener('mozfullscreenchange', utils.bind(fullscreen, 'detectFullscreenChangeEvent'), false); + document.addEventListener('fullscreenchange', utils.bind(fullscreen, 'detectFullscreenChangeEvent'), false); + } + }); + + /** + * If this platform doesn't have native support for fullscreen, add an escape handler to mimic + * native behavior. + */ + if(!fullscreen.nativeSupport()) { + dispatcher.features.push( + function(e) { + if (e.type === 'keydown' && e.keyCode === 27) { + fullscreen.cancelFullscreen(); + } + } + ); + } + + return fullscreen; +}; +},{'../dispatcher':'enyo/dispatcher','../utils':'enyo/utils','../ready':'enyo/ready','../Signals':'enyo/Signals'}],'enyo/Scrollable':[function (module,exports,global,require,request){ +var + kind = require('../kind'), + utils = require('../utils'), + dom = require('../dom'), + dispatcher = require('../dispatcher'); + +var + EventEmitter = require('../EventEmitter'), + ScrollMath = require('../ScrollMath'); + +var blockOptions = { + start: true, + end: true, + nearest: true, + farthest: true +}; + +function calcNodeVisibility (nodePos, nodeSize, scrollPos, scrollSize) { + return (nodePos >= scrollPos && nodePos + nodeSize <= scrollPos + scrollSize) + ? 0 + : nodePos - scrollPos > 0 + ? 1 + : nodePos - scrollPos < 0 + ? -1 + : 0; +} + +/** +* Doc +* +* @module enyo/Scrollable +* @public +*/ +module.exports = { + + /** + * @private + */ + name: 'Scrollable', + + /** + * Specifies how to horizontally scroll. Acceptable values are `'scroll'`, `'auto'`, + * `'hidden'`, and `'default'`. The precise effect of the setting is determined by the + * scroll strategy. + * + * @type {String} + * @default 'default' + * @public + */ + horizontal: 'default', + + /** + * Specifies how to vertically scroll. Acceptable values are `'scroll'`, `'auto'`, + * `'hidden'`, and `'default'`. The precise effect of the setting is determined by the + * scroll strategy. + * + * @type {String} + * @default 'default' + * @public + */ + vertical: 'default', + + /** + * The vertical scroll position. + * + * @type {Number} + * @default 0 + * @public + */ + scrollTop: 0, + + /** + * The horizontal scroll position. + * + * @type {Number} + * @default 0 + * @public + */ + scrollLeft: 0, + + /** + * Maximum height of the scroll content. + * + * @type {Number} + * @default null + * @memberof module:enyo/Scroller~Scroller.prototype + * @public + */ + maxHeight: null, + + /** + * Set to `true` to make this [scroller]{@link module:enyo/Scroller~Scroller} select a + * platform-appropriate touch-based scrolling strategy. Note that if you specify a value + * for [strategyKind]{@link module:enyo/Scroller~Scroller#strategyKind}, that will take precedence over + * this setting. + * + * @type {Boolean} + * @default false + * @public + */ + touch: true, + + /** + * TODO: Document. Based on CSSOM View spec (). + * Options: 'smooth', 'instant', maybe 'auto' + * @see {@linkplain http://dev.w3.org/csswg/cssom-view/} + * @see {@linkplain https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView} + * + * @type {String} + * @default 'smooth' + * @public + */ + behavior: 'smooth', + + /** + * TODO: Document. Based on CSSOM View spec (), but modified to add 'nearest' and + * 'farthest' to the possible values. + * Options: 'start', 'end', 'nearest', 'farthest' + * @see {@linkplain http://dev.w3.org/csswg/cssom-view/} + * @see {@linkplain https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView} + * + * @type {String} + * @default 'nearest' + * @public + */ + block: 'farthest', + + /** + * Set to `true` to display a scroll thumb in touch [scrollers]{@link module:enyo/Scroller~Scroller}. + * + * @type {Boolean} + * @default true + * @public + */ + thumb: true, + + /** + * If `true`, mouse wheel may be used to move the [scroller]{@link module:enyo/Scroller~Scroller}. + * + * @type {Boolean} + * @default true + * @public + */ + useMouseWheel: true, + + /** + * TODO: Document + * Experimental + * + * @public + */ + horizontalSnapIncrement: null, + + /** + * TODO: Document + * Experimental + * + * @public + */ + verticalSnapIncrement: null, + + /** + * TODO: Document + * Experimental + * + * @public + */ + suppressMouseEvents: false, + + scrollMath: {kind: ScrollMath}, + + pageMultiplier: 1, + + canScrollX: false, + canScrollY: false, + couldScrollX: false, + couldScrollY: false, + canScrollUp: false, + canScrollDown: false, + canScrollLeft: false, + canScrollRight: false, + + velocity: 0, + + topOffset: 0, + rightOffset: 0, + bottomOffset: 0, + leftOffset: 0, + + mixins: [EventEmitter], + + handlers: { + ondragstart: 'dragstart', + ondragfinish: 'dragfinish', + ondrag: 'drag', + onflick: 'flick', + ondown: 'down', + onmove: 'move', + onmousewheel: 'mousewheel', + onscroll: 'domScroll', + onScroll: 'scroll', + onScrollStart: 'scrollStart', + onScrollStop: 'scrollStop', + onShouldDrag: 'shouldDrag', + onStabilize: 'scroll' + }, + + events: { + onScrollStart: '', + onScroll: '', + onScrollStop: '', + onShouldDrag: '' + }, + + classes: 'enyo-scrollable enyo-fill', + + create: kind.inherit(function (sup) { + return function() { + sup.apply(this, arguments); + + var smc = [ utils.mixin(utils.clone(this.scrollMath), {name: 'scrollMath'}) ], + defProps = this.defaultProps; + + this.defaultProps = {}; + this.accessibilityPreventScroll = true; + + this.createComponents(smc, {isChrome: true, owner: this}); + if (this.scrollControls) { + this.createComponents(this.scrollControls, {isChrome: true, scroller: this}); + } + + this.defaultProps = defProps; + }; + }), + + destroy: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + if (this._suppressing) { + this._resumeMouseEvents(); + } + }; + }), + + showingChangedHandler: kind.inherit(function (sup) { + return function (sender, event) { + sup.apply(this, arguments); + if (!event.showing && this._suppressing) { + this._resumeMouseEvents(); + } + }; + }), + + rendered: kind.inherit(function (sup) { + return function() { + sup.apply(this, arguments); + this.calcScrollNode(); + this.calcBoundaries(); + }; + }), + + /** + * @private + */ + horizontalSnapIncrementChanged: function() { + this.$.scrollMath.xSnapIncrement = this.horizontalSnapIncrement; + }, + + /** + * @private + */ + verticalSnapIncrementChanged: function() { + this.$.scrollMath.ySnapIncrement = this.verticalSnapIncrement; + }, + + /** + * @private + */ + horizontalChanged: function () { + var hEnabled = (this.hEnabled = (this.horizontal !== 'hidden')); + this.$.scrollMath.horizontal = hEnabled; + this.addRemoveClass('h-enabled', hEnabled); + this.emit('scrollabilityChanged'); + }, + + /** + * @private + */ + verticalChanged: function () { + var vEnabled = (this.vEnabled = (this.vertical !== 'hidden')); + this.$.scrollMath.vertical = vEnabled; + this.addRemoveClass('v-enabled', vEnabled); + this.emit('scrollabilityChanged'); + }, + + /** + * @private + */ + scrollTopChanged: function() { + this.$.scrollMath.setScrollY(-this.scrollTop); + }, + + /** + * @private + */ + scrollLeftChanged: function() { + this.$.scrollMath.setScrollX(-this.scrollLeft); + }, + + /** + * TODO: Document. Based on CSSOM View spec () + * @see {@linkplain http://dev.w3.org/csswg/cssom-view/} + * @see {@linkplain https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView} + * + * @public + */ + scrollTo: function(x, y, opts) { + opts = opts || {}; + opts.behavior = opts.behavior || this.behavior; + this.$.scrollMath.scrollTo(x, y, opts); + }, + + /** + * TODO: Document. Based on CSSOM View spec () + * @see {@linkplain http://dev.w3.org/csswg/cssom-view/} + * @see {@linkplain https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView} + * + * @public + */ + scrollToControl: function (control, opts) { + var n = control.hasNode(); + + if (n) { + this.scrollToNode(n, opts); + } + }, + + /** + * TODO: Document. Based on CSSOM View spec () + * @see {@linkplain http://dev.w3.org/csswg/cssom-view/} + * @see {@linkplain https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView} + * + * @public + */ + scrollToNode: function(node, opts) { + var nodeBounds = dom.getAbsoluteBounds(node), + absoluteBounds = dom.getAbsoluteBounds(this.scrollNode), + scrollBounds = this.getScrollBounds(), + docWidth = document.body.offsetWidth, + block, + offsetTop, + offsetLeft, + offsetHeight, + offsetWidth, + xDir, + yDir, + x, + y; + + if (typeof opts === 'boolean') { + block = opts ? 'start' : 'end'; + } + else if (typeof opts === 'object' && blockOptions[opts.block]) { + block = opts.block; + } + else { + block = this.block; + } + + + // For the calculations below, we want the `right` bound + // to be relative to the document's right edge, not its + // left edge, so we adjust it now + nodeBounds.right = docWidth - nodeBounds.right; + absoluteBounds.right = docWidth - absoluteBounds.right; + + // Make absolute controlBounds relative to scroll position + nodeBounds.top += scrollBounds.top; + if (this.rtl) { + nodeBounds.right += scrollBounds.left; + } else { + nodeBounds.left += scrollBounds.left; + } + + offsetTop = nodeBounds.top - absoluteBounds.top; + offsetLeft = (this.rtl ? nodeBounds.right : nodeBounds.left) - (this.rtl ? absoluteBounds.right : absoluteBounds.left); + offsetHeight = nodeBounds.height; + offsetWidth = nodeBounds.width; + + // 0: currently visible, 1: right of viewport, -1: left of viewport + xDir = calcNodeVisibility(offsetLeft, offsetWidth, scrollBounds.left, scrollBounds.clientWidth); + // 0: currently visible, 1: below viewport, -1: above viewport + yDir = calcNodeVisibility(offsetTop, offsetHeight, scrollBounds.top, scrollBounds.clientHeight); + // If we're already scrolling and the direction the node is in is not the same as the direction we're scrolling, + // we need to recalculate based on where the scroller will end up, not where it is now. This is to handle the + // case where the node is currently visible but won't be once the scroller settles. + // + // NOTE: Currently setting block = 'nearest' whenever we make this correction to avoid some nasty jumpiness + // when 5-way moving horizontally in a vertically scrolling grid layout in Moonstone. Not sure this is the + // right fix. + if (this.isScrolling) { + if (this.xDir !== xDir) { + scrollBounds.left = this.destX; + xDir = calcNodeVisibility(offsetLeft, offsetWidth, scrollBounds.left, scrollBounds.clientWidth); + block = 'nearest'; + } + if (this.yDir !== yDir) { + scrollBounds.top = this.destY; + yDir = calcNodeVisibility(offsetTop, offsetHeight, scrollBounds.top, scrollBounds.clientHeight); + block = 'nearest'; + } + } + + switch (xDir) { + case 0: + x = this.scrollLeft; + break; + case 1: + // If control requested to be scrolled all the way to the viewport's left, or if the control + // is larger than the viewport, scroll to the control's left edge. Otherwise, scroll just + // far enough to get the control into view. + if (block === 'farthest' || block === 'start' || offsetWidth > scrollBounds.clientWidth) { + x = offsetLeft; + } else { + x = offsetLeft - scrollBounds.clientWidth + offsetWidth; + // If nodeStyle exists, add the _marginRight_ to the scroll value. + x += dom.getComputedBoxValue(node, 'margin', 'right'); + } + break; + case -1: + // If control requested to be scrolled all the way to the viewport's right, or if the control + // is larger than the viewport, scroll to the control's right edge. Otherwise, scroll just + // far enough to get the control into view. + if (block === 'farthest' || block === 'end' || offsetWidth > scrollBounds.clientWidth) { + x = offsetLeft - scrollBounds.clientWidth + offsetWidth; + } else { + x = offsetLeft; + // If nodeStyle exists, subtract the _marginLeft_ from the scroll value. + x -= dom.getComputedBoxValue(node, 'margin', 'left'); + } + break; + } + + switch (yDir) { + case 0: + y = this.scrollTop; + break; + case 1: + // If control requested to be scrolled all the way to the viewport's top, or if the control + // is larger than the viewport, scroll to the control's top edge. Otherwise, scroll just + // far enough to get the control into view. + if (block === 'farthest' || block === 'start' || offsetHeight > scrollBounds.clientHeight) { + y = offsetTop; + // If nodeStyle exists, subtract the _marginTop_ from the scroll value. + y -= dom.getComputedBoxValue(node, 'margin', 'top'); + } else { + y = offsetTop - scrollBounds.clientHeight + offsetHeight; + // If nodeStyle exists, add the _marginBottom_ to the scroll value. + y += dom.getComputedBoxValue(node, 'margin', 'bottom'); + } + break; + case -1: + // If control requested to be scrolled all the way to the viewport's bottom, or if the control + // is larger than the viewport, scroll to the control's bottom edge. Otherwise, scroll just + // far enough to get the control into view. + if (block === 'farthest' || block === 'end' || offsetHeight > scrollBounds.clientHeight) { + y = offsetTop - scrollBounds.clientHeight + offsetHeight; + } else { + y = offsetTop; + // If nodeStyle exists, subtract the _marginBottom_ from the scroll value. + y -= dom.getComputedBoxValue(node, 'margin', 'bottom'); + } + break; + } + + // If x or y changed, scroll to new position + if (x !== this.scrollLeft || y !== this.scrollTop) { + this.scrollTo(x, y, opts); + } + }, + + /** + * @public + */ + pageUp: function() { + this.paginate('up'); + }, + + /** + * @public + */ + pageDown: function() { + this.paginate('down'); + }, + + /** + * @public + */ + pageLeft: function() { + this.paginate('left'); + }, + + /** + * @public + */ + pageRight: function() { + this.paginate('right'); + }, + + /** + * Stops any active scroll movement. + * + * @param {Boolean} emit - Whether or not to fire the `onScrollStop` event. + * @public + */ + stop: function () { + var m = this.$.scrollMath; + + if (m.isScrolling()) { + m.stop(true); + } + }, + + /** + * @private + */ + paginate: function(direction) { + var b = this.calcBoundaries(), + scrollYDelta = b.clientHeight * this.pageMultiplier, + scrollXDelta = b.clientWidth * this.pageMultiplier, + x = this.scrollLeft, + y = this.scrollTop; + + switch (direction) { + case 'left': + x -= scrollXDelta; + break; + case 'up': + y -= scrollYDelta; + break; + case 'right': + x += scrollXDelta; + break; + case 'down': + y += scrollYDelta; + break; + } + + x = Math.max(0, Math.min(x, b.maxLeft)); + y = Math.max(0, Math.min(y, b.maxTop)); + + this.scrollTo(x, y); + }, + + /** + /* @private + */ + mousewheel: function (sender, e) { + if (this.useMouseWheel) { + if (!this.$.scrollMath.isScrolling()) { + this.calcBoundaries(); + } + + // TODO: Change this after newMousewheel becomes mousewheel + if (this.$.scrollMath.newMousewheel(e, {rtl: this.rtl})) { + e.preventDefault(); + return true; + } + } + }, + + /** + * @private + */ + down: function (sender, e) { + var m = this.$.scrollMath; + + if (m.isScrolling() && !m.isInOverScroll() && !this.isScrollControl(e.originator)) { + this.stop(true); + e.preventTap(); + } + this.calcStartInfo(); + }, + + /** + * @private + */ + dragstart: function (sender, e) { + if (this.touch) { + this.calcBoundaries(); + // Ignore drags sent from multi-touch events + if(!this.dragDuringGesture && e.srcEvent.touches && e.srcEvent.touches.length > 1) { + return true; + } + // note: allow drags to propagate to parent scrollers via data returned in the shouldDrag event. + this.doShouldDrag(e); + this.dragging = (e.dragger == this || (!e.dragger && e.boundaryDragger == this)); + if (this.dragging) { + if(this.preventDefault){ + e.preventDefault(); + } + // note: needed because show/hide changes + // the position so sync'ing is required when + // dragging begins (needed because show/hide does not trigger onscroll) + //this.syncScrollMath(); + this.$.scrollMath.startDrag(e); + if (this.preventDragPropagation) { + return true; + } + } + } + }, + + /** + * @private + */ + drag: function (sender, e) { + if (this.touch) { + if (this.dragging) { + if(this.preventDefault){ + e.preventDefault(); + } + this.$.scrollMath.drag(e); + } + } + }, + + /** + * @private + */ + dragfinish: function (sender, e) { + if (this.touch) { + if (this.dragging) { + e.preventTap(); + this.$.scrollMath.dragFinish(); + this.dragging = false; + } + } + }, + + /** + * @private + */ + flick: function (sender, e) { + if (this.touch) { + var onAxis = Math.abs(e.xVelocity) > Math.abs(e.yVelocity) ? this.$.scrollMath.horizontal : this.$.scrollMath.vertical; + if (onAxis && this.dragging) { + this.$.scrollMath.flick(e); + return this.preventDragPropagation; + } + } + }, + + /** + * @private + */ + shouldDrag: function (sender, e) { + //this.calcAutoScrolling(); + var requestV = e.vertical, + // canH = this.$.scrollMath.horizontal && !requestV, + // canV = this.$.scrollMath.vertical && requestV, + canH = this.$.scrollMath.canScrollX() && !requestV, + canV = this.$.scrollMath.canScrollY() && requestV, + down = e.dy < 0, + right = e.dx < 0, + oobV = (!down && this.startEdges.top || down && this.startEdges.bottom), + oobH = (!right && this.startEdges.left || right && this.startEdges.right); + // we would scroll if not at a boundary + if (!e.boundaryDragger && (canH || canV)) { + e.boundaryDragger = this; + } + // include boundary exclusion + if ((!oobV && canV) || (!oobH && canH)) { + e.dragger = this; + return true; + } + }, + + stabilize: function() { + this.$.scrollMath.stabilize(); + }, + + /** + * @private + */ + scroll: kind.inherit(function (sup) { + return function(sender, event) { + var px = this.scrollLeft, + py = this.scrollTop, + pv = this.velocity, + x = this.scrollLeft = -sender.x, + y = this.scrollTop = -sender.y, + dx = px - x, + dy = py - y, + v = (dx * dx) + (dy * dy); + this.xDir = (dx < 0? 1: dx > 0? -1: 0); + this.yDir = (dy < 0? 1: dy > 0? -1: 0); + this.velocity = v; + this.acc = v > pv; + this.dec = v < pv; + this.destX = -sender.endX; + this.destY = -sender.endY; + this.updateScrollability(x, y); + // Experimental: suppress and resume mouse events + // based on veclocity and acceleration + this._manageMouseEvents(); + sup.apply(this, arguments); + }; + }), + + /** + * @private + */ + updateScrollability: function(x, y) { + var m = this.$.scrollMath, + b = -m.bottomBoundary, + r = -m.rightBoundary, + c = (this.canScrollX !== this.couldScrollX) || + (this.canScrollY !== this.couldScrollY); + + if (this.canScrollY || this.couldScrollY) { + this.set('yPosRatio', y / b); + + if (this.canScrollUp) { + if (y <= 0) { + this.canScrollUp = false; + c = true; + } + } + else { + if (y > 0) { + this.canScrollUp = true; + c = true; + } + } + if (this.canScrollDown) { + if (y >= b) { + this.canScrollDown = false; + c = true; + } + } + else { + if (y < b) { + this.canScrollDown = true; + c = true; + } + } + } + + if (this.canScrollX || this.couldScrollX) { + this.set('xPosRatio', x / r); + + if (this.canScrollLeft) { + if (x <= 0) { + this.canScrollLeft = false; + c = true; + } + } + else { + if (x > 0) { + this.canScrollLeft = true; + c = true; + } + } + if (this.canScrollRight) { + if (x >= r) { + this.canScrollRight = false; + c = true; + } + } + else { + if (x < r) { + this.canScrollRight = true; + c = true; + } + } + } + + this.couldScrollX = this.canScrollX; + this.couldScrollY = this.canScrollY; + + if (c) { + this.emit('scrollabilityChanged'); + } + }, + + /** + * @private + */ + scrollStart: function () { + this.calcBoundaries(); + this.isScrolling = true; + this.emit('stateChanged'); + }, + /** + * @private + */ + scrollStop: function () { + // TODO: Leaving this in to be safe... + // But we should already have resumed, due to + // velocity-sensitive logic in scroll(). + // This whole scheme probably needs bullet-proofing. + if (!this.touch) { + this._resumeMouseEvents(); + } + this.isScrolling = false; + this.emit('stateChanged'); + }, + /** + * @private + */ + _manageMouseEvents: function () { + // TODO: Experiment, make configurable + var t = 5, + v = this.velocity; + + if (this._suppressing) { + if (this.dec && v < t) { + this._resumeMouseEvents(); + } + } + // TODO: Can probably allow suppressing events when this.touch === true + // if we resume events on down so we can capture flicks and drags. Need + // to experiment. + else if (this.suppressMouseEvents && !this.touch) { + if (this.isScrolling && this.acc && v > t) { + this._suppressMouseEvents(); + } + } + }, + + /** + * @private + */ + _suppressMouseEvents: function () { + // TODO: Create a dispatcher API for this + dispatcher.stopListening(document, 'mouseover'); + dispatcher.stopListening(document, 'mouseout'); + dispatcher.stopListening(document, 'mousemove'); + this._suppressing = true; + }, + + /** + * @private + */ + _resumeMouseEvents: function () { + // TODO: Create a dispatcher API for this + dispatcher.listen(document, 'mouseover'); + dispatcher.listen(document, 'mouseout'); + dispatcher.listen(document, 'mousemove'); + this._suppressing = false; + }, + + /** + * @private + */ + getScrollBounds: function () { + var s = this.getScrollSize(), cn = this.calcScrollNode(); + var b = { + left: this.get('scrollLeft'), + top: this.get('scrollTop'), + clientHeight: cn ? cn.clientHeight : 0, + clientWidth: cn ? cn.clientWidth : 0, + height: s.height, + width: s.width + }; + b.maxLeft = Math.max(0, b.width - b.clientWidth); + b.maxTop = Math.max(0, b.height - b.clientHeight); + this.cachedBounds = b; + return b; + }, + + /** + * @private + */ + getScrollSize: function () { + var n = this.calcScrollNode(), + w = this.getScrollWidth && this.getScrollWidth() || (n ? n.scrollWidth : 0), + h = this.getScrollHeight && this.getScrollHeight() || (n ? n.scrollHeight : 0); + return {width: w, height: h}; + }, + + /** + * @private + */ + calcScrollNode: kind.inherit(function (sup) { + return function() { + return (this.scrollNode = sup.apply(this, arguments) || this.hasNode()); + }; + }), + + /** + * @private + */ + calcStartInfo: function (bounds) { + var sb = bounds || this.getScrollBounds(), + y = this.scrollTop, + x = this.scrollLeft; + + // this.canVertical = sb.maxTop > 0 && this.vertical !== 'hidden'; + // this.canHorizontal = sb.maxLeft > 0 && this.horizontal !== 'hidden'; + this.startEdges = { + top: y === 0, + bottom: y === sb.maxTop, + left: x === 0, + right: x === sb.maxLeft + }; + }, + + /** + * @private + */ + calcBoundaries: function (bounds) { + var m = this.$.scrollMath, + b = bounds || this.getScrollBounds(), + width = b.width, + height = b.height, + clWidth = b.clientWidth, + clHeight = b.clientHeight, + rBound = clWidth - width, + bBound = clHeight - height, + xRatio = Math.min(1, clWidth / width), + yRatio = Math.min(1, clHeight / height), + cTop = Math.min(b.top, Math.max(0, -bBound)), + cLeft = Math.min(b.left, Math.max(0, -rBound)); + + m.rightBoundary = rBound; + m.bottomBoundary = bBound; + + this.set('canScrollX', m.canScrollX()); + this.set('canScrollY', m.canScrollY()); + + if (b.top !== cTop || b.left !== cLeft) { + b.left = cLeft; + b.top = cTop; + m.setScrollX(-cLeft); + m.setScrollY(-cTop); + this.scroll(m); + this.stop(); + } + + this.set('xSizeRatio', xRatio); + this.set('ySizeRatio', yRatio); + + this.updateScrollability(cLeft, cTop); + + return b; + }, + + // xSizeRatioChanged: function() { + // this.emit('metricsChanged', this.xSizeRatio); + // }, + + // ySizeRatioChanged: function() { + // this.emit('metricsChanged', this.ySizeRatio); + // }, + + xPosRatioChanged: function() { + this.emit('metricsChanged', this.xPosRatio); + }, + + yPosRatioChanged: function() { + this.emit('metricsChanged', this.yPosRatio); + }, + + // canScrollXChanged: function() { + // this.emit('scrollabilityChanged'); + // }, + + // canScrollYChanged: function() { + // this.emit('scrollabilityChanged'); + // } + + /** + * Returns `true` if `control` is a scrolling child of this + * scrollable (an element within the Scrollable's scrolling + * region). + * + * (Currently, we assume that any child of the Scrollable that + * is not a scroll control is a scrolling child, but this + * is an implementation detail and could change if we + * determine that there's a more appropriate way to test.) + * + * Returns `false` if `control` is one of the Scrollable's + * scroll controls, or if `control` is not a child of the + * Scrollable at all. + * + * @param {module:enyo/Control~Control} control - The control to be tested + * @protected + */ + isScrollingChild: function (control) { + var c = control; + + while (c && c !== this) { + if (c.scroller === this) { + return false; + } + c = c.parent; + } + + return c === this; + }, + + /** + * Returns `true` if `control` is one of the Scrollable's + * scroll controls. + * + * @param {module:enyo/Control~Control} control - The control to be tested + * @protected + */ + isScrollControl: function (control) { + var c = control; + + while (c && c !== this) { + if (c.scroller === this) { + return true; + } + c = c.parent; + } + + return false; + } +}; + +},{'../kind':'enyo/kind','../utils':'enyo/utils','../dom':'enyo/dom','../dispatcher':'enyo/dispatcher','../EventEmitter':'enyo/EventEmitter','../ScrollMath':'enyo/ScrollMath'}],'enyo/gesture/drag':[function (module,exports,global,require,request){ +var + dispatcher = require('../dispatcher'), + platform = require('../platform'), + utils = require('../utils'); + +var + gestureUtil = require('./util'); + +/** +* Enyo supports a cross-platform set of drag [events]{@glossary event}. These +* events allow users to write a single set of event handlers for applications +* that run on both mobile and desktop platforms. +* +* The following events are provided: +* +* * 'dragstart' +* * 'dragfinish' +* * 'drag' +* * 'drop' +* * 'dragover' +* * 'dragout' +* * 'hold' +* * 'release' +* * 'holdpulse' +* * 'flick' +* +* For more information on these events, see the documentation on +* [Event Handling]{@linkplain $dev-guide/key-concepts/event-handling.html} in +* the Enyo Developer Guide. +* +* Used internally by {@link module:enyo/gesture} +* +* @module enyo/gesture/drag +* @public +*/ +module.exports = { + + /** + * @private + */ + holdPulseDefaultConfig: { + frequency: 200, + events: [{name: 'hold', time: 200}], + resume: false, + preventTap: false, + moveTolerance: 16, + endHold: 'onMove' + }, + + /** + * Call this method to specify the framework's 'holdPulse' behavior, which + * determines the nature of the events generated when a user presses and holds + * on a user interface element. + * + * By default, an `onhold` event fires after 200 ms. After that, an `onholdpulse` + * event fires every 200 ms until the user stops holding, at which point a + * `onrelease` event fires. + * + * To change the default behavior, call this method and pass it a holdPulse + * configuration object. The holdPulse configuration object has a number of + * properties. + * + * You can specify a set of custom hold events by setting the `events` property + * to an array containing one or more objects. Each object specifies a custom + * hold event, in the form of a `name` / `time` pair. Notes: + * + * * Your custom event names should not include the 'on' prefix; that will be + * added automatically by the framework. + * + * * Times should be specified in milliseconds. + * + * * Your `events` array overrides the framework defaults entirely, so if you + * want the standard `hold` event to fire at 200 ms (in addition to whatever + * custom events you define), you'll need to redefine it yourself as part of + * your `events` array. + * + * Regardless of how many custom hold events you define, `onholdpulse` events + * will start firing after the first custom hold event fires, and continue until + * the user stops holding. Likewise, only one `onrelease` event will fire, + * regardless of how many custom hold events you define. + * + * The`frequency` parameter determines not only how often `holdpulse` events are + * sent, but the frequency with which the hold duration is measured. This means + * that the value you set for `frequency` should always be a common factor of the + * times you set for your custom hold events, to ensure accurate event timing. + * + * You can use the `endHold` property to specify the circumstances under which a + * hold is considered to end. Set `endHold` to `onMove` (the default) if you want + * the hold to end as soon as the user's finger or pointer moves. Set `endHold` + * to `onLeave` if you want the hold to end only when the finger or pointer + * leaves the element altogether. When specifying `onMove`, you can also provide + * a `moveTolerance` value (default: `16`) that determines how tolerant you want + * to be of small movements when deciding whether a hold has ended. The higher + * the value, the further a user's finger or pointer may move without causing + * the hold to end. + * + * The `resume` parameter (default: `false`) specifies whether a hold + * that has ended due to finger / pointer movement should be resumed if the + * user's finger or pointer moves back inside the tolerance threshold (in the + * case of `endHold: onMove`) or back over the element (in the case of + * `endHold: onLeave`). + * + * Finally, the `preventTap` paramenter (default: `false`) allows you to prevent + * an `ontap` event from firing when the hold is released. + * + * Here is an example: + * + * ``` + * gesture.drag.configureHoldPulse({ + * frequency: 100, + * events: [ + * {name: 'hold', time: 200}, + * {name: 'longpress', time: 500} + * ], + * endHold: 'onLeave', + * resume: true, + * preventTap: true + * }); + * ``` + * For comparison, here are the out-of-the-box defaults: + * + * ``` + * gesture.drag.configureHoldPulse({ + * frequency: 200, + * events: [ + * {name: 'hold', time: 200} + * ], + * endHold: 'onMove', + * moveTolerance: 16, + * resume: false, + * preventTap: false + * }); + * ``` + * + * The settings you provide via this method will be applied globally, affecting + * every Control. Note that you can also override the defaults on a case-by-case + * basis by handling the `down` event for any Control and calling the + * `configureHoldPulse` method exposed by the event itself. That method works + * exactly like this one, except that the settings you provide will apply only to + * holds on that particular Control. + * + * @public + */ + configureHoldPulse: function (config) { + // TODO: Might be nice to do some validation, error handling + + // _holdPulseConfig represents the current, global `holdpulse` settings, if the default + // settings have been overridden in some way. + this._holdPulseConfig = this._holdPulseConfig || utils.clone(this.holdPulseDefaultConfig, true); + utils.mixin(this._holdPulseConfig, config); + }, + + /** + * Resets the `holdPulse` behavior to the default settings. + * + * @public + */ + resetHoldPulseConfig: function () { + this._holdPulseConfig = null; + }, + + /** + * @private + */ + holdPulseConfig: {}, + + /** + * @private + */ + trackCount: 5, + + /** + * @private + */ + minFlick: 0.1, + + /** + * @private + */ + minTrack: 8, + + /** + * @private + */ + down: function(e) { + // tracking if the mouse is down + //enyo.log('tracking ON'); + // Note: 'tracking' flag indicates interest in mousemove, it's turned off + // on mouseup + // make sure to stop dragging in case the up event was not received. + this.stopDragging(e); + this.target = e.target; + this.startTracking(e); + }, + + /** + * @private + */ + move: function(e) { + if (this.tracking) { + this.track(e); + // If the mouse is not down and we're tracking a drag, abort. + // this error condition can occur on IE/Webkit after interaction with a scrollbar. + if (!e.which) { + this.stopDragging(e); + this.endHold(); + this.tracking = false; + //enyo.log('gesture.drag: mouse must be down to drag.'); + return; + } + if (this.dragEvent) { + this.sendDrag(e); + } else if (this.holdPulseConfig.endHold === 'onMove') { + if (this.dy*this.dy + this.dx*this.dx >= this.holdPulseConfig.moveTolerance) { // outside of target + if (this.holdJob) { // only stop/cancel hold job if it currently exists + if (this.holdPulseConfig.resume) { // pause hold to potentially resume later + this.suspendHold(); + } else { // completely cancel hold + this.endHold(); + this.sendDragStart(e); + } + } + } else if (this.holdPulseConfig.resume && !this.holdJob) { // when moving inside target, only resume hold job if it was previously paused + this.resumeHold(); + } + } + } + }, + + /** + * @private + */ + up: function(e) { + this.endTracking(e); + this.stopDragging(e); + this.endHold(); + this.target = null; + }, + + /** + * @private + */ + enter: function(e) { + // resume hold when re-entering original target when using 'onLeave' endHold value + if (this.holdPulseConfig.resume && this.holdPulseConfig.endHold === 'onLeave' && this.target && e.target === this.target) { + this.resumeHold(); + } + }, + + /** + * @private + */ + leave: function(e) { + if (this.dragEvent) { + this.sendDragOut(e); + } else if (this.holdPulseConfig.endHold === 'onLeave') { + if (this.holdPulseConfig.resume) { // pause hold to potentially resume later + this.suspendHold(); + } else { // completely cancel hold + this.endHold(); + this.sendDragStart(e); + } + } + }, + + /** + * @private + */ + stopDragging: function(e) { + if (this.dragEvent) { + this.sendDrop(e); + var handled = this.sendDragFinish(e); + this.dragEvent = null; + return handled; + } + }, + + /** + * @private + */ + makeDragEvent: function(inType, inTarget, inEvent, inInfo) { + var adx = Math.abs(this.dx), ady = Math.abs(this.dy); + var h = adx > ady; + // suggest locking if off-axis < 22.5 degrees + var l = (h ? ady/adx : adx/ady) < 0.414; + var e = {}; + // var e = { + e.type = inType; + e.dx = this.dx; + e.dy = this.dy; + e.ddx = this.dx - this.lastDx; + e.ddy = this.dy - this.lastDy; + e.xDirection = this.xDirection; + e.yDirection = this.yDirection; + e.pageX = inEvent.pageX; + e.pageY = inEvent.pageY; + e.clientX = inEvent.clientX; + e.clientY = inEvent.clientY; + e.horizontal = h; + e.vertical = !h; + e.lockable = l; + e.target = inTarget; + e.dragInfo = inInfo; + e.ctrlKey = inEvent.ctrlKey; + e.altKey = inEvent.altKey; + e.metaKey = inEvent.metaKey; + e.shiftKey = inEvent.shiftKey; + e.srcEvent = inEvent.srcEvent; + // }; + //Fix for IE8, which doesn't include pageX and pageY properties + if(platform.ie==8 && e.target) { + e.pageX = e.clientX + e.target.scrollLeft; + e.pageY = e.clientY + e.target.scrollTop; + } + e.preventDefault = gestureUtil.preventDefault; + e.disablePrevention = gestureUtil.disablePrevention; + return e; + }, + + /** + * @private + */ + sendDragStart: function(e) { + //enyo.log('dragstart'); + this.dragEvent = this.makeDragEvent('dragstart', this.target, e); + dispatcher.dispatch(this.dragEvent); + }, + + /** + * @private + */ + sendDrag: function(e) { + //enyo.log('sendDrag to ' + this.dragEvent.target.id + ', over to ' + e.target.id); + // send dragOver event to the standard event target + var synth = this.makeDragEvent('dragover', e.target, e, this.dragEvent.dragInfo); + dispatcher.dispatch(synth); + // send drag event to the drag source + synth.type = 'drag'; + synth.target = this.dragEvent.target; + dispatcher.dispatch(synth); + }, + + /** + * @private + */ + sendDragFinish: function(e) { + //enyo.log('dragfinish'); + var synth = this.makeDragEvent('dragfinish', this.dragEvent.target, e, this.dragEvent.dragInfo); + synth.preventTap = function() { + if (e.preventTap) { + e.preventTap(); + } + }; + dispatcher.dispatch(synth); + }, + + /** + * @private + */ + sendDragOut: function(e) { + var synth = this.makeDragEvent('dragout', e.target, e, this.dragEvent.dragInfo); + dispatcher.dispatch(synth); + }, + + /** + * @private + */ + sendDrop: function(e) { + var synth = this.makeDragEvent('drop', e.target, e, this.dragEvent.dragInfo); + synth.preventTap = function() { + if (e.preventTap) { + e.preventTap(); + } + }; + dispatcher.dispatch(synth); + }, + + /** + * @private + */ + startTracking: function(e) { + this.tracking = true; + // note: use clientX/Y to be compatible with ie8 + this.px0 = e.clientX; + this.py0 = e.clientY; + // this.flickInfo = {startEvent: e, moves: []}; + this.flickInfo = {}; + this.flickInfo.startEvent = e; + // FIXME: so we're trying to reuse objects where possible, should + // do the same in scenarios like this for arrays + this.flickInfo.moves = []; + this.track(e); + }, + + /** + * @private + */ + track: function(e) { + this.lastDx = this.dx; + this.lastDy = this.dy; + this.dx = e.clientX - this.px0; + this.dy = e.clientY - this.py0; + this.xDirection = this.calcDirection(this.dx - this.lastDx, 0); + this.yDirection = this.calcDirection(this.dy - this.lastDy, 0); + // + var ti = this.flickInfo; + ti.moves.push({ + x: e.clientX, + y: e.clientY, + t: utils.perfNow() + }); + // track specified # of points + if (ti.moves.length > this.trackCount) { + ti.moves.shift(); + } + }, + + /** + * @private + */ + endTracking: function() { + this.tracking = false; + var ti = this.flickInfo; + var moves = ti && ti.moves; + if (moves && moves.length > 1) { + // note: important to use up time to reduce flick + // velocity based on time between move and up. + var l = moves[moves.length-1]; + var n = utils.perfNow(); + // take the greatest of flick between each tracked move and last move + for (var i=moves.length-2, dt=0, x1=0, y1=0, x=0, y=0, sx=0, sy=0, m; (m=moves[i]); i--) { + // this flick (this move - last move) / (this time - last time) + dt = n - m.t; + x1 = (l.x - m.x) / dt; + y1 = (l.y - m.y) / dt; + // establish flick direction + sx = sx || (x1 < 0 ? -1 : (x1 > 0 ? 1 : 0)); + sy = sy || (y1 < 0 ? -1 : (y1 > 0 ? 1 : 0)); + // if either axis is a greater flick than previously recorded use this one + if ((x1 * sx > x * sx) || (y1 * sy > y * sy)) { + x = x1; + y = y1; + } + } + var v = Math.sqrt(x*x + y*y); + if (v > this.minFlick) { + // generate the flick using the start event so it has those coordinates + this.sendFlick(ti.startEvent, x, y, v); + } + } + this.flickInfo = null; + }, + + /** + * @private + */ + calcDirection: function(inNum, inDefault) { + return inNum > 0 ? 1 : (inNum < 0 ? -1 : inDefault); + }, + + /** + * Translate the old format for holdPulseConfig to the new one, to + * preserve backward compatibility. + * + * @private + */ + normalizeHoldPulseConfig: function (oldOpts) { + var nOpts = utils.clone(oldOpts); + nOpts.frequency = nOpts.delay; + nOpts.events = [{name: 'hold', time: nOpts.delay}]; + return nOpts; + }, + + /** + * Method to override holdPulseConfig for a given gesture. This method isn't + * accessed directly from gesture.drag, but exposed by the `down` event. + * See `prepareHold()`. + * + * @private + */ + _configureHoldPulse: function(opts) { + var nOpts = (opts.delay === undefined) ? + opts : + this.normalizeHoldPulseConfig(opts); + utils.mixin(this.holdPulseConfig, nOpts); + }, + + /** + * @private + */ + prepareHold: function(e) { + // quick copy as the prototype of the new overridable config + this.holdPulseConfig = utils.clone(this._holdPulseConfig || this.holdPulseDefaultConfig, true); + + // expose method for configuring holdpulse options + e.configureHoldPulse = this._configureHoldPulse.bind(this); + }, + + /** + * @private + */ + beginHold: function(e) { + var ce; + // cancel any existing hold since it's possible in corner cases to get a down without an up + this.endHold(); + this.holdStart = utils.perfNow(); + this._holdJobFunction = utils.bind(this, 'handleHoldPulse'); + // clone the event to ensure it stays alive on IE upon returning to event loop + ce = this._holdJobEvent = utils.clone(e); + ce.srcEvent = utils.clone(e.srcEvent); + ce.downEvent = e; + this._pulsing = false; + this._unsent = utils.clone(this.holdPulseConfig.events); + this._unsent.sort(this.sortEvents); + this._next = this._unsent.shift(); + if (this._next) { + this.holdJob = setInterval(this._holdJobFunction, this.holdPulseConfig.frequency); + } + }, + + /** + * @private + */ + resumeHold: function() { + this.handleHoldPulse(); + this.holdJob = setInterval(this._holdJobFunction, this.holdPulseConfig.frequency); + }, + + /** + * @private + */ + sortEvents: function(a, b) { + if (a.time < b.time) return -1; + if (a.time > b.time) return 1; + return 0; + }, + + /** + * @private + */ + endHold: function() { + var e = this._holdJobEvent; + this.suspendHold(); + if (e && this._pulsing) { + this.sendRelease(e); + } + this._pulsing = false; + this._unsent = null; + this._holdJobFunction = null; + this._holdJobEvent = null; + this._next = null; + }, + + /** + * @private + */ + suspendHold: function() { + clearInterval(this.holdJob); + this.holdJob = null; + }, + + /** + * @private + */ + handleHoldPulse: function() { + var holdTime = utils.perfNow() - this.holdStart, + hje = this._holdJobEvent, + e; + this.maybeSendHold(hje, holdTime); + if (this._pulsing) { + e = gestureUtil.makeEvent('holdpulse', hje); + e.holdTime = holdTime; + dispatcher.dispatch(e); + } + }, + + /** + * @private + */ + maybeSendHold: function(inEvent, inHoldTime) { + var n = this._next; + while (n && n.time <= inHoldTime) { + var e = gestureUtil.makeEvent(n.name, inEvent); + if (!this._pulsing && this.holdPulseConfig.preventTap) { + inEvent.downEvent.preventTap(); + } + this._pulsing = true; + dispatcher.dispatch(e); + n = this._next = this._unsent.shift(); + } + }, + + /** + * @private + */ + sendRelease: function(inEvent) { + var e = gestureUtil.makeEvent('release', inEvent); + dispatcher.dispatch(e); + }, + + /** + * @private + */ + sendFlick: function(inEvent, inX, inY, inV) { + var e = gestureUtil.makeEvent('flick', inEvent); + e.xVelocity = inX; + e.yVelocity = inY; + e.velocity = inV; + dispatcher.dispatch(e); + } +}; + +},{'../dispatcher':'enyo/dispatcher','../platform':'enyo/platform','../utils':'enyo/utils','./util':'enyo/gesture/util'}],'enyo/gesture/touchGestures':[function (module,exports,global,require,request){ +var + dispatcher = require('../dispatcher'), + utils = require('../utils'); + +/** +* The extended {@glossary event} [object]{@glossary Object} that is provided when we +* emulate iOS gesture events on non-iOS devices. +* +* @typedef {Object} module:enyo/gesture/touchGestures~EmulatedGestureEvent +* @property {Number} pageX - The x-coordinate of the center point between fingers. +* @property {Number} pageY - The y-coordinate of the center point between fingers. +* @property {Number} rotation - The degrees of rotation from the beginning of the gesture. +* @property {Number} scale - The percent change of distance between fingers. +*/ + +/** +* @module enyo/gesture/touchGestures +* @private +*/ +module.exports = { + + /** + * @private + */ + orderedTouches: [], + + /** + * @private + */ + gesture: null, + + /** + * @private + */ + touchstart: function (e) { + // some devices can send multiple changed touches on start and end + var i, + changedTouches = e.changedTouches, + length = changedTouches.length; + + for (i = 0; i < length; i++) { + var id = changedTouches[i].identifier; + + // some devices can send multiple touchstarts + if (utils.indexOf(id, this.orderedTouches) < 0) { + this.orderedTouches.push(id); + } + } + + if (e.touches.length >= 2 && !this.gesture) { + var p = this.gesturePositions(e); + + this.gesture = this.gestureVector(p); + this.gesture.angle = this.gestureAngle(p); + this.gesture.scale = 1; + this.gesture.rotation = 0; + var g = this.makeGesture('gesturestart', e, {vector: this.gesture, scale: 1, rotation: 0}); + dispatcher.dispatch(g); + } + }, + + /** + * @private + */ + touchend: function (e) { + // some devices can send multiple changed touches on start and end + var i, + changedTouches = e.changedTouches, + length = changedTouches.length; + + for (i = 0; i < length; i++) { + utils.remove(changedTouches[i].identifier, this.orderedTouches); + } + + if (e.touches.length <= 1 && this.gesture) { + var t = e.touches[0] || e.changedTouches[e.changedTouches.length - 1]; + + // gesture end sends last rotation and scale, with the x/y of the last finger + dispatcher.dispatch(this.makeGesture('gestureend', e, {vector: {xcenter: t.pageX, ycenter: t.pageY}, scale: this.gesture.scale, rotation: this.gesture.rotation})); + this.gesture = null; + } + }, + + /** + * @private + */ + touchmove: function (e) { + if (this.gesture) { + var g = this.makeGesture('gesturechange', e); + this.gesture.scale = g.scale; + this.gesture.rotation = g.rotation; + dispatcher.dispatch(g); + } + }, + + /** + * @private + */ + findIdentifiedTouch: function (touches, id) { + for (var i = 0, t; (t = touches[i]); i++) { + if (t.identifier === id) { + return t; + } + } + }, + + /** + * @private + */ + gesturePositions: function (e) { + var first = this.findIdentifiedTouch(e.touches, this.orderedTouches[0]); + var last = this.findIdentifiedTouch(e.touches, this.orderedTouches[this.orderedTouches.length - 1]); + var fx = first.pageX, lx = last.pageX, fy = first.pageY, ly = last.pageY; + // center the first touch as 0,0 + var x = lx - fx, y = ly - fy; + var h = Math.sqrt(x*x + y*y); + return {x: x, y: y, h: h, fx: fx, lx: lx, fy: fy, ly: ly}; + }, + + /** + * Finds rotation angle. + * + * @private + */ + gestureAngle: function (positions) { + var p = positions; + // yay math!, rad -> deg + var a = Math.asin(p.y / p.h) * (180 / Math.PI); + // fix for range limits of asin (-90 to 90) + // Quadrants II and III + if (p.x < 0) { + a = 180 - a; + } + // Quadrant IV + if (p.x > 0 && p.y < 0) { + a += 360; + } + return a; + }, + + /** + * Finds bounding box. + * + * @private + */ + gestureVector: function (positions) { + // the least recent touch and the most recent touch determine the bounding box of the gesture event + var p = positions; + // center the first touch as 0,0 + return { + magnitude: p.h, + xcenter: Math.abs(Math.round(p.fx + (p.x / 2))), + ycenter: Math.abs(Math.round(p.fy + (p.y / 2))) + }; + }, + + /** + * @private + */ + makeGesture: function (type, e, cache) { + var vector, scale, rotation; + if (cache) { + vector = cache.vector; + scale = cache.scale; + rotation = cache.rotation; + } else { + var p = this.gesturePositions(e); + vector = this.gestureVector(p); + scale = vector.magnitude / this.gesture.magnitude; + // gestureEvent.rotation is difference from the starting angle, clockwise + rotation = (360 + this.gestureAngle(p) - this.gesture.angle) % 360; + } + var event = utils.clone(e); + return utils.mixin(event, { + type: type, + scale: scale, + pageX: vector.xcenter, + pageY: vector.ycenter, + rotation: rotation + }); + } +}; + +},{'../dispatcher':'enyo/dispatcher','../utils':'enyo/utils'}],'enyo/UiComponent':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/UiComponent~UiComponent} kind. +* @module enyo/UiComponent +*/ + +var + kind = require('./kind'), + utils = require('./utils'), + master = require('./master'), + AnimationSupport = require('./AnimationSupport/AnimationSupport'), + AnimationInterfaceSupport = require('./AnimationSupport/AnimationInterfaceSupport'); + +var + Component = require('./Component'); + +/** +* {@link module:enyo/UiComponent~UiComponent} implements a container strategy suitable for presentation layers. +* +* `UiComponent` itself is abstract. Concrete [subkinds]{@glossary subkind} include +* {@link module:enyo/Control~Control} (for HTML/DOM) and +* {@link module:canvas/Control~Control} (for Canvas contexts). +* +* @class UiComponent +* @extends module:enyo/Component~Component +* @public +*/ +var UiComponent = module.exports = kind( + /** @lends module:enyo/UiComponent~UiComponent.prototype */ { + + name: 'enyo.UiComponent', + + /** + * @private + */ + kind: Component, + + statics: { + + /** + * The default set of keys which are effectively "ignored" when determining whether or not the + * this control has changed in such a way that warrants a complete re-render. When + * {@link enyo.UIComponent#updateComponents} is invoked on a parent component, this set of + * stateful keys is utilized by default, if no stateful keys are provided by us. + * + * @type {String[]} + * @default ['content', active', 'disabled'] + * @private + */ + statefulKeys: [ + 'content', + 'active', + 'disabled' + ], + + /** + * Finds static properties by walking up the inheritance chain, until the property is found. + * By default this will return the property from {@link module:enyo/UiComponent} if the + * property is not found anywhere along the chain. + * + * @param {module:enyo/kind} kind - The kind which we are attempting to retrieve the property + * from; if the property is not found on this kind, its parent kind will be examined. + * @param {String} prop - The property we are trying to retrieve. + * @returns {String[]} The array of stateful key strings. + * @public + */ + findStatic: function (kind, prop) { + if (kind) { + if (kind[prop]) return kind[prop]; + return UiComponent.findStatic(kind.kind, prop); + } else { + return UiComponent[prop]; + } + } + }, + + /** + * @private + */ + published: + /** @lends module:enyo/UiComponent~UiComponent.prototype */ { + + /** + * The [UiComponent]{@link module:enyo/UiComponent~UiComponent} that physically contains this + * [component]{@link module:enyo/Component~Component} in the DOM. + * + * @type {module:enyo/UiComponent~UiComponent} + * @default null + * @public + */ + container: null, + + /** + * The [UiComponent]{@link module:enyo/UiComponent~UiComponent} that owns this + * [component]{@link module:enyo/Component~Component} for purposes of {@glossary event} + * propagation. + * + * @type {module:enyo/UiComponent~UiComponent} + * @default null + * @public + */ + parent: null, + + /** + * The [UiComponent]{@link module:enyo/UiComponent~UiComponent} that will physically contain new items added + * by calls to [createComponent()]{@link module:enyo/UiComponent~UiComponent#createComponent}. + * + * @type {String} + * @default 'client' + * @public + */ + controlParentName: 'client', + + /** + * A {@glossary kind} used to manage the size and placement of child + * [components]{@link module:enyo/Component~Component}. + * + * @type {String} + * @default '' + * @public + */ + layoutKind: '' + }, + + /** + * @private + */ + handlers: { + onresize: 'handleResize' + }, + + /** + * Adding animation support for controls + * @private + */ + mixins: [AnimationSupport, AnimationInterfaceSupport], + + /** + * When set, provides a [control]{@link module:enyo/Control~Control} reference used to indicate where a + * newly-created [component]{@link module:enyo/Component~Component} should be added in the + * [UiComponent's]{@link module:enyo/UiComponent~UiComponent} [array]{@glossary Array} of children. This is + * typically used when creating children dynamically (rather than at design time). If set + * to `null`, the new control will be added at the beginning of the array; if set to a + * specific existing control, the new control will be added before the specified + * control. If left as `undefined`, the default behavior is to add the new control + * at the end of the array. + * + * @type {module:enyo/Control~Control} + * @default undefined + * @public + */ + addBefore: undefined, + + /** + * @method + * @private + */ + create: kind.inherit(function (sup) { + return function() { + this.controls = this.controls || []; + this.children = this.children || []; + this.containerChanged(); + sup.apply(this, arguments); + this.layoutKindChanged(); + }; + }), + + /** + * @method + * @private + */ + destroy: kind.inherit(function (sup) { + return function() { + // Destroys all non-chrome controls (regardless of owner). + this.destroyClientControls(); + // Removes us from our container. + this.setContainer(null); + // Destroys chrome controls owned by this. + sup.apply(this, arguments); + }; + }), + + /** + * @method + * @private + */ + importProps: kind.inherit(function (sup) { + return function(inProps) { + sup.apply(this, arguments); + if (!this.owner) { + this.owner = master; + } + }; + }), + + /** + * Creates [components]{@link module:enyo/Component~Component} as defined by the [arrays]{@glossary Array} + * of base and additional property [hashes]{@glossary Object}. The standard and + * additional property hashes are combined as described in + * {@link module:enyo/Component~Component#createComponent}. + * + * ``` + * // ask foo to create components 'bar' and 'zot', but set the owner of + * // both components to 'this'. + * this.$.foo.createComponents([ + * {name: 'bar'}, + * {name: 'zot'} + * ], {owner: this}); + * ``` + * + * As implemented, [controlParentName]{@link module:enyo/UiComponent~UiComponent#controlParentName} only works + * to identify an owned control created via `createComponents()` + * (i.e., usually in our `components` block). To attach a `controlParent` via other means, + * one must call [discoverControlParent()]{@link module:enyo/UiComponent~UiComponent#discoverControlParent} or + * set `controlParent` directly. + * + * We could call `discoverControlParent()` in + * [addComponent()]{@link module:enyo/Component~Component#addComponent}, but that would + * cause a lot of useless checking. + * + * @param {Object[]} props The array of {@link module:enyo/Component~Component} definitions to be created. + * @param {Object} ext - Additional properties to be supplied as defaults for each. + * @returns {module:enyo/Component~Component[]} The array of components that were created. + * @method + * @public + */ + // + createComponents: kind.inherit(function (sup) { + return function() { + var results = sup.apply(this, arguments); + this.discoverControlParent(); + return results; + }; + }), + + /** + * An alternative component update path that attempts to intelligently update only the + * relevant portions of the component which have changed. + * + * @param {Array} comps - An array of kind definitions to be set as the child components of + * this component. + * @returns {Boolean} - Whether or not the component should be re-rendered. + * @public + */ + updateComponents: function (comps) { + var allStatefulKeys = {}, + isChanged = this.computeComponentsDiff(comps, allStatefulKeys), + comp, controls, control, keys, key, idxKey, idxComp, kind; + + if (isChanged) { + this.destroyClientControls(); + this.createComponents(comps); + return true; + } else { + controls = this.getClientControls(); + for (idxComp = 0; idxComp < comps.length; idxComp++) { + comp = comps[idxComp]; + control = controls[idxComp]; + kind = comp.kind || this.defaultKind; + keys = allStatefulKeys[idxComp]; + + for (idxKey = 0; idxKey < keys.length; idxKey++) { // for each key, determine if there is a change + key = keys[idxKey]; + if (comp[key] != control[key]) { + control.set(key, comp[key]); + } + } + } + } + + return false; + }, + + /** + * @private + */ + computeComponentsDiff: function (comps, allStatefulKeys) { + var hash = this.computeComponentsHash(comps, allStatefulKeys), + isChanged = false; + + if (this._compHash) isChanged = this._compHash != hash; + else isChanged = true; + + this._compHash = hash; + + return isChanged; + }, + + /** + * @private + */ + computeComponentsHash: function (comps, allStatefulKeys) { + var keyCount = 0, + hash, str, filtered, chr, len, idx; + + // http://jsperf.com/json-parse-and-iteration-vs-array-map + filtered = comps.map(this.bindSafely(function (comp, itemIdx) { + var kind = comp.kind || this.defaultKind, + keys = UiComponent.findStatic(kind, 'statefulKeys'), + objKeys = Object.keys(comp), + obj = {}, + idx, key, value; + + allStatefulKeys[itemIdx] = keys; // cache statefulKeys + + for (idx = 0; idx < objKeys.length; idx++) { + key = objKeys[idx]; + + if (keys.indexOf(key) == -1) { // ignore stateful keys + value = comp[key]; + if (typeof value == 'function') value = (value.prototype && value.prototype.kindName) || value.toString(); + obj[key] = value; + keyCount++; + } + + } + + return obj; + })); + + // Adapted from http://stackoverflow.com/a/7616484 + str = JSON.stringify(filtered) + keyCount; + hash = 0; + + for (idx = 0, len = str.length; idx < len; idx++) { + chr = str.charCodeAt(idx); + hash = ((hash << 5) - hash) + chr; + hash |= 0; // Convert to 32bit integer + } + + return hash; + }, + + /** + * Determines and sets the current [control's]{@link module:enyo/Control~Control} parent. + * + * @protected + */ + discoverControlParent: function () { + this.controlParent = this.$[this.controlParentName] || this.controlParent; + }, + + /** + * @method + * @private + */ + adjustComponentProps: kind.inherit(function (sup) { + return function(inProps) { + // Components we create have us as a container by default. + inProps.container = inProps.container || this; + sup.apply(this, arguments); + }; + }), + + /** + * Containment + * + * @method + * @private + */ + containerChanged: function (container) { + if (container) { + container.removeControl(this); + } + if (this.container) { + this.container.addControl(this, this.addBefore); + } + }, + + /** + * Parentage + * + * @method + * @private + */ + parentChanged: function (oldParent) { + if (oldParent && oldParent != this.parent) { + oldParent.removeChild(this); + } + }, + + /** + * Determines whether the [control]{@link module:enyo/Control~Control} is a descendant of + * another control. + * + * Note: Oddly, a control is considered to be a descendant of itself. + * + * @method + * @param {module:enyo/Control~Control} ancestor - The [control]{@link module:enyo/Control~Control} whose lineage + * will be checked to determine whether the current control is a descendant. + * @public + */ + isDescendantOf: function (ancestor) { + var p = this; + while (p && p!=ancestor) { + p = p.parent; + } + return ancestor && (p === ancestor); + }, + + /** + * Returns all controls. + * + * @method + * @returns {module:enyo/Control~Control[]} An [array]{@glossary Array} of [controls]{@link module:enyo/Control~Control}. + * @public + */ + getControls: function () { + return this.controls; + }, + + /** + * Returns all non-chrome controls. + * + * @method + * @returns {module:enyo/Control~Control[]} An [array]{@glossary Array} of [controls]{@link module:enyo/Control~Control}. + * @public + */ + getClientControls: function () { + var results = []; + for (var i=0, cs=this.controls, c; (c=cs[i]); i++) { + if (!c.isChrome) { + results.push(c); + } + } + return results; + }, + + /** + * Destroys "client controls", the same set of [controls]{@link module:enyo/Control~Control} returned by + * [getClientControls()]{@link module:enyo/UiComponent~UiComponent#getClientControls}. + * + * @method + * @public + */ + destroyClientControls: function () { + var c$ = this.getClientControls(); + for (var i=0, c; (c=c$[i]); i++) { + c.destroy(); + } + }, + + /** + * @method + * @private + */ + addControl: function (ctl, before) { + // Called to add an already created control to the object's control list. It is + // not used to create controls and should likely not be called directly. + // It can be overridden to detect when controls are added. + if (before !== undefined) { + var idx = (before === null) ? 0 : this.indexOfControl(before); + this.controls.splice(idx, 0, ctl); + } else { + this.controls.push(ctl); + } + // When we add a Control, we also establish a parent. + this.addChild(ctl, before); + }, + + /** + * @method + * @private + */ + removeControl: function (ctl) { + // Called to remove a control from the object's control list. As with addControl it + // can be overridden to detect when controls are removed. + // When we remove a Control, we also remove it from its parent. + ctl.setParent(null); + return utils.remove(ctl, this.controls); + }, + + /** + * @method + * @private + */ + indexOfControl: function (ctl) { + return utils.indexOf(ctl, this.controls); + }, + + /** + * @method + * @private + */ + indexOfClientControl: function (ctl) { + return utils.indexOf(ctl, this.getClientControls()); + }, + + /** + * @method + * @private + */ + indexInContainer: function () { + return this.container.indexOfControl(this); + }, + + /** + * @method + * @private + */ + clientIndexInContainer: function () { + return this.container.indexOfClientControl(this); + }, + + /** + * @method + * @private + */ + controlAtIndex: function (idx) { + return this.controls[idx]; + }, + + /** + * Determines what the following sibling [control]{@link module:enyo/Control~Control} is for the current + * [control]{@link module:enyo/Control~Control}. + * + * @method + * @returns {module:enyo/Control~Control | null} The [control]{@link module:enyo/Control~Control} that is the] following + * sibling. If no following sibling exists, we return `null`. + * @public + */ + getNextControl: function () { + var comps = this.getParent().children, + comp, + sibling, + i; + + for (i = comps.length - 1; i >= 0; i--) { + comp = comps[i]; + if (comp === this) return sibling ? sibling : null; + if (comp.generated) sibling = comp; + } + + return null; + }, + + /** + * Children + * + * @method + * @private + */ + addChild: function (child, before) { + // if before is undefined, add to the end of the child list. + // If it's null, add to front of list, otherwise add before the + // specified control. + // + // allow delegating the child to a different container + if (this.controlParent /*&& !child.isChrome*/) { + // this.controlParent might have a controlParent, and so on; seek the ultimate parent + this.controlParent.addChild(child, before); + } else { + // NOTE: addChild drives setParent. + // It's the opposite for setContainer, where containerChanged (in Containable) + // drives addControl. + // Because of the way 'parent' is derived from 'container', this difference is + // helpful for implementing controlParent. + // By the same token, since 'parent' is derived from 'container', setParent is + // not intended to be called by client code. Therefore, the lack of parallelism + // should be private to this implementation. + // Set the child's parent property to this + child.setParent(this); + // track in children array + if (before !== undefined) { + var idx = (before === null) ? 0 : this.indexOfChild(before); + this.children.splice(idx, 0, child); + } else { + this.children.push(child); + } + } + }, + + /** + * @method + * @private + */ + removeChild: function (child) { + return utils.remove(child, this.children); + }, + + /** + * @method + * @private + */ + indexOfChild: function (child) { + return utils.indexOf(child, this.children); + }, + + /** + * @method + * @private + */ + layoutKindChanged: function () { + if (this.layout) { + this.layout.destroy(); + } + this.layout = kind.createFromKind(this.layoutKind, this); + if (this.generated) { + this.render(); + } + }, + + /** + * @method + * @private + */ + flow: function () { + if (this.layout) { + this.layout.flow(); + } + }, + + /** + * CAVEAT: currently we use the entry point for both post-render layout work *and* + * post-resize layout work. + * @method + * @private + */ + reflow: function () { + if (this.layout) { + this.layout.reflow(); + } + }, + + /** + * Call after this [control]{@link module:enyo/Control~Control} has been resized to allow it to process the + * size change. To respond to a resize, override `handleResize()` instead. Acts as syntactic + * sugar for `waterfall('onresize')`. + * + * @method + * @public + */ + resize: function () { + this.waterfall('onresize'); + this.waterfall('onpostresize'); + }, + + /** + * @method + * @private + */ + handleResize: function () { + // FIXME: once we are in the business of reflowing layouts on resize, then we have an + // inside/outside problem: some scenarios will need to reflow before child + // controls reflow, and some will need to reflow after. Even more complex scenarios + // have circular dependencies, and can require multiple passes or other resolution. + // When we can rely on CSS to manage reflows we do not have these problems. + this.reflow(); + }, + + /** + * Sends a message to all of my descendants, but not myself. You can stop a + * [waterfall]{@link module:enyo/Component~Component#waterfall} into [components]{@link module:enyo/Component~Component} + * owned by a receiving [object]{@glossary Object} by returning a truthy value from the + * {@glossary event} [handler]{@link module:enyo/Component~Component~EventHandler}. + * + * @method + * @param {String} nom - The name of the {@glossary event}. + * @param {Object} [event] - The event object to pass along. + * @param {module:enyo/Component~Component} [sender=this] - The event's originator. + * @returns {this} The callee for chaining. + * @public + */ + waterfallDown: function (nom, event, sender) { + event = event || {}; + // Note: Controls will generally be both in a $ hash and a child list somewhere. + // Attempt to avoid duplicated messages by sending only to components that are not + // UiComponent, as those components are guaranteed not to be in a child list. + // May cause a problem if there is a scenario where a UiComponent owns a pure + // Component that in turn owns Controls. + // + // waterfall to all pure components + for (var n in this.$) { + if (!(this.$[n] instanceof UiComponent)) { + this.$[n].waterfall(nom, event, sender); + } + } + // waterfall to my children + for (var i=0, cs=this.children, c; (c=cs[i]); i++) { + c.waterfall(nom, event, sender); + } + }, + + /** + * @method + * @private + */ + getBubbleTarget: function (nom, event) { + if (event.delegate) return this.owner; + else { + return ( + this.bubbleTarget + || (this.cachedBubble && this.cachedBubbleTarget[nom]) + || this.parent + || this.owner + ); + } + }, + + /** + * @method + * @private + */ + bubbleTargetChanged: function (was) { + if (was && this.cachedBubble && this.cachedBubbleTarget) { + for (var n in this.cachedBubbleTarget) { + if (this.cachedBubbleTarget[n] === was) delete this.cachedBubbleTarget[n]; + } + } + } +}); + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./master':'enyo/master','./AnimationSupport/AnimationSupport':'enyo/AnimationSupport/AnimationSupport','./AnimationSupport/AnimationInterfaceSupport':'enyo/AnimationSupport/AnimationInterfaceSupport','./Component':'enyo/Component'}],'enyo/gesture':[function (module,exports,global,require,request){ +require('enyo'); +/** + * @module enyo/gesture + */ + + +var + dispatcher = require('../dispatcher'), + dom = require('../dom'), + platform = require('../platform'), + utils = require('../utils'); + +var + drag = require('./drag'), + touchGestures = require('./touchGestures'), + gestureUtil = require('./util'); + + +/** +* Enyo supports a set of normalized events that work similarly across all supported platforms. +* These events are provided so that users can write a single set of event handlers for +* applications that run on both mobile and desktop platforms. They are needed because desktop +* and mobile platforms handle basic input differently. +* +* For more information on normalized input events and their associated properties, see the +* documentation on [Event Handling]{@linkplain $dev-guide/key-concepts/event-handling.html} +* in the Enyo Developer Guide. +* +* @module enyo/gesture +* @public +*/ +var gesture = module.exports = { + /** + * Handles "down" [events]{@glossary event}, including `mousedown` and `keydown`. This is + * responsible for the press-and-hold key repeater. + * + * @param {Event} evt - The standard {@glossary event} [object]{glossary Object}. + * @public + */ + down: function(evt) { + var e = gestureUtil.makeEvent('down', evt); + + // prepare for hold + drag.prepareHold(e); + + // enable prevention of tap event + e.preventTap = function() { + e._tapPrevented = true; + }; + + dispatcher.dispatch(e); + this.downEvent = e; + + // start hold, now that control has had a chance + // to override the holdPulse configuration + drag.beginHold(e); + }, + + /** + * Handles `mousemove` [events]{@glossary event}. + * + * @param {Event} evt - The standard {@glossary event} [object]{glossary Object}. + * @public + */ + move: function(evt) { + var e = gestureUtil.makeEvent('move', evt); + // include delta and direction v. down info in move event + e.dx = e.dy = e.horizontal = e.vertical = 0; + if (e.which && this.downEvent) { + e.dx = evt.clientX - this.downEvent.clientX; + e.dy = evt.clientY - this.downEvent.clientY; + e.horizontal = Math.abs(e.dx) > Math.abs(e.dy); + e.vertical = !e.horizontal; + } + dispatcher.dispatch(e); + }, + + /** + * Handles "up" [events]{@glossary event}, including `mouseup` and `keyup`. + * + * @param {Event} evt - The standard {@glossary event} [object]{glossary Object}. + * @public + */ + up: function(evt) { + var e = gestureUtil.makeEvent('up', evt); + + // We have added some logic to synchronize up and down events in certain scenarios (i.e. + // clicking multiple buttons with a mouse) and to generally guard against any potential + // asymmetry, but a full solution would be to maintain a map of up/down events as an + // ideal solution, for future work. + e._tapPrevented = this.downEvent && this.downEvent._tapPrevented && this.downEvent.which == e.which; + e.preventTap = function() { + e._tapPrevented = true; + }; + + dispatcher.dispatch(e); + if (!e._tapPrevented && this.downEvent && this.downEvent.which == 1) { + var target = this.findCommonAncestor(this.downEvent.target, evt.target); + + // the common ancestor of the down/up events is the target of the tap + if(target) { + if(this.supportsDoubleTap(target)) { + this.doubleTap(e, target); + } else { + this.sendTap(e, target); + } + } + } + if (this.downEvent && this.downEvent.which == e.which) { + this.downEvent = null; + } + }, + + /** + * Handles `mouseover` [events]{@glossary event}. + * + * @param {Event} evt - The standard {@glossary event} [object]{glossary Object}. + * @public + */ + over: function(evt) { + var e = gestureUtil.makeEvent('enter', evt); + dispatcher.dispatch(e); + }, + + /** + * Handles `mouseout` [events]{@glossary event}. + * + * @param {Event} evt - The standard {@glossary event} [object]{glossary Object}. + * @public + */ + out: function(evt) { + var e = gestureUtil.makeEvent('leave', evt); + dispatcher.dispatch(e); + }, + + /** + * Generates `tap` [events]{@glossary event}. + * + * @param {Event} evt - The standard {@glossary event} [object]{glossary Object}. + * @public + */ + sendTap: function(evt, target) { + var e = gestureUtil.makeEvent('tap', evt); + e.target = target; + dispatcher.dispatch(e); + }, + + /** + * @private + */ + tapData: { + id: null, + timer: null, + start: 0 + }, + + /** + * Global configuration for double tap support. If this is true, all tap events for Controls + * that do not have {@link module:enyo/Control~Control#doubleTapEnabled} explicitly set to false will be + * delayed by the {@link module:enyo/Control~Control#doubleTapInterval}. + * + * @type {Boolean} + * @default false + * @public + */ + doubleTapEnabled: false, + + /** + * Determines if the provided target node supports double tap events + * + * @param {Node} target + * @return {Boolean} + * @private + */ + supportsDoubleTap: function(target) { + var obj = dispatcher.findDispatchTarget(target); + + if(obj) { + // Control.doubleTapEnabled is a tri-value property. The default is 'inherit' + // which takes its cue from gesture's doubleTapEnabled. Values of true or false + // override the default. So, if the global is true, any truthy value on Control + // results in true. If the global is false, only an explicit true on Control + // results in true. + return this.doubleTapEnabled? !!obj.doubleTapEnabled : obj.doubleTapEnabled === true; + } else { + return false; + } + }, + + /** + * @private + */ + doubleTap: function(evt, t) { + var obj = dispatcher.findDispatchTarget(t); + + if(this.tapData.id !== obj.id) { // this is the first tap + this.resetTapData(true); + + this.tapData.id = obj.id; + this.tapData.event = evt; + this.tapData.target = t; + this.tapData.timer = setTimeout(utils.bind(this, "resetTapData", true), obj.doubleTapInterval); + this.tapData.start = utils.perfNow(); + } else { // this is the double tap + var e2 = gestureUtil.makeEvent('doubletap', evt); + e2.target = t; + e2.tapInterval = utils.perfNow() - this.tapData.start; + this.resetTapData(false); + dispatcher.dispatch(e2); + } + }, + + resetTapData: function(sendTap) { + var data = this.tapData; + + if(sendTap && data.id) { + this.sendTap(data.event, data.target); + } + + clearTimeout(data.timer); + data.id = data.start = data.event = data.target = data.timer = null; + }, + + /** + * Given two [DOM nodes]{@glossary Node}, searches for a shared ancestor (looks up + * the hierarchic [DOM]{@glossary DOM} tree of [nodes]{@glossary Node}). The shared + * ancestor node is returned. + * + * @param {Node} controlA - Control one. + * @param {Node} controlB - Control two. + * @returns {(Node|undefined)} The shared ancestor. + * @public + */ + findCommonAncestor: function(controlA, controlB) { + var p = controlB; + while (p) { + if (this.isTargetDescendantOf(controlA, p)) { + return p; + } + p = p.parentNode; + } + }, + + /** + * Given two controls, returns `true` if the `child` is inside the `parent`. + * + * @param {Node} child - The child to search for. + * @param {Node} parent - The expected parent. + * @returns {(Boolean|undefined)} `true` if the `child` is actually a child of `parent`. + */ + isTargetDescendantOf: function(child, parent) { + var c = child; + while(c) { + if (c == parent) { + return true; + } + c = c.parentNode; + } + }, + + /** + * @todo I'd rather refine the public API of gesture rather than simply forwarding the internal + * drag module but this will work in the interim. - ryanjduffy + * + * Known Consumers: + * - Spotlight.onAcceleratedKey - (prepare|begin|end)Hold() + * - Moonstone - configureHoldPulse() + */ + drag: drag +}; + +/** +* Contains various methods for gesture events. +* +* @type {object} +* @public +*/ +module.exports.events = { + /** + * Shortcut to [gesture.down()]{@link module:enyo/gesture#down}. + * + * @memberof! module:enyo/gesture# + * @method events.mousedown + * @public + */ + mousedown: function(e) { + gesture.down(e); + }, + + /** + * Shortcut to [gesture.up()]{@link module:enyo/gesture#up}. + * + * @memberof! module:enyo/gesture# + * @method events.mouseup + * @public + */ + mouseup: function(e) { + gesture.up(e); + }, + + /** + * Shortcut to [gesture.move()]{@link module:enyo/gesture#move}. + * + * @memberof! module:enyo/gesture# + * @method events.mousemove + * @public + */ + mousemove: function(e) { + gesture.move(e); + }, + + /** + * Shortcut to [gesture.over()]{@link module:enyo/gesture#over}. + * + * @memberof! module:enyo/gesture# + * @method events.mouseover + * @public + */ + mouseover: function(e) { + gesture.over(e); + }, + + /** + * Shortcut to [gesture.out()]{@link module:enyo/gesture#out}. + * + * @memberof! module:enyo/gesture# + * @method events.mouseout + * @public + */ + mouseout: function(e) { + gesture.out(e); + } +}; + +// Firefox mousewheel handling +dom.requiresWindow(function() { + if (document.addEventListener) { + document.addEventListener('DOMMouseScroll', function(inEvent) { + var e = utils.clone(inEvent); + e.preventDefault = function() { + inEvent.preventDefault(); + }; + e.type = 'mousewheel'; + var p = e.VERTICAL_AXIS == e.axis ? 'wheelDeltaY' : 'wheelDeltaX'; + e[p] = e.detail * -40; + dispatcher.dispatch(e); + }, false); + } +}); + +/** +* @private +*/ +var handlers = { + touchstart: true, + touchmove: true, + touchend: true +}; + +/** +* @private +*/ +dispatcher.features.push(function (e) { + var type = e.type; + + // NOTE: beware of properties in gesture.events and drag inadvertently mapped to event types + if (gesture.events[type]) { + gesture.events[type](e); + } + if (!platform.gesture && platform.touch && handlers[type]) { + touchGestures[type](e); + } + if (drag[type]) { + drag[type](e); + } +}); + +},{'../dispatcher':'enyo/dispatcher','../dom':'enyo/dom','../platform':'enyo/platform','../utils':'enyo/utils','./drag':'enyo/gesture/drag','./touchGestures':'enyo/gesture/touchGestures','./util':'enyo/gesture/util'}],'enyo/ViewController':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/ViewController~ViewController} kind. +* @module enyo/ViewController +*/ + +var + kind = require('./kind'), + utils = require('./utils'); + +var + Controller = require('./Controller'), + UiComponent = require('./UiComponent'); + +var + Dom = require('./dom'); + +/** +* {@link module:enyo/ViewController~ViewController} is designed to manage the lifecycle of a particular view +* ({@link module:enyo/Control~Control}) that it owns. It is capable of controlling when a view is inserted into +* the DOM and where, managing [events]{@glossary event} bubbled from the view, and isolating (or +* encapsulating) the entire view hierarchy below it. Alternatively, it may be implemented as a +* [component]{@link module:enyo/Component~Component} in a larger hierarchy, in which case it will inject its view +* into its parent rather than directly into the DOM. And, of course, a ViewController may be +* used as the `controller` property of another view, although this usage will (by default) +* result in the removal of its own view from the {@link module:enyo/Component~Component} bubbling hierarchy. +* +* Note that `enyo.ViewController` may have components defined in its +* `components` [array]{@glossary Array}, but these components should +* not be `enyo.Controls`. +* +* @class ViewController +* @extends module:enyo/Controller~Controller +* @public +*/ +module.exports = kind( + /** @lends module:enyo/ViewController~ViewController.prototype */ { + + name: 'enyo.ViewController', + + /** + * @private + */ + kind: Controller, + + /** + * The `view` property may either be a [constructor]{@glossary constructor}, an + * instance of {@link module:enyo/Control~Control}, an [object]{@glossary Object} + * description of the view ([object literal/hash]), or `null` if it will be + * set later. Setting this property to a constructor or string naming a kind + * will automatically create an instance of that kind according to this + * controller's settings. If the `view` is set to an instance, it will be + * rendered according to the properties of the controller. If this property + * is a constructor, it will be preserved in the + * [viewKind]{@link module:enyo/ViewController~ViewController#viewKind} property. Once + * initialization is complete, the instance of this controller's view will be + * available via this property. + * + * @type {Control|Function|Object} + * @default null + * @public + */ + view: null, + + /** + * The preserved [kind]{@glossary kind} for this controller's view. You may + * set this to a [constructor]{@glossary constructor} (or the + * [view]{@link module:enyo/ViewController~ViewController#view} property). In either case, if a + * view is set explicitly or this property is used, the constructor will be + * available via this property. + * + * @type {Function} + * @default null + * @public + */ + viewKind: null, + + /** + * Designates where the controller's view will render. This should be a + * string consisting of either `'document.body'` (the default) or the DOM id + * of a node (either inserted by an {@link module:enyo/Control~Control} or static HTML + * already in the `document.body`). If the controller has a parent (because + * it was instantiated as a component in an `enyo.Control`, this property + * will be ignored and the view will instead be rendered in the parent. This + * will not happen if the controller is a component of {@link module:enyo/Component~Component} + * or is set as the `controller` property of an `enyo.Control`. + * + * @type {String} + * @default 'document.body' + * @public + */ + renderTarget: 'document.body', + + /** + * When the view of the controller has its [destroy()]{@link module:enyo/Control~Control#destroy} + * method called, it automatically triggers its own removal from the controller's + * [view]{@link module:enyo/ViewController~ViewController#view} property. By default, the controller + * will not create a new view (from [viewKind]{@link module:enyo/ViewController~ViewController#viewKind}) + * automatically unless this flag is set to `true`. + * + * @type {Boolean} + * @default false + * @public + */ + resetView: false, + + /** + * Renders the controller's view, if possible. If the controller is a + * component of a [UiComponent]{@link module:enyo/UiComponent~UiComponent}, the view will be + * rendered into its container; otherwise, the view will be rendered into the + * controller's [renderTarget]{@link module:enyo/ViewController~ViewController#renderTarget}. If + * the view is already rendered, this method will do nothing. + * + * @param {String} [target] - When specified, this value will be used instead of + * [renderTarget]{@link module:enyo/ViewController~ViewController#renderTarget}. + * @public + */ + render: function (target) { + var v = this.view, + t = target || this.renderTarget; + if (v) { + if (v.hasNode() && v.generated) { return; } + // here we test to see if we need to render it into our target node or the container + if (this.container) { + v.render(); + } else { + v.renderInto(Dom.byId(t) || utils.getPath.call(window, t)); + } + } + }, + + /** + * Renders the view into the specified `target` and sets the + * [renderTarget]{@link module:enyo/ViewController~ViewController#renderTarget} property to + * `target`. + * + * @param {String} target - Where the view will be rendered into. + * @public + */ + renderInto: function (target) { + this.render((this.renderTarget=target)); + }, + + /** + * Responds to changes in the controller's [view]{@link module:enyo/ViewController~ViewController#view} + * property during initialization or whenever `set('view', ...)` is called. + * If a [constructor]{@glossary constructor} is found, it will be instanced + * or resolved from a [string]{@glossary String}. If a previous view exists + * and the controller is its [owner]{@link module:enyo/Component~Component#owner}, it will be + * destroyed; otherwise, it will simply be removed. + * + * @private + */ + viewChanged: function (previous) { + if (previous) { + previous.set('bubbleTarget', null); + if (previous.owner === this && !previous.destroyed) { + previous.destroy(); + } + if (previous.destroyed && !this.resetView) { + return; + } + } + var v = this.view; + + // if it is a function we need to instance it + if (typeof v == 'function') { + // save the constructor for later + this.viewKind = v; + v = null; + } + + if ((!v && this.viewKind) || (v && typeof v == 'object' && !(v instanceof UiComponent))) { + var d = (typeof v == 'object' && v !== null && !v.destroyed && v) || {kind: this.viewKind}, + s = this; + // in case it isn't set... + d.kind = d.kind || this.viewKind || kind.getDefaultCtor(); + v = this.createComponent(d, { + owner: this, + // if this controller is a component of a UiComponent kind then it + // will have assigned a container that we can add to the child + // so it will register as a child and control to be rendered in the + // correct location + container: this.container || null, + bubbleTarget: this + }); + v.extend({ + destroy: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + // if the bubble target is the view contorller then we need to + // let it know we've been destroyed + if (this.bubbleTarget === s) { + this.bubbleTarget.set('view', null); + } + }; + }) + }); + } else if (v && v instanceof UiComponent) { + // make sure we grab the constructor from an instance so we know what kind + // it was to recreate later if necessary + if (!this.viewKind) { + this.viewKind = v.ctor; + } + v.set('bubbleTarget', this); + } + this.view = v; + }, + + /** + * @method + * @private + */ + create: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + this.viewChanged(); + }; + }), + + /** + * @method + * @private + */ + destroy: kind.inherit(function (sup) { + return function () { + this.view = null; + this.viewKind = null; + sup.apply(this, arguments); + }; + }), + /** + The `controller` can't be the instance owner of its child view for event + propagation reasons. When this flag is `true`, it ensures that events will + not be handled multiple times (by the `controller` and its `view` + separately). + */ + notInstanceOwner: true +}); + +},{'./kind':'enyo/kind','./utils':'enyo/utils','./Controller':'enyo/Controller','./UiComponent':'enyo/UiComponent','./dom':'enyo/dom'}],'enyo/Control':[function (module,exports,global,require,request){ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/Control~Control} kind. +* @module enyo/Control +*/ + +var + kind = require('../kind'), + utils = require('../utils'), + platform = require('../platform'), + dispatcher = require('../dispatcher'), + options = require('../options'), + roots = require('../roots'); + +var + AccessibilitySupport = require('../AccessibilitySupport'), + UiComponent = require('../UiComponent'), + HTMLStringDelegate = require('../HTMLStringDelegate'), + Dom = require('../dom'); + +var + fullscreen = require('./fullscreen'), + FloatingLayer = require('./floatingLayer'); + +// While the namespace isn't needed here, gesture is required for ontap events for which Control +// has a handler. Bringing them all in now for the time being. +require('../gesture'); + +var nodePurgatory; + +/** +* Called by `Control.teardownRender()`. In certain circumstances, +* we need to temporarily keep a DOM node around after tearing down +* because we're still acting on a stream of touch events emanating +* from the node. See `Control.retainNode()` for more information. +* +* @private +*/ +function storeRetainedNode (control) { + var p = getNodePurgatory(), + n = control._retainedNode; + if (n) { + p.appendChild(n); + } + control._retainedNode = null; +} + +/** +* Called (via a callback) when it's time to release a DOM node +* that we've retained. +* +* @private +*/ +function releaseRetainedNode (retainedNode) { + var p = getNodePurgatory(); + if (retainedNode) { + p.removeChild(retainedNode); + } +} + +/** +* Lazily add a hidden `

%s
', indent(), utils.escape(test.title)); + var code = utils.escape(utils.clean(test.fn.toString())); + console.log('%s
%s
', indent(), code); + }); + + runner.on('fail', function(test, err) { + console.log('%s
%s
', indent(), utils.escape(test.title)); + var code = utils.escape(utils.clean(test.fn.toString())); + console.log('%s
%s
', indent(), code); + console.log('%s
%s
', indent(), utils.escape(err)); + }); +} + +},{"../utils":39,"./base":17}],19:[function(require,module,exports){ +(function (process){ +/** + * Module dependencies. + */ + +var Base = require('./base'); +var create = require('lodash.create'); +var color = Base.color; + +/** + * Expose `Dot`. + */ + +exports = module.exports = Dot; + +/** + * Initialize a new `Dot` matrix test reporter. + * + * @api public + * @param {Runner} runner + */ +function Dot(runner) { + Base.call(this, runner); + + var self = this; + var width = Base.window.width * .75 | 0; + var n = -1; + + runner.on('start', function() { + process.stdout.write('\n'); + }); + + runner.on('pending', function() { + if (++n % width === 0) { + process.stdout.write('\n '); + } + process.stdout.write(color('pending', Base.symbols.dot)); + }); + + runner.on('pass', function(test) { + if (++n % width === 0) { + process.stdout.write('\n '); + } + if (test.speed === 'slow') { + process.stdout.write(color('bright yellow', Base.symbols.dot)); + } else { + process.stdout.write(color(test.speed, Base.symbols.dot)); + } + }); + + runner.on('fail', function() { + if (++n % width === 0) { + process.stdout.write('\n '); + } + process.stdout.write(color('fail', Base.symbols.dot)); + }); + + runner.on('end', function() { + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +Dot.prototype = create(Base.prototype, { + constructor: Dot +}); + +}).call(this,require('_process')) +},{"./base":17,"_process":50,"lodash.create":70}],20:[function(require,module,exports){ +(function (process,__dirname){ +/** + * Module dependencies. + */ + +var JSONCov = require('./json-cov'); +var readFileSync = require('fs').readFileSync; +var join = require('path').join; + +/** + * Expose `HTMLCov`. + */ + +exports = module.exports = HTMLCov; + +/** + * Initialize a new `JsCoverage` reporter. + * + * @api public + * @param {Runner} runner + */ +function HTMLCov(runner) { + var jade = require('jade'); + var file = join(__dirname, '/templates/coverage.jade'); + var fn = jade.compile(str, { filename: file }); + var self = this; + var str = readFileSync(file, 'utf8'); + + JSONCov.call(this, runner, false); + + runner.on('end', function() { + process.stdout.write(fn({ + cov: self.cov, + coverageClass: coverageClass + })); + }); +} + +/** + * Return coverage class for a given coverage percentage. + * + * @api private + * @param {number} coveragePctg + * @return {string} + */ +function coverageClass(coveragePctg) { + if (coveragePctg >= 75) { + return 'high'; + } + if (coveragePctg >= 50) { + return 'medium'; + } + if (coveragePctg >= 25) { + return 'low'; + } + return 'terrible'; +} + +}).call(this,require('_process'),"/lib/reporters") +},{"./json-cov":23,"_process":50,"fs":41,"jade":41,"path":41}],21:[function(require,module,exports){ +(function (global){ +/* eslint-env browser */ + +/** + * Module dependencies. + */ + +var Base = require('./base'); +var utils = require('../utils'); +var Progress = require('../browser/progress'); +var escapeRe = require('escape-string-regexp'); +var escape = utils.escape; + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +/* eslint-disable no-unused-vars, no-native-reassign */ +var Date = global.Date; +var setTimeout = global.setTimeout; +var setInterval = global.setInterval; +var clearTimeout = global.clearTimeout; +var clearInterval = global.clearInterval; +/* eslint-enable no-unused-vars, no-native-reassign */ + +/** + * Expose `HTML`. + */ + +exports = module.exports = HTML; + +/** + * Stats template. + */ + +var statsTemplate = ''; + +/** + * Initialize a new `HTML` reporter. + * + * @api public + * @param {Runner} runner + */ +function HTML(runner) { + Base.call(this, runner); + + var self = this; + var stats = this.stats; + var stat = fragment(statsTemplate); + var items = stat.getElementsByTagName('li'); + var passes = items[1].getElementsByTagName('em')[0]; + var passesLink = items[1].getElementsByTagName('a')[0]; + var failures = items[2].getElementsByTagName('em')[0]; + var failuresLink = items[2].getElementsByTagName('a')[0]; + var duration = items[3].getElementsByTagName('em')[0]; + var canvas = stat.getElementsByTagName('canvas')[0]; + var report = fragment('
    '); + var stack = [report]; + var progress; + var ctx; + var root = document.getElementById('mocha'); + + if (canvas.getContext) { + var ratio = window.devicePixelRatio || 1; + canvas.style.width = canvas.width; + canvas.style.height = canvas.height; + canvas.width *= ratio; + canvas.height *= ratio; + ctx = canvas.getContext('2d'); + ctx.scale(ratio, ratio); + progress = new Progress(); + } + + if (!root) { + return error('#mocha div missing, add it to your document'); + } + + // pass toggle + on(passesLink, 'click', function() { + unhide(); + var name = (/pass/).test(report.className) ? '' : ' pass'; + report.className = report.className.replace(/fail|pass/g, '') + name; + if (report.className.trim()) { + hideSuitesWithout('test pass'); + } + }); + + // failure toggle + on(failuresLink, 'click', function() { + unhide(); + var name = (/fail/).test(report.className) ? '' : ' fail'; + report.className = report.className.replace(/fail|pass/g, '') + name; + if (report.className.trim()) { + hideSuitesWithout('test fail'); + } + }); + + root.appendChild(stat); + root.appendChild(report); + + if (progress) { + progress.size(40); + } + + runner.on('suite', function(suite) { + if (suite.root) { + return; + } + + // suite + var url = self.suiteURL(suite); + var el = fragment('
  • %s

  • ', url, escape(suite.title)); + + // container + stack[0].appendChild(el); + stack.unshift(document.createElement('ul')); + el.appendChild(stack[0]); + }); + + runner.on('suite end', function(suite) { + if (suite.root) { + return; + } + stack.shift(); + }); + + runner.on('fail', function(test) { + if (test.type === 'hook') { + runner.emit('test end', test); + } + }); + + runner.on('test end', function(test) { + // TODO: add to stats + var percent = stats.tests / this.total * 100 | 0; + if (progress) { + progress.update(percent).draw(ctx); + } + + // update stats + var ms = new Date() - stats.start; + text(passes, stats.passes); + text(failures, stats.failures); + text(duration, (ms / 1000).toFixed(2)); + + // test + var el; + if (test.state === 'passed') { + var url = self.testURL(test); + el = fragment('
  • %e%ems ‣

  • ', test.speed, test.title, test.duration, url); + } else if (test.pending) { + el = fragment('
  • %e

  • ', test.title); + } else { + el = fragment('
  • %e ‣

  • ', test.title, self.testURL(test)); + var stackString; // Note: Includes leading newline + var message = test.err.toString(); + + // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we + // check for the result of the stringifying. + if (message === '[object Error]') { + message = test.err.message; + } + + if (test.err.stack) { + var indexOfMessage = test.err.stack.indexOf(test.err.message); + if (indexOfMessage === -1) { + stackString = test.err.stack; + } else { + stackString = test.err.stack.substr(test.err.message.length + indexOfMessage); + } + } else if (test.err.sourceURL && test.err.line !== undefined) { + // Safari doesn't give you a stack. Let's at least provide a source line. + stackString = '\n(' + test.err.sourceURL + ':' + test.err.line + ')'; + } + + stackString = stackString || ''; + + if (test.err.htmlMessage && stackString) { + el.appendChild(fragment('
    %s\n
    %e
    ', test.err.htmlMessage, stackString)); + } else if (test.err.htmlMessage) { + el.appendChild(fragment('
    %s
    ', test.err.htmlMessage)); + } else { + el.appendChild(fragment('
    %e%e
    ', message, stackString)); + } + } + + // toggle code + // TODO: defer + if (!test.pending) { + var h2 = el.getElementsByTagName('h2')[0]; + + on(h2, 'click', function() { + pre.style.display = pre.style.display === 'none' ? 'block' : 'none'; + }); + + var pre = fragment('
    %e
    ', utils.clean(test.fn.toString())); + el.appendChild(pre); + pre.style.display = 'none'; + } + + // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack. + if (stack[0]) { + stack[0].appendChild(el); + } + }); +} + +/** + * Makes a URL, preserving querystring ("search") parameters. + * + * @param {string} s + * @return {string} A new URL. + */ +function makeUrl(s) { + var search = window.location.search; + + // Remove previous grep query parameter if present + if (search) { + search = search.replace(/[?&]grep=[^&\s]*/g, '').replace(/^&/, '?'); + } + + return window.location.pathname + (search ? search + '&' : '?') + 'grep=' + encodeURIComponent(escapeRe(s)); +} + +/** + * Provide suite URL. + * + * @param {Object} [suite] + */ +HTML.prototype.suiteURL = function(suite) { + return makeUrl(suite.fullTitle()); +}; + +/** + * Provide test URL. + * + * @param {Object} [test] + */ +HTML.prototype.testURL = function(test) { + return makeUrl(test.fullTitle()); +}; + +/** + * Display error `msg`. + * + * @param {string} msg + */ +function error(msg) { + document.body.appendChild(fragment('
    %s
    ', msg)); +} + +/** + * Return a DOM fragment from `html`. + * + * @param {string} html + */ +function fragment(html) { + var args = arguments; + var div = document.createElement('div'); + var i = 1; + + div.innerHTML = html.replace(/%([se])/g, function(_, type) { + switch (type) { + case 's': return String(args[i++]); + case 'e': return escape(args[i++]); + // no default + } + }); + + return div.firstChild; +} + +/** + * Check for suites that do not have elements + * with `classname`, and hide them. + * + * @param {text} classname + */ +function hideSuitesWithout(classname) { + var suites = document.getElementsByClassName('suite'); + for (var i = 0; i < suites.length; i++) { + var els = suites[i].getElementsByClassName(classname); + if (!els.length) { + suites[i].className += ' hidden'; + } + } +} + +/** + * Unhide .hidden suites. + */ +function unhide() { + var els = document.getElementsByClassName('suite hidden'); + for (var i = 0; i < els.length; ++i) { + els[i].className = els[i].className.replace('suite hidden', 'suite'); + } +} + +/** + * Set an element's text contents. + * + * @param {HTMLElement} el + * @param {string} contents + */ +function text(el, contents) { + if (el.textContent) { + el.textContent = contents; + } else { + el.innerText = contents; + } +} + +/** + * Listen on `event` with callback `fn`. + */ +function on(el, event, fn) { + if (el.addEventListener) { + el.addEventListener(event, fn, false); + } else { + el.attachEvent('on' + event, fn); + } +} + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"../browser/progress":4,"../utils":39,"./base":17,"escape-string-regexp":67}],22:[function(require,module,exports){ +// Alias exports to a their normalized format Mocha#reporter to prevent a need +// for dynamic (try/catch) requires, which Browserify doesn't handle. +exports.Base = exports.base = require('./base'); +exports.Dot = exports.dot = require('./dot'); +exports.Doc = exports.doc = require('./doc'); +exports.TAP = exports.tap = require('./tap'); +exports.JSON = exports.json = require('./json'); +exports.HTML = exports.html = require('./html'); +exports.List = exports.list = require('./list'); +exports.Min = exports.min = require('./min'); +exports.Spec = exports.spec = require('./spec'); +exports.Nyan = exports.nyan = require('./nyan'); +exports.XUnit = exports.xunit = require('./xunit'); +exports.Markdown = exports.markdown = require('./markdown'); +exports.Progress = exports.progress = require('./progress'); +exports.Landing = exports.landing = require('./landing'); +exports.JSONCov = exports['json-cov'] = require('./json-cov'); +exports.HTMLCov = exports['html-cov'] = require('./html-cov'); +exports.JSONStream = exports['json-stream'] = require('./json-stream'); + +},{"./base":17,"./doc":18,"./dot":19,"./html":21,"./html-cov":20,"./json":25,"./json-cov":23,"./json-stream":24,"./landing":26,"./list":27,"./markdown":28,"./min":29,"./nyan":30,"./progress":31,"./spec":32,"./tap":33,"./xunit":34}],23:[function(require,module,exports){ +(function (process,global){ +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `JSONCov`. + */ + +exports = module.exports = JSONCov; + +/** + * Initialize a new `JsCoverage` reporter. + * + * @api public + * @param {Runner} runner + * @param {boolean} output + */ +function JSONCov(runner, output) { + Base.call(this, runner); + + output = arguments.length === 1 || output; + var self = this; + var tests = []; + var failures = []; + var passes = []; + + runner.on('test end', function(test) { + tests.push(test); + }); + + runner.on('pass', function(test) { + passes.push(test); + }); + + runner.on('fail', function(test) { + failures.push(test); + }); + + runner.on('end', function() { + var cov = global._$jscoverage || {}; + var result = self.cov = map(cov); + result.stats = self.stats; + result.tests = tests.map(clean); + result.failures = failures.map(clean); + result.passes = passes.map(clean); + if (!output) { + return; + } + process.stdout.write(JSON.stringify(result, null, 2)); + }); +} + +/** + * Map jscoverage data to a JSON structure + * suitable for reporting. + * + * @api private + * @param {Object} cov + * @return {Object} + */ + +function map(cov) { + var ret = { + instrumentation: 'node-jscoverage', + sloc: 0, + hits: 0, + misses: 0, + coverage: 0, + files: [] + }; + + for (var filename in cov) { + if (Object.prototype.hasOwnProperty.call(cov, filename)) { + var data = coverage(filename, cov[filename]); + ret.files.push(data); + ret.hits += data.hits; + ret.misses += data.misses; + ret.sloc += data.sloc; + } + } + + ret.files.sort(function(a, b) { + return a.filename.localeCompare(b.filename); + }); + + if (ret.sloc > 0) { + ret.coverage = (ret.hits / ret.sloc) * 100; + } + + return ret; +} + +/** + * Map jscoverage data for a single source file + * to a JSON structure suitable for reporting. + * + * @api private + * @param {string} filename name of the source file + * @param {Object} data jscoverage coverage data + * @return {Object} + */ +function coverage(filename, data) { + var ret = { + filename: filename, + coverage: 0, + hits: 0, + misses: 0, + sloc: 0, + source: {} + }; + + data.source.forEach(function(line, num) { + num++; + + if (data[num] === 0) { + ret.misses++; + ret.sloc++; + } else if (data[num] !== undefined) { + ret.hits++; + ret.sloc++; + } + + ret.source[num] = { + source: line, + coverage: data[num] === undefined ? '' : data[num] + }; + }); + + ret.coverage = ret.hits / ret.sloc * 100; + + return ret; +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @api private + * @param {Object} test + * @return {Object} + */ +function clean(test) { + return { + duration: test.duration, + fullTitle: test.fullTitle(), + title: test.title + }; +} + +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./base":17,"_process":50}],24:[function(require,module,exports){ +(function (process){ +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `List`. + */ + +exports = module.exports = List; + +/** + * Initialize a new `List` test reporter. + * + * @api public + * @param {Runner} runner + */ +function List(runner) { + Base.call(this, runner); + + var self = this; + var total = runner.total; + + runner.on('start', function() { + console.log(JSON.stringify(['start', { total: total }])); + }); + + runner.on('pass', function(test) { + console.log(JSON.stringify(['pass', clean(test)])); + }); + + runner.on('fail', function(test, err) { + test = clean(test); + test.err = err.message; + test.stack = err.stack || null; + console.log(JSON.stringify(['fail', test])); + }); + + runner.on('end', function() { + process.stdout.write(JSON.stringify(['end', self.stats])); + }); +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @api private + * @param {Object} test + * @return {Object} + */ +function clean(test) { + return { + title: test.title, + fullTitle: test.fullTitle(), + duration: test.duration + }; +} + +}).call(this,require('_process')) +},{"./base":17,"_process":50}],25:[function(require,module,exports){ +(function (process){ +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `JSON`. + */ + +exports = module.exports = JSONReporter; + +/** + * Initialize a new `JSON` reporter. + * + * @api public + * @param {Runner} runner + */ +function JSONReporter(runner) { + Base.call(this, runner); + + var self = this; + var tests = []; + var pending = []; + var failures = []; + var passes = []; + + runner.on('test end', function(test) { + tests.push(test); + }); + + runner.on('pass', function(test) { + passes.push(test); + }); + + runner.on('fail', function(test) { + failures.push(test); + }); + + runner.on('pending', function(test) { + pending.push(test); + }); + + runner.on('end', function() { + var obj = { + stats: self.stats, + tests: tests.map(clean), + pending: pending.map(clean), + failures: failures.map(clean), + passes: passes.map(clean) + }; + + runner.testResults = obj; + + process.stdout.write(JSON.stringify(obj, null, 2)); + }); +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @api private + * @param {Object} test + * @return {Object} + */ +function clean(test) { + return { + title: test.title, + fullTitle: test.fullTitle(), + duration: test.duration, + err: errorJSON(test.err || {}) + }; +} + +/** + * Transform `error` into a JSON object. + * + * @api private + * @param {Error} err + * @return {Object} + */ +function errorJSON(err) { + var res = {}; + Object.getOwnPropertyNames(err).forEach(function(key) { + res[key] = err[key]; + }, err); + return res; +} + +}).call(this,require('_process')) +},{"./base":17,"_process":50}],26:[function(require,module,exports){ +(function (process){ +/** + * Module dependencies. + */ + +var Base = require('./base'); +var create = require('lodash.create'); +var cursor = Base.cursor; +var color = Base.color; + +/** + * Expose `Landing`. + */ + +exports = module.exports = Landing; + +/** + * Airplane color. + */ + +Base.colors.plane = 0; + +/** + * Airplane crash color. + */ + +Base.colors['plane crash'] = 31; + +/** + * Runway color. + */ + +Base.colors.runway = 90; + +/** + * Initialize a new `Landing` reporter. + * + * @api public + * @param {Runner} runner + */ +function Landing(runner) { + Base.call(this, runner); + + var self = this; + var width = Base.window.width * .75 | 0; + var total = runner.total; + var stream = process.stdout; + var plane = color('plane', '✈'); + var crashed = -1; + var n = 0; + + function runway() { + var buf = Array(width).join('-'); + return ' ' + color('runway', buf); + } + + runner.on('start', function() { + stream.write('\n\n\n '); + cursor.hide(); + }); + + runner.on('test end', function(test) { + // check if the plane crashed + var col = crashed === -1 ? width * ++n / total | 0 : crashed; + + // show the crash + if (test.state === 'failed') { + plane = color('plane crash', '✈'); + crashed = col; + } + + // render landing strip + stream.write('\u001b[' + (width + 1) + 'D\u001b[2A'); + stream.write(runway()); + stream.write('\n '); + stream.write(color('runway', Array(col).join('⋅'))); + stream.write(plane); + stream.write(color('runway', Array(width - col).join('⋅') + '\n')); + stream.write(runway()); + stream.write('\u001b[0m'); + }); + + runner.on('end', function() { + cursor.show(); + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +Landing.prototype = create(Base.prototype, { + constructor: Landing +}); + +}).call(this,require('_process')) +},{"./base":17,"_process":50,"lodash.create":70}],27:[function(require,module,exports){ +(function (process){ +/** + * Module dependencies. + */ + +var Base = require('./base'); +var create = require('lodash.create'); +var color = Base.color; +var cursor = Base.cursor; + +/** + * Expose `List`. + */ + +exports = module.exports = List; + +/** + * Initialize a new `List` test reporter. + * + * @api public + * @param {Runner} runner + */ +function List(runner) { + Base.call(this, runner); + + var self = this; + var n = 0; + + runner.on('start', function() { + console.log(); + }); + + runner.on('test', function(test) { + process.stdout.write(color('pass', ' ' + test.fullTitle() + ': ')); + }); + + runner.on('pending', function(test) { + var fmt = color('checkmark', ' -') + + color('pending', ' %s'); + console.log(fmt, test.fullTitle()); + }); + + runner.on('pass', function(test) { + var fmt = color('checkmark', ' ' + Base.symbols.dot) + + color('pass', ' %s: ') + + color(test.speed, '%dms'); + cursor.CR(); + console.log(fmt, test.fullTitle(), test.duration); + }); + + runner.on('fail', function(test) { + cursor.CR(); + console.log(color('fail', ' %d) %s'), ++n, test.fullTitle()); + }); + + runner.on('end', self.epilogue.bind(self)); +} + +/** + * Inherit from `Base.prototype`. + */ + +List.prototype = create(Base.prototype, { + constructor: List +}); + +}).call(this,require('_process')) +},{"./base":17,"_process":50,"lodash.create":70}],28:[function(require,module,exports){ +(function (process){ +/** + * Module dependencies. + */ + +var Base = require('./base'); +var utils = require('../utils'); + +/** + * Constants + */ + +var SUITE_PREFIX = '$'; + +/** + * Expose `Markdown`. + */ + +exports = module.exports = Markdown; + +/** + * Initialize a new `Markdown` reporter. + * + * @api public + * @param {Runner} runner + */ +function Markdown(runner) { + Base.call(this, runner); + + var level = 0; + var buf = ''; + + function title(str) { + return Array(level).join('#') + ' ' + str; + } + + function mapTOC(suite, obj) { + var ret = obj; + var key = SUITE_PREFIX + suite.title; + + obj = obj[key] = obj[key] || { suite: suite }; + suite.suites.forEach(function() { + mapTOC(suite, obj); + }); + + return ret; + } + + function stringifyTOC(obj, level) { + ++level; + var buf = ''; + var link; + for (var key in obj) { + if (key === 'suite') { + continue; + } + if (key !== SUITE_PREFIX) { + link = ' - [' + key.substring(1) + ']'; + link += '(#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; + buf += Array(level).join(' ') + link; + } + buf += stringifyTOC(obj[key], level); + } + return buf; + } + + function generateTOC(suite) { + var obj = mapTOC(suite, {}); + return stringifyTOC(obj, 0); + } + + generateTOC(runner.suite); + + runner.on('suite', function(suite) { + ++level; + var slug = utils.slug(suite.fullTitle()); + buf += '' + '\n'; + buf += title(suite.title) + '\n'; + }); + + runner.on('suite end', function() { + --level; + }); + + runner.on('pass', function(test) { + var code = utils.clean(test.fn.toString()); + buf += test.title + '.\n'; + buf += '\n```js\n'; + buf += code + '\n'; + buf += '```\n\n'; + }); + + runner.on('end', function() { + process.stdout.write('# TOC\n'); + process.stdout.write(generateTOC(runner.suite)); + process.stdout.write(buf); + }); +} + +}).call(this,require('_process')) +},{"../utils":39,"./base":17,"_process":50}],29:[function(require,module,exports){ +(function (process){ +/** + * Module dependencies. + */ + +var Base = require('./base'); +var create = require('lodash.create'); + +/** + * Expose `Min`. + */ + +exports = module.exports = Min; + +/** + * Initialize a new `Min` minimal test reporter (best used with --watch). + * + * @api public + * @param {Runner} runner + */ +function Min(runner) { + Base.call(this, runner); + + runner.on('start', function() { + // clear screen + process.stdout.write('\u001b[2J'); + // set cursor position + process.stdout.write('\u001b[1;3H'); + }); + + runner.on('end', this.epilogue.bind(this)); +} + +/** + * Inherit from `Base.prototype`. + */ + +Min.prototype = create(Base.prototype, { + constructor: Min +}); + +}).call(this,require('_process')) +},{"./base":17,"_process":50,"lodash.create":70}],30:[function(require,module,exports){ +(function (process){ +/** + * Module dependencies. + */ + +var Base = require('./base'); +var create = require('lodash.create'); + +/** + * Expose `Dot`. + */ + +exports = module.exports = NyanCat; + +/** + * Initialize a new `Dot` matrix test reporter. + * + * @param {Runner} runner + * @api public + */ + +function NyanCat(runner) { + Base.call(this, runner); + + var self = this; + var width = Base.window.width * .75 | 0; + var nyanCatWidth = this.nyanCatWidth = 11; + + this.colorIndex = 0; + this.numberOfLines = 4; + this.rainbowColors = self.generateColors(); + this.scoreboardWidth = 5; + this.tick = 0; + this.trajectories = [[], [], [], []]; + this.trajectoryWidthMax = (width - nyanCatWidth); + + runner.on('start', function() { + Base.cursor.hide(); + self.draw(); + }); + + runner.on('pending', function() { + self.draw(); + }); + + runner.on('pass', function() { + self.draw(); + }); + + runner.on('fail', function() { + self.draw(); + }); + + runner.on('end', function() { + Base.cursor.show(); + for (var i = 0; i < self.numberOfLines; i++) { + write('\n'); + } + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +NyanCat.prototype = create(Base.prototype, { + constructor: NyanCat +}); + +/** + * Draw the nyan cat + * + * @api private + */ + +NyanCat.prototype.draw = function() { + this.appendRainbow(); + this.drawScoreboard(); + this.drawRainbow(); + this.drawNyanCat(); + this.tick = !this.tick; +}; + +/** + * Draw the "scoreboard" showing the number + * of passes, failures and pending tests. + * + * @api private + */ + +NyanCat.prototype.drawScoreboard = function() { + var stats = this.stats; + + function draw(type, n) { + write(' '); + write(Base.color(type, n)); + write('\n'); + } + + draw('green', stats.passes); + draw('fail', stats.failures); + draw('pending', stats.pending); + write('\n'); + + this.cursorUp(this.numberOfLines); +}; + +/** + * Append the rainbow. + * + * @api private + */ + +NyanCat.prototype.appendRainbow = function() { + var segment = this.tick ? '_' : '-'; + var rainbowified = this.rainbowify(segment); + + for (var index = 0; index < this.numberOfLines; index++) { + var trajectory = this.trajectories[index]; + if (trajectory.length >= this.trajectoryWidthMax) { + trajectory.shift(); + } + trajectory.push(rainbowified); + } +}; + +/** + * Draw the rainbow. + * + * @api private + */ + +NyanCat.prototype.drawRainbow = function() { + var self = this; + + this.trajectories.forEach(function(line) { + write('\u001b[' + self.scoreboardWidth + 'C'); + write(line.join('')); + write('\n'); + }); + + this.cursorUp(this.numberOfLines); +}; + +/** + * Draw the nyan cat + * + * @api private + */ +NyanCat.prototype.drawNyanCat = function() { + var self = this; + var startWidth = this.scoreboardWidth + this.trajectories[0].length; + var dist = '\u001b[' + startWidth + 'C'; + var padding = ''; + + write(dist); + write('_,------,'); + write('\n'); + + write(dist); + padding = self.tick ? ' ' : ' '; + write('_|' + padding + '/\\_/\\ '); + write('\n'); + + write(dist); + padding = self.tick ? '_' : '__'; + var tail = self.tick ? '~' : '^'; + write(tail + '|' + padding + this.face() + ' '); + write('\n'); + + write(dist); + padding = self.tick ? ' ' : ' '; + write(padding + '"" "" '); + write('\n'); + + this.cursorUp(this.numberOfLines); +}; + +/** + * Draw nyan cat face. + * + * @api private + * @return {string} + */ + +NyanCat.prototype.face = function() { + var stats = this.stats; + if (stats.failures) { + return '( x .x)'; + } else if (stats.pending) { + return '( o .o)'; + } else if (stats.passes) { + return '( ^ .^)'; + } + return '( - .-)'; +}; + +/** + * Move cursor up `n`. + * + * @api private + * @param {number} n + */ + +NyanCat.prototype.cursorUp = function(n) { + write('\u001b[' + n + 'A'); +}; + +/** + * Move cursor down `n`. + * + * @api private + * @param {number} n + */ + +NyanCat.prototype.cursorDown = function(n) { + write('\u001b[' + n + 'B'); +}; + +/** + * Generate rainbow colors. + * + * @api private + * @return {Array} + */ +NyanCat.prototype.generateColors = function() { + var colors = []; + + for (var i = 0; i < (6 * 7); i++) { + var pi3 = Math.floor(Math.PI / 3); + var n = (i * (1.0 / 6)); + var r = Math.floor(3 * Math.sin(n) + 3); + var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3); + var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3); + colors.push(36 * r + 6 * g + b + 16); + } + + return colors; +}; + +/** + * Apply rainbow to the given `str`. + * + * @api private + * @param {string} str + * @return {string} + */ +NyanCat.prototype.rainbowify = function(str) { + if (!Base.useColors) { + return str; + } + var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length]; + this.colorIndex += 1; + return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m'; +}; + +/** + * Stdout helper. + * + * @param {string} string A message to write to stdout. + */ +function write(string) { + process.stdout.write(string); +} + +}).call(this,require('_process')) +},{"./base":17,"_process":50,"lodash.create":70}],31:[function(require,module,exports){ +(function (process){ +/** + * Module dependencies. + */ + +var Base = require('./base'); +var create = require('lodash.create'); +var color = Base.color; +var cursor = Base.cursor; + +/** + * Expose `Progress`. + */ + +exports = module.exports = Progress; + +/** + * General progress bar color. + */ + +Base.colors.progress = 90; + +/** + * Initialize a new `Progress` bar test reporter. + * + * @api public + * @param {Runner} runner + * @param {Object} options + */ +function Progress(runner, options) { + Base.call(this, runner); + + var self = this; + var width = Base.window.width * .50 | 0; + var total = runner.total; + var complete = 0; + var lastN = -1; + + // default chars + options = options || {}; + options.open = options.open || '['; + options.complete = options.complete || '▬'; + options.incomplete = options.incomplete || Base.symbols.dot; + options.close = options.close || ']'; + options.verbose = false; + + // tests started + runner.on('start', function() { + console.log(); + cursor.hide(); + }); + + // tests complete + runner.on('test end', function() { + complete++; + + var percent = complete / total; + var n = width * percent | 0; + var i = width - n; + + if (n === lastN && !options.verbose) { + // Don't re-render the line if it hasn't changed + return; + } + lastN = n; + + cursor.CR(); + process.stdout.write('\u001b[J'); + process.stdout.write(color('progress', ' ' + options.open)); + process.stdout.write(Array(n).join(options.complete)); + process.stdout.write(Array(i).join(options.incomplete)); + process.stdout.write(color('progress', options.close)); + if (options.verbose) { + process.stdout.write(color('progress', ' ' + complete + ' of ' + total)); + } + }); + + // tests are complete, output some stats + // and the failures if any + runner.on('end', function() { + cursor.show(); + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +Progress.prototype = create(Base.prototype, { + constructor: Progress +}); + +}).call(this,require('_process')) +},{"./base":17,"_process":50,"lodash.create":70}],32:[function(require,module,exports){ +/** + * Module dependencies. + */ + +var Base = require('./base'); +var create = require('lodash.create'); +var color = Base.color; +var cursor = Base.cursor; + +/** + * Expose `Spec`. + */ + +exports = module.exports = Spec; + +/** + * Initialize a new `Spec` test reporter. + * + * @api public + * @param {Runner} runner + */ +function Spec(runner) { + Base.call(this, runner); + + var self = this; + var indents = 0; + var n = 0; + + function indent() { + return Array(indents).join(' '); + } + + runner.on('start', function() { + console.log(); + }); + + runner.on('suite', function(suite) { + ++indents; + console.log(color('suite', '%s%s'), indent(), suite.title); + }); + + runner.on('suite end', function() { + --indents; + if (indents === 1) { + console.log(); + } + }); + + runner.on('pending', function(test) { + var fmt = indent() + color('pending', ' - %s'); + console.log(fmt, test.title); + }); + + runner.on('pass', function(test) { + var fmt; + if (test.speed === 'fast') { + fmt = indent() + + color('checkmark', ' ' + Base.symbols.ok) + + color('pass', ' %s'); + cursor.CR(); + console.log(fmt, test.title); + } else { + fmt = indent() + + color('checkmark', ' ' + Base.symbols.ok) + + color('pass', ' %s') + + color(test.speed, ' (%dms)'); + cursor.CR(); + console.log(fmt, test.title, test.duration); + } + }); + + runner.on('fail', function(test) { + cursor.CR(); + console.log(indent() + color('fail', ' %d) %s'), ++n, test.title); + }); + + runner.on('end', self.epilogue.bind(self)); +} + +/** + * Inherit from `Base.prototype`. + */ + +Spec.prototype = create(Base.prototype, { + constructor: Spec +}); + +},{"./base":17,"lodash.create":70}],33:[function(require,module,exports){ +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `TAP`. + */ + +exports = module.exports = TAP; + +/** + * Initialize a new `TAP` reporter. + * + * @api public + * @param {Runner} runner + */ +function TAP(runner) { + Base.call(this, runner); + + var n = 1; + var passes = 0; + var failures = 0; + + runner.on('start', function() { + var total = runner.grepTotal(runner.suite); + console.log('%d..%d', 1, total); + }); + + runner.on('test end', function() { + ++n; + }); + + runner.on('pending', function(test) { + console.log('ok %d %s # SKIP -', n, title(test)); + }); + + runner.on('pass', function(test) { + passes++; + console.log('ok %d %s', n, title(test)); + }); + + runner.on('fail', function(test, err) { + failures++; + console.log('not ok %d %s', n, title(test)); + if (err.stack) { + console.log(err.stack.replace(/^/gm, ' ')); + } + }); + + runner.on('end', function() { + console.log('# tests ' + (passes + failures)); + console.log('# pass ' + passes); + console.log('# fail ' + failures); + }); +} + +/** + * Return a TAP-safe title of `test` + * + * @api private + * @param {Object} test + * @return {String} + */ +function title(test) { + return test.fullTitle().replace(/#/g, ''); +} + +},{"./base":17}],34:[function(require,module,exports){ +(function (global){ +/** + * Module dependencies. + */ + +var Base = require('./base'); +var create = require('lodash.create'); +var fs = require('fs'); +var escape = require('../utils').escape; + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +/* eslint-disable no-unused-vars, no-native-reassign */ +var Date = global.Date; +var setTimeout = global.setTimeout; +var setInterval = global.setInterval; +var clearTimeout = global.clearTimeout; +var clearInterval = global.clearInterval; +/* eslint-enable no-unused-vars, no-native-reassign */ + +/** + * Expose `XUnit`. + */ + +exports = module.exports = XUnit; + +/** + * Initialize a new `XUnit` reporter. + * + * @api public + * @param {Runner} runner + */ +function XUnit(runner, options) { + Base.call(this, runner); + + var stats = this.stats; + var tests = []; + var self = this; + + if (options.reporterOptions && options.reporterOptions.output) { + if (!fs.createWriteStream) { + throw new Error('file output not supported in browser'); + } + self.fileStream = fs.createWriteStream(options.reporterOptions.output); + } + + runner.on('pending', function(test) { + tests.push(test); + }); + + runner.on('pass', function(test) { + tests.push(test); + }); + + runner.on('fail', function(test) { + tests.push(test); + }); + + runner.on('end', function() { + self.write(tag('testsuite', { + name: 'Mocha Tests', + tests: stats.tests, + failures: stats.failures, + errors: stats.failures, + skipped: stats.tests - stats.failures - stats.passes, + timestamp: (new Date()).toUTCString(), + time: (stats.duration / 1000) || 0 + }, false)); + + tests.forEach(function(t) { + self.test(t); + }); + + self.write(''); + }); +} + +/** + * Override done to close the stream (if it's a file). + * + * @param failures + * @param {Function} fn + */ +XUnit.prototype.done = function(failures, fn) { + if (this.fileStream) { + this.fileStream.end(function() { + fn(failures); + }); + } else { + fn(failures); + } +}; + +/** + * Inherit from `Base.prototype`. + */ + +XUnit.prototype = create(Base.prototype, { + constructor: XUnit +}); + +/** + * Write out the given line. + * + * @param {string} line + */ +XUnit.prototype.write = function(line) { + if (this.fileStream) { + this.fileStream.write(line + '\n'); + } else { + console.log(line); + } +}; + +/** + * Output tag for the given `test.` + * + * @param {Test} test + */ +XUnit.prototype.test = function(test) { + var attrs = { + classname: test.parent.fullTitle(), + name: test.title, + time: (test.duration / 1000) || 0 + }; + + if (test.state === 'failed') { + var err = test.err; + this.write(tag('testcase', attrs, false, tag('failure', {}, false, cdata(escape(err.message) + '\n' + err.stack)))); + } else if (test.pending) { + this.write(tag('testcase', attrs, false, tag('skipped', {}, true))); + } else { + this.write(tag('testcase', attrs, true)); + } +}; + +/** + * HTML tag helper. + * + * @param name + * @param attrs + * @param close + * @param content + * @return {string} + */ +function tag(name, attrs, close, content) { + var end = close ? '/>' : '>'; + var pairs = []; + var tag; + + for (var key in attrs) { + if (Object.prototype.hasOwnProperty.call(attrs, key)) { + pairs.push(key + '="' + escape(attrs[key]) + '"'); + } + } + + tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end; + if (content) { + tag += content + ''; +} + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"../utils":39,"./base":17,"fs":41,"lodash.create":70}],35:[function(require,module,exports){ +(function (global){ +/** + * Module dependencies. + */ + +var EventEmitter = require('events').EventEmitter; +var Pending = require('./pending'); +var create = require('lodash.create'); +var debug = require('debug')('mocha:runnable'); +var milliseconds = require('./ms'); +var utils = require('./utils'); + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +/* eslint-disable no-unused-vars, no-native-reassign */ +var Date = global.Date; +var setTimeout = global.setTimeout; +var setInterval = global.setInterval; +var clearTimeout = global.clearTimeout; +var clearInterval = global.clearInterval; +/* eslint-enable no-unused-vars, no-native-reassign */ + +/** + * Object#toString(). + */ + +var toString = Object.prototype.toString; + +/** + * Expose `Runnable`. + */ + +module.exports = Runnable; + +/** + * Initialize a new `Runnable` with the given `title` and callback `fn`. + * + * @api private + * @param {string} title + * @param {Function} fn + */ +function Runnable(title, fn) { + this.title = title; + this.fn = fn; + this.async = fn && fn.length; + this.sync = !this.async; + this._timeout = 2000; + this._slow = 75; + this._enableTimeouts = true; + this.timedOut = false; + this._trace = new Error('done() called multiple times'); +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +Runnable.prototype = create(EventEmitter.prototype, { + constructor: Runnable +}); + +/** + * Set & get timeout `ms`. + * + * @api private + * @param {number|string} ms + * @return {Runnable|number} ms or Runnable instance. + */ +Runnable.prototype.timeout = function(ms) { + if (!arguments.length) { + return this._timeout; + } + if (ms === 0) { + this._enableTimeouts = false; + } + if (typeof ms === 'string') { + ms = milliseconds(ms); + } + debug('timeout %d', ms); + this._timeout = ms; + if (this.timer) { + this.resetTimeout(); + } + return this; +}; + +/** + * Set & get slow `ms`. + * + * @api private + * @param {number|string} ms + * @return {Runnable|number} ms or Runnable instance. + */ +Runnable.prototype.slow = function(ms) { + if (!arguments.length) { + return this._slow; + } + if (typeof ms === 'string') { + ms = milliseconds(ms); + } + debug('timeout %d', ms); + this._slow = ms; + return this; +}; + +/** + * Set and get whether timeout is `enabled`. + * + * @api private + * @param {boolean} enabled + * @return {Runnable|boolean} enabled or Runnable instance. + */ +Runnable.prototype.enableTimeouts = function(enabled) { + if (!arguments.length) { + return this._enableTimeouts; + } + debug('enableTimeouts %s', enabled); + this._enableTimeouts = enabled; + return this; +}; + +/** + * Halt and mark as pending. + * + * @api private + */ +Runnable.prototype.skip = function() { + throw new Pending(); +}; + +/** + * Return the full title generated by recursively concatenating the parent's + * full title. + * + * @api public + * @return {string} + */ +Runnable.prototype.fullTitle = function() { + return this.parent.fullTitle() + ' ' + this.title; +}; + +/** + * Clear the timeout. + * + * @api private + */ +Runnable.prototype.clearTimeout = function() { + clearTimeout(this.timer); +}; + +/** + * Inspect the runnable void of private properties. + * + * @api private + * @return {string} + */ +Runnable.prototype.inspect = function() { + return JSON.stringify(this, function(key, val) { + if (key[0] === '_') { + return; + } + if (key === 'parent') { + return '#'; + } + if (key === 'ctx') { + return '#'; + } + return val; + }, 2); +}; + +/** + * Reset the timeout. + * + * @api private + */ +Runnable.prototype.resetTimeout = function() { + var self = this; + var ms = this.timeout() || 1e9; + + if (!this._enableTimeouts) { + return; + } + this.clearTimeout(); + this.timer = setTimeout(function() { + if (!self._enableTimeouts) { + return; + } + self.callback(new Error('timeout of ' + ms + 'ms exceeded. Ensure the done() callback is being called in this test.')); + self.timedOut = true; + }, ms); +}; + +/** + * Whitelist a list of globals for this test run. + * + * @api private + * @param {string[]} globals + */ +Runnable.prototype.globals = function(globals) { + this._allowedGlobals = globals; +}; + +/** + * Run the test and invoke `fn(err)`. + * + * @api private + * @param {Function} fn + */ +Runnable.prototype.run = function(fn) { + var self = this; + var start = new Date(); + var ctx = this.ctx; + var finished; + var emitted; + + // Sometimes the ctx exists, but it is not runnable + if (ctx && ctx.runnable) { + ctx.runnable(this); + } + + // called multiple times + function multiple(err) { + if (emitted) { + return; + } + emitted = true; + self.emit('error', err || new Error('done() called multiple times; stacktrace may be inaccurate')); + } + + // finished + function done(err) { + var ms = self.timeout(); + if (self.timedOut) { + return; + } + if (finished) { + return multiple(err || self._trace); + } + + self.clearTimeout(); + self.duration = new Date() - start; + finished = true; + if (!err && self.duration > ms && self._enableTimeouts) { + err = new Error('timeout of ' + ms + 'ms exceeded. Ensure the done() callback is being called in this test.'); + } + fn(err); + } + + // for .resetTimeout() + this.callback = done; + + // explicit async with `done` argument + if (this.async) { + this.resetTimeout(); + + try { + this.fn.call(ctx, function(err) { + if (err instanceof Error || toString.call(err) === '[object Error]') { + return done(err); + } + if (err != null) { + if (Object.prototype.toString.call(err) === '[object Object]') { + return done(new Error('done() invoked with non-Error: ' + JSON.stringify(err))); + } + return done(new Error('done() invoked with non-Error: ' + err)); + } + done(); + }); + } catch (err) { + done(utils.getError(err)); + } + return; + } + + if (this.asyncOnly) { + return done(new Error('--async-only option in use without declaring `done()`')); + } + + // sync or promise-returning + try { + if (this.pending) { + done(); + } else { + callFn(this.fn); + } + } catch (err) { + done(utils.getError(err)); + } + + function callFn(fn) { + var result = fn.call(ctx); + if (result && typeof result.then === 'function') { + self.resetTimeout(); + result + .then(function() { + done(); + }, + function(reason) { + done(reason || new Error('Promise rejected with no or falsy reason')); + }); + } else { + done(); + } + } +}; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./ms":15,"./pending":16,"./utils":39,"debug":2,"events":3,"lodash.create":70}],36:[function(require,module,exports){ +(function (process,global){ +/** + * Module dependencies. + */ + +var EventEmitter = require('events').EventEmitter; +var Pending = require('./pending'); +var create = require('lodash.create'); +var debug = require('debug')('mocha:runner'); +var filter = require('./utils').filter; +var indexOf = require('./utils').indexOf; +var keys = require('./utils').keys; +var stackFilter = require('./utils').stackTraceFilter(); +var stringify = require('./utils').stringify; +var type = require('./utils').type; +var undefinedError = require('./utils').undefinedError; + +/** + * Non-enumerable globals. + */ + +var globals = [ + 'setTimeout', + 'clearTimeout', + 'setInterval', + 'clearInterval', + 'XMLHttpRequest', + 'Date', + 'setImmediate', + 'clearImmediate' +]; + +/** + * Expose `Runner`. + */ + +module.exports = Runner; + +/** + * Initialize a `Runner` for the given `suite`. + * + * Events: + * + * - `start` execution started + * - `end` execution complete + * - `suite` (suite) test suite execution started + * - `suite end` (suite) all tests (and sub-suites) have finished + * - `test` (test) test execution started + * - `test end` (test) test completed + * - `hook` (hook) hook execution started + * - `hook end` (hook) hook complete + * - `pass` (test) test passed + * - `fail` (test, err) test failed + * - `pending` (test) test pending + * + * @api public + * @param {Suite} suite Root suite + * @param {boolean} [delay] Whether or not to delay execution of root suite + * until ready. + */ +function Runner(suite, delay) { + var self = this; + this._globals = []; + this._abort = false; + this._delay = delay; + this.suite = suite; + this.total = suite.total(); + this.failures = 0; + this.on('test end', function(test) { + self.checkGlobals(test); + }); + this.on('hook end', function(hook) { + self.checkGlobals(hook); + }); + this.grep(/.*/); + this.globals(this.globalProps().concat(extraGlobals())); +} + +/** + * Wrapper for setImmediate, process.nextTick, or browser polyfill. + * + * @api private + * @param {Function} fn + */ +Runner.immediately = global.setImmediate || process.nextTick; + +/** + * Inherit from `EventEmitter.prototype`. + */ + +Runner.prototype = create(EventEmitter.prototype, { + constructor: Runner +}); + +/** + * Run tests with full titles matching `re`. Updates runner.total + * with number of tests matched. + * + * @api public + * @param {RegExp} re + * @param {boolean} invert + * @return {Runner} Runner instance. + */ +Runner.prototype.grep = function(re, invert) { + debug('grep %s', re); + this._grep = re; + this._invert = invert; + this.total = this.grepTotal(this.suite); + return this; +}; + +/** + * Returns the number of tests matching the grep search for the + * given suite. + * + * @api public + * @param {Suite} suite + * @return {number} + */ +Runner.prototype.grepTotal = function(suite) { + var self = this; + var total = 0; + + suite.eachTest(function(test) { + var match = self._grep.test(test.fullTitle()); + if (self._invert) { + match = !match; + } + if (match) { + total++; + } + }); + + return total; +}; + +/** + * Return a list of global properties. + * + * @api private + * @return {Array} + */ +Runner.prototype.globalProps = function() { + var props = keys(global); + + // non-enumerables + for (var i = 0; i < globals.length; ++i) { + if (~indexOf(props, globals[i])) { + continue; + } + props.push(globals[i]); + } + + return props; +}; + +/** + * Allow the given `arr` of globals. + * + * @api public + * @param {Array} arr + * @return {Runner} Runner instance. + */ +Runner.prototype.globals = function(arr) { + if (!arguments.length) { + return this._globals; + } + debug('globals %j', arr); + this._globals = this._globals.concat(arr); + return this; +}; + +/** + * Check for global variable leaks. + * + * @api private + */ +Runner.prototype.checkGlobals = function(test) { + if (this.ignoreLeaks) { + return; + } + var ok = this._globals; + + var globals = this.globalProps(); + var leaks; + + if (test) { + ok = ok.concat(test._allowedGlobals || []); + } + + if (this.prevGlobalsLength === globals.length) { + return; + } + this.prevGlobalsLength = globals.length; + + leaks = filterLeaks(ok, globals); + this._globals = this._globals.concat(leaks); + + if (leaks.length > 1) { + this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + '')); + } else if (leaks.length) { + this.fail(test, new Error('global leak detected: ' + leaks[0])); + } +}; + +/** + * Fail the given `test`. + * + * @api private + * @param {Test} test + * @param {Error} err + */ +Runner.prototype.fail = function(test, err) { + ++this.failures; + test.state = 'failed'; + + if (!(err instanceof Error || err && typeof err.message === 'string')) { + err = new Error('the ' + type(err) + ' ' + stringify(err) + ' was thrown, throw an Error :)'); + } + + err.stack = (this.fullStackTrace || !err.stack) + ? err.stack + : stackFilter(err.stack); + + this.emit('fail', test, err); +}; + +/** + * Fail the given `hook` with `err`. + * + * Hook failures work in the following pattern: + * - If bail, then exit + * - Failed `before` hook skips all tests in a suite and subsuites, + * but jumps to corresponding `after` hook + * - Failed `before each` hook skips remaining tests in a + * suite and jumps to corresponding `after each` hook, + * which is run only once + * - Failed `after` hook does not alter + * execution order + * - Failed `after each` hook skips remaining tests in a + * suite and subsuites, but executes other `after each` + * hooks + * + * @api private + * @param {Hook} hook + * @param {Error} err + */ +Runner.prototype.failHook = function(hook, err) { + this.fail(hook, err); + if (this.suite.bail()) { + this.emit('end'); + } +}; + +/** + * Run hook `name` callbacks and then invoke `fn()`. + * + * @api private + * @param {string} name + * @param {Function} fn + */ +Runner.prototype.hook = function(name, fn) { + var suite = this.suite; + var hooks = suite['_' + name]; + var self = this; + + function next(i) { + var hook = hooks[i]; + if (!hook) { + return fn(); + } + self.currentRunnable = hook; + + hook.ctx.currentTest = self.test; + + self.emit('hook', hook); + + hook.on('error', function(err) { + self.failHook(hook, err); + }); + + hook.run(function(err) { + hook.removeAllListeners('error'); + var testError = hook.error(); + if (testError) { + self.fail(self.test, testError); + } + if (err) { + if (err instanceof Pending) { + suite.pending = true; + } else { + self.failHook(hook, err); + + // stop executing hooks, notify callee of hook err + return fn(err); + } + } + self.emit('hook end', hook); + delete hook.ctx.currentTest; + next(++i); + }); + } + + Runner.immediately(function() { + next(0); + }); +}; + +/** + * Run hook `name` for the given array of `suites` + * in order, and callback `fn(err, errSuite)`. + * + * @api private + * @param {string} name + * @param {Array} suites + * @param {Function} fn + */ +Runner.prototype.hooks = function(name, suites, fn) { + var self = this; + var orig = this.suite; + + function next(suite) { + self.suite = suite; + + if (!suite) { + self.suite = orig; + return fn(); + } + + self.hook(name, function(err) { + if (err) { + var errSuite = self.suite; + self.suite = orig; + return fn(err, errSuite); + } + + next(suites.pop()); + }); + } + + next(suites.pop()); +}; + +/** + * Run hooks from the top level down. + * + * @api private + * @param {string} name + * @param {Function} fn + */ +Runner.prototype.hookUp = function(name, fn) { + var suites = [this.suite].concat(this.parents()).reverse(); + this.hooks(name, suites, fn); +}; + +/** + * Run hooks from the bottom up. + * + * @api private + * @param {string} name + * @param {Function} fn + */ +Runner.prototype.hookDown = function(name, fn) { + var suites = [this.suite].concat(this.parents()); + this.hooks(name, suites, fn); +}; + +/** + * Return an array of parent Suites from + * closest to furthest. + * + * @api private + * @return {Array} + */ +Runner.prototype.parents = function() { + var suite = this.suite; + var suites = []; + while (suite = suite.parent) { + suites.push(suite); + } + return suites; +}; + +/** + * Run the current test and callback `fn(err)`. + * + * @api private + * @param {Function} fn + */ +Runner.prototype.runTest = function(fn) { + var self = this; + var test = this.test; + + if (this.asyncOnly) { + test.asyncOnly = true; + } + + try { + test.on('error', function(err) { + self.fail(test, err); + }); + test.run(fn); + } catch (err) { + fn(err); + } +}; + +/** + * Run tests in the given `suite` and invoke the callback `fn()` when complete. + * + * @api private + * @param {Suite} suite + * @param {Function} fn + */ +Runner.prototype.runTests = function(suite, fn) { + var self = this; + var tests = suite.tests.slice(); + var test; + + function hookErr(_, errSuite, after) { + // before/after Each hook for errSuite failed: + var orig = self.suite; + + // for failed 'after each' hook start from errSuite parent, + // otherwise start from errSuite itself + self.suite = after ? errSuite.parent : errSuite; + + if (self.suite) { + // call hookUp afterEach + self.hookUp('afterEach', function(err2, errSuite2) { + self.suite = orig; + // some hooks may fail even now + if (err2) { + return hookErr(err2, errSuite2, true); + } + // report error suite + fn(errSuite); + }); + } else { + // there is no need calling other 'after each' hooks + self.suite = orig; + fn(errSuite); + } + } + + function next(err, errSuite) { + // if we bail after first err + if (self.failures && suite._bail) { + return fn(); + } + + if (self._abort) { + return fn(); + } + + if (err) { + return hookErr(err, errSuite, true); + } + + // next test + test = tests.shift(); + + // all done + if (!test) { + return fn(); + } + + // grep + var match = self._grep.test(test.fullTitle()); + if (self._invert) { + match = !match; + } + if (!match) { + return next(); + } + + // pending + if (test.pending) { + self.emit('pending', test); + self.emit('test end', test); + return next(); + } + + // execute test and hook(s) + self.emit('test', self.test = test); + self.hookDown('beforeEach', function(err, errSuite) { + if (suite.pending) { + self.emit('pending', test); + self.emit('test end', test); + return next(); + } + if (err) { + return hookErr(err, errSuite, false); + } + self.currentRunnable = self.test; + self.runTest(function(err) { + test = self.test; + + if (err) { + if (err instanceof Pending) { + self.emit('pending', test); + } else { + self.fail(test, err); + } + self.emit('test end', test); + + if (err instanceof Pending) { + return next(); + } + + return self.hookUp('afterEach', next); + } + + test.state = 'passed'; + self.emit('pass', test); + self.emit('test end', test); + self.hookUp('afterEach', next); + }); + }); + } + + this.next = next; + next(); +}; + +/** + * Run the given `suite` and invoke the callback `fn()` when complete. + * + * @api private + * @param {Suite} suite + * @param {Function} fn + */ +Runner.prototype.runSuite = function(suite, fn) { + var i = 0; + var self = this; + var total = this.grepTotal(suite); + + debug('run suite %s', suite.fullTitle()); + + if (!total) { + return fn(); + } + + this.emit('suite', this.suite = suite); + + function next(errSuite) { + if (errSuite) { + // current suite failed on a hook from errSuite + if (errSuite === suite) { + // if errSuite is current suite + // continue to the next sibling suite + return done(); + } + // errSuite is among the parents of current suite + // stop execution of errSuite and all sub-suites + return done(errSuite); + } + + if (self._abort) { + return done(); + } + + var curr = suite.suites[i++]; + if (!curr) { + return done(); + } + self.runSuite(curr, next); + } + + function done(errSuite) { + self.suite = suite; + self.hook('afterAll', function() { + self.emit('suite end', suite); + fn(errSuite); + }); + } + + this.hook('beforeAll', function(err) { + if (err) { + return done(); + } + self.runTests(suite, next); + }); +}; + +/** + * Handle uncaught exceptions. + * + * @api private + * @param {Error} err + */ +Runner.prototype.uncaught = function(err) { + if (err) { + debug('uncaught exception %s', err !== function() { + return this; + }.call(err) ? err : (err.message || err)); + } else { + debug('uncaught undefined exception'); + err = undefinedError(); + } + err.uncaught = true; + + var runnable = this.currentRunnable; + if (!runnable) { + return; + } + + runnable.clearTimeout(); + + // Ignore errors if complete + if (runnable.state) { + return; + } + this.fail(runnable, err); + + // recover from test + if (runnable.type === 'test') { + this.emit('test end', runnable); + this.hookUp('afterEach', this.next); + return; + } + + // bail on hooks + this.emit('end'); +}; + +/** + * Run the root suite and invoke `fn(failures)` + * on completion. + * + * @api public + * @param {Function} fn + * @return {Runner} Runner instance. + */ +Runner.prototype.run = function(fn) { + var self = this; + var rootSuite = this.suite; + + fn = fn || function() {}; + + function uncaught(err) { + self.uncaught(err); + } + + function start() { + self.emit('start'); + self.runSuite(rootSuite, function() { + debug('finished running'); + self.emit('end'); + }); + } + + debug('start'); + + // callback + this.on('end', function() { + debug('end'); + process.removeListener('uncaughtException', uncaught); + fn(self.failures); + }); + + // uncaught exception + process.on('uncaughtException', uncaught); + + if (this._delay) { + // for reporters, I guess. + // might be nice to debounce some dots while we wait. + this.emit('waiting', rootSuite); + rootSuite.once('run', start); + } else { + start(); + } + + return this; +}; + +/** + * Cleanly abort execution. + * + * @api public + * @return {Runner} Runner instance. + */ +Runner.prototype.abort = function() { + debug('aborting'); + this._abort = true; + + return this; +}; + +/** + * Filter leaks with the given globals flagged as `ok`. + * + * @api private + * @param {Array} ok + * @param {Array} globals + * @return {Array} + */ +function filterLeaks(ok, globals) { + return filter(globals, function(key) { + // Firefox and Chrome exposes iframes as index inside the window object + if (/^d+/.test(key)) { + return false; + } + + // in firefox + // if runner runs in an iframe, this iframe's window.getInterface method not init at first + // it is assigned in some seconds + if (global.navigator && (/^getInterface/).test(key)) { + return false; + } + + // an iframe could be approached by window[iframeIndex] + // in ie6,7,8 and opera, iframeIndex is enumerable, this could cause leak + if (global.navigator && (/^\d+/).test(key)) { + return false; + } + + // Opera and IE expose global variables for HTML element IDs (issue #243) + if (/^mocha-/.test(key)) { + return false; + } + + var matched = filter(ok, function(ok) { + if (~ok.indexOf('*')) { + return key.indexOf(ok.split('*')[0]) === 0; + } + return key === ok; + }); + return !matched.length && (!global.navigator || key !== 'onerror'); + }); +} + +/** + * Array of globals dependent on the environment. + * + * @api private + * @return {Array} + */ +function extraGlobals() { + if (!process.browser) { + var nodeVersion = process.version.split('.').reduce(function(a, v) { + return a << 8 | v; + }); + + // 'errno' was renamed to process._errno in v0.9.11. + + if (nodeVersion < 0x00090B) { + return ['errno']; + } + } + + return []; +} + +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./pending":16,"./utils":39,"_process":50,"debug":2,"events":3,"lodash.create":70}],37:[function(require,module,exports){ +/** + * Module dependencies. + */ + +var EventEmitter = require('events').EventEmitter; +var Hook = require('./hook'); +var create = require('lodash.create'); +var debug = require('debug')('mocha:suite'); +var milliseconds = require('./ms'); +var utils = require('./utils'); + +/** + * Expose `Suite`. + */ + +exports = module.exports = Suite; + +/** + * Create a new `Suite` with the given `title` and parent `Suite`. When a suite + * with the same title is already present, that suite is returned to provide + * nicer reporter and more flexible meta-testing. + * + * @api public + * @param {Suite} parent + * @param {string} title + * @return {Suite} + */ +exports.create = function(parent, title) { + var suite = new Suite(title, parent.ctx); + suite.parent = parent; + if (parent.pending) { + suite.pending = true; + } + title = suite.fullTitle(); + parent.addSuite(suite); + return suite; +}; + +/** + * Initialize a new `Suite` with the given `title` and `ctx`. + * + * @api private + * @param {string} title + * @param {Context} parentContext + */ +function Suite(title, parentContext) { + this.title = title; + function Context() {} + Context.prototype = parentContext; + this.ctx = new Context(); + this.suites = []; + this.tests = []; + this.pending = false; + this._beforeEach = []; + this._beforeAll = []; + this._afterEach = []; + this._afterAll = []; + this.root = !title; + this._timeout = 2000; + this._enableTimeouts = true; + this._slow = 75; + this._bail = false; + this.delayed = false; +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +Suite.prototype = create(EventEmitter.prototype, { + constructor: Suite +}); + +/** + * Return a clone of this `Suite`. + * + * @api private + * @return {Suite} + */ +Suite.prototype.clone = function() { + var suite = new Suite(this.title); + debug('clone'); + suite.ctx = this.ctx; + suite.timeout(this.timeout()); + suite.enableTimeouts(this.enableTimeouts()); + suite.slow(this.slow()); + suite.bail(this.bail()); + return suite; +}; + +/** + * Set timeout `ms` or short-hand such as "2s". + * + * @api private + * @param {number|string} ms + * @return {Suite|number} for chaining + */ +Suite.prototype.timeout = function(ms) { + if (!arguments.length) { + return this._timeout; + } + if (ms.toString() === '0') { + this._enableTimeouts = false; + } + if (typeof ms === 'string') { + ms = milliseconds(ms); + } + debug('timeout %d', ms); + this._timeout = parseInt(ms, 10); + return this; +}; + +/** + * Set timeout to `enabled`. + * + * @api private + * @param {boolean} enabled + * @return {Suite|boolean} self or enabled + */ +Suite.prototype.enableTimeouts = function(enabled) { + if (!arguments.length) { + return this._enableTimeouts; + } + debug('enableTimeouts %s', enabled); + this._enableTimeouts = enabled; + return this; +}; + +/** + * Set slow `ms` or short-hand such as "2s". + * + * @api private + * @param {number|string} ms + * @return {Suite|number} for chaining + */ +Suite.prototype.slow = function(ms) { + if (!arguments.length) { + return this._slow; + } + if (typeof ms === 'string') { + ms = milliseconds(ms); + } + debug('slow %d', ms); + this._slow = ms; + return this; +}; + +/** + * Sets whether to bail after first error. + * + * @api private + * @param {boolean} bail + * @return {Suite|number} for chaining + */ +Suite.prototype.bail = function(bail) { + if (!arguments.length) { + return this._bail; + } + debug('bail %s', bail); + this._bail = bail; + return this; +}; + +/** + * Run `fn(test[, done])` before running tests. + * + * @api private + * @param {string} title + * @param {Function} fn + * @return {Suite} for chaining + */ +Suite.prototype.beforeAll = function(title, fn) { + if (this.pending) { + return this; + } + if (typeof title === 'function') { + fn = title; + title = fn.name; + } + title = '"before all" hook' + (title ? ': ' + title : ''); + + var hook = new Hook(title, fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.enableTimeouts(this.enableTimeouts()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + this._beforeAll.push(hook); + this.emit('beforeAll', hook); + return this; +}; + +/** + * Run `fn(test[, done])` after running tests. + * + * @api private + * @param {string} title + * @param {Function} fn + * @return {Suite} for chaining + */ +Suite.prototype.afterAll = function(title, fn) { + if (this.pending) { + return this; + } + if (typeof title === 'function') { + fn = title; + title = fn.name; + } + title = '"after all" hook' + (title ? ': ' + title : ''); + + var hook = new Hook(title, fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.enableTimeouts(this.enableTimeouts()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + this._afterAll.push(hook); + this.emit('afterAll', hook); + return this; +}; + +/** + * Run `fn(test[, done])` before each test case. + * + * @api private + * @param {string} title + * @param {Function} fn + * @return {Suite} for chaining + */ +Suite.prototype.beforeEach = function(title, fn) { + if (this.pending) { + return this; + } + if (typeof title === 'function') { + fn = title; + title = fn.name; + } + title = '"before each" hook' + (title ? ': ' + title : ''); + + var hook = new Hook(title, fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.enableTimeouts(this.enableTimeouts()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + this._beforeEach.push(hook); + this.emit('beforeEach', hook); + return this; +}; + +/** + * Run `fn(test[, done])` after each test case. + * + * @api private + * @param {string} title + * @param {Function} fn + * @return {Suite} for chaining + */ +Suite.prototype.afterEach = function(title, fn) { + if (this.pending) { + return this; + } + if (typeof title === 'function') { + fn = title; + title = fn.name; + } + title = '"after each" hook' + (title ? ': ' + title : ''); + + var hook = new Hook(title, fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.enableTimeouts(this.enableTimeouts()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + this._afterEach.push(hook); + this.emit('afterEach', hook); + return this; +}; + +/** + * Add a test `suite`. + * + * @api private + * @param {Suite} suite + * @return {Suite} for chaining + */ +Suite.prototype.addSuite = function(suite) { + suite.parent = this; + suite.timeout(this.timeout()); + suite.enableTimeouts(this.enableTimeouts()); + suite.slow(this.slow()); + suite.bail(this.bail()); + this.suites.push(suite); + this.emit('suite', suite); + return this; +}; + +/** + * Add a `test` to this suite. + * + * @api private + * @param {Test} test + * @return {Suite} for chaining + */ +Suite.prototype.addTest = function(test) { + test.parent = this; + test.timeout(this.timeout()); + test.enableTimeouts(this.enableTimeouts()); + test.slow(this.slow()); + test.ctx = this.ctx; + this.tests.push(test); + this.emit('test', test); + return this; +}; + +/** + * Return the full title generated by recursively concatenating the parent's + * full title. + * + * @api public + * @return {string} + */ +Suite.prototype.fullTitle = function() { + if (this.parent) { + var full = this.parent.fullTitle(); + if (full) { + return full + ' ' + this.title; + } + } + return this.title; +}; + +/** + * Return the total number of tests. + * + * @api public + * @return {number} + */ +Suite.prototype.total = function() { + return utils.reduce(this.suites, function(sum, suite) { + return sum + suite.total(); + }, 0) + this.tests.length; +}; + +/** + * Iterates through each suite recursively to find all tests. Applies a + * function in the format `fn(test)`. + * + * @api private + * @param {Function} fn + * @return {Suite} + */ +Suite.prototype.eachTest = function(fn) { + utils.forEach(this.tests, fn); + utils.forEach(this.suites, function(suite) { + suite.eachTest(fn); + }); + return this; +}; + +/** + * This will run the root suite if we happen to be running in delayed mode. + */ +Suite.prototype.run = function run() { + if (this.root) { + this.emit('run'); + } +}; + +},{"./hook":7,"./ms":15,"./utils":39,"debug":2,"events":3,"lodash.create":70}],38:[function(require,module,exports){ +/** + * Module dependencies. + */ + +var Runnable = require('./runnable'); +var create = require('lodash.create'); + +/** + * Expose `Test`. + */ + +module.exports = Test; + +/** + * Initialize a new `Test` with the given `title` and callback `fn`. + * + * @api private + * @param {String} title + * @param {Function} fn + */ +function Test(title, fn) { + Runnable.call(this, title, fn); + this.pending = !fn; + this.type = 'test'; +} + +/** + * Inherit from `Runnable.prototype`. + */ + +Test.prototype = create(Runnable.prototype, { + constructor: Test +}); + +},{"./runnable":35,"lodash.create":70}],39:[function(require,module,exports){ +(function (process,Buffer){ +/* eslint-env browser */ + +/** + * Module dependencies. + */ + +var basename = require('path').basename; +var debug = require('debug')('mocha:watch'); +var exists = require('fs').existsSync || require('path').existsSync; +var glob = require('glob'); +var join = require('path').join; +var readdirSync = require('fs').readdirSync; +var statSync = require('fs').statSync; +var watchFile = require('fs').watchFile; + +/** + * Ignored directories. + */ + +var ignore = ['node_modules', '.git']; + +/** + * Escape special characters in the given string of html. + * + * @api private + * @param {string} html + * @return {string} + */ +exports.escape = function(html) { + return String(html) + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(//g, '>'); +}; + +/** + * Array#forEach (<=IE8) + * + * @api private + * @param {Array} arr + * @param {Function} fn + * @param {Object} scope + */ +exports.forEach = function(arr, fn, scope) { + for (var i = 0, l = arr.length; i < l; i++) { + fn.call(scope, arr[i], i); + } +}; + +/** + * Test if the given obj is type of string. + * + * @api private + * @param {Object} obj + * @return {boolean} + */ +exports.isString = function(obj) { + return typeof obj === 'string'; +}; + +/** + * Array#map (<=IE8) + * + * @api private + * @param {Array} arr + * @param {Function} fn + * @param {Object} scope + * @return {Array} + */ +exports.map = function(arr, fn, scope) { + var result = []; + for (var i = 0, l = arr.length; i < l; i++) { + result.push(fn.call(scope, arr[i], i, arr)); + } + return result; +}; + +/** + * Array#indexOf (<=IE8) + * + * @api private + * @param {Array} arr + * @param {Object} obj to find index of + * @param {number} start + * @return {number} + */ +exports.indexOf = function(arr, obj, start) { + for (var i = start || 0, l = arr.length; i < l; i++) { + if (arr[i] === obj) { + return i; + } + } + return -1; +}; + +/** + * Array#reduce (<=IE8) + * + * @api private + * @param {Array} arr + * @param {Function} fn + * @param {Object} val Initial value. + * @return {*} + */ +exports.reduce = function(arr, fn, val) { + var rval = val; + + for (var i = 0, l = arr.length; i < l; i++) { + rval = fn(rval, arr[i], i, arr); + } + + return rval; +}; + +/** + * Array#filter (<=IE8) + * + * @api private + * @param {Array} arr + * @param {Function} fn + * @return {Array} + */ +exports.filter = function(arr, fn) { + var ret = []; + + for (var i = 0, l = arr.length; i < l; i++) { + var val = arr[i]; + if (fn(val, i, arr)) { + ret.push(val); + } + } + + return ret; +}; + +/** + * Object.keys (<=IE8) + * + * @api private + * @param {Object} obj + * @return {Array} keys + */ +exports.keys = typeof Object.keys === 'function' ? Object.keys : function(obj) { + var keys = []; + var has = Object.prototype.hasOwnProperty; // for `window` on <=IE8 + + for (var key in obj) { + if (has.call(obj, key)) { + keys.push(key); + } + } + + return keys; +}; + +/** + * Watch the given `files` for changes + * and invoke `fn(file)` on modification. + * + * @api private + * @param {Array} files + * @param {Function} fn + */ +exports.watch = function(files, fn) { + var options = { interval: 100 }; + files.forEach(function(file) { + debug('file %s', file); + watchFile(file, options, function(curr, prev) { + if (prev.mtime < curr.mtime) { + fn(file); + } + }); + }); +}; + +/** + * Array.isArray (<=IE8) + * + * @api private + * @param {Object} obj + * @return {Boolean} + */ +var isArray = typeof Array.isArray === 'function' ? Array.isArray : function(obj) { + return Object.prototype.toString.call(obj) === '[object Array]'; +}; + +/** + * Buffer.prototype.toJSON polyfill. + * + * @type {Function} + */ +if (typeof Buffer !== 'undefined' && Buffer.prototype) { + Buffer.prototype.toJSON = Buffer.prototype.toJSON || function() { + return Array.prototype.slice.call(this, 0); + }; +} + +/** + * Ignored files. + * + * @api private + * @param {string} path + * @return {boolean} + */ +function ignored(path) { + return !~ignore.indexOf(path); +} + +/** + * Lookup files in the given `dir`. + * + * @api private + * @param {string} dir + * @param {string[]} [ext=['.js']] + * @param {Array} [ret=[]] + * @return {Array} + */ +exports.files = function(dir, ext, ret) { + ret = ret || []; + ext = ext || ['js']; + + var re = new RegExp('\\.(' + ext.join('|') + ')$'); + + readdirSync(dir) + .filter(ignored) + .forEach(function(path) { + path = join(dir, path); + if (statSync(path).isDirectory()) { + exports.files(path, ext, ret); + } else if (path.match(re)) { + ret.push(path); + } + }); + + return ret; +}; + +/** + * Compute a slug from the given `str`. + * + * @api private + * @param {string} str + * @return {string} + */ +exports.slug = function(str) { + return str + .toLowerCase() + .replace(/ +/g, '-') + .replace(/[^-\w]/g, ''); +}; + +/** + * Strip the function definition from `str`, and re-indent for pre whitespace. + * + * @param {string} str + * @return {string} + */ +exports.clean = function(str) { + str = str + .replace(/\r\n?|[\n\u2028\u2029]/g, '\n').replace(/^\uFEFF/, '') + .replace(/^function *\(.*\)\s*{|\(.*\) *=> *{?/, '') + .replace(/\s+\}$/, ''); + + var spaces = str.match(/^\n?( *)/)[1].length; + var tabs = str.match(/^\n?(\t*)/)[1].length; + var re = new RegExp('^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs ? tabs : spaces) + '}', 'gm'); + + str = str.replace(re, ''); + + return exports.trim(str); +}; + +/** + * Trim the given `str`. + * + * @api private + * @param {string} str + * @return {string} + */ +exports.trim = function(str) { + return str.replace(/^\s+|\s+$/g, ''); +}; + +/** + * Parse the given `qs`. + * + * @api private + * @param {string} qs + * @return {Object} + */ +exports.parseQuery = function(qs) { + return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair) { + var i = pair.indexOf('='); + var key = pair.slice(0, i); + var val = pair.slice(++i); + + obj[key] = decodeURIComponent(val); + return obj; + }, {}); +}; + +/** + * Highlight the given string of `js`. + * + * @api private + * @param {string} js + * @return {string} + */ +function highlight(js) { + return js + .replace(//g, '>') + .replace(/\/\/(.*)/gm, '//$1') + .replace(/('.*?')/gm, '$1') + .replace(/(\d+\.\d+)/gm, '$1') + .replace(/(\d+)/gm, '$1') + .replace(/\bnew[ \t]+(\w+)/gm, 'new $1') + .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '$1'); +} + +/** + * Highlight the contents of tag `name`. + * + * @api private + * @param {string} name + */ +exports.highlightTags = function(name) { + var code = document.getElementById('mocha').getElementsByTagName(name); + for (var i = 0, len = code.length; i < len; ++i) { + code[i].innerHTML = highlight(code[i].innerHTML); + } +}; + +/** + * If a value could have properties, and has none, this function is called, + * which returns a string representation of the empty value. + * + * Functions w/ no properties return `'[Function]'` + * Arrays w/ length === 0 return `'[]'` + * Objects w/ no properties return `'{}'` + * All else: return result of `value.toString()` + * + * @api private + * @param {*} value The value to inspect. + * @param {string} [type] The type of the value, if known. + * @returns {string} + */ +function emptyRepresentation(value, type) { + type = type || exports.type(value); + + switch (type) { + case 'function': + return '[Function]'; + case 'object': + return '{}'; + case 'array': + return '[]'; + default: + return value.toString(); + } +} + +/** + * Takes some variable and asks `Object.prototype.toString()` what it thinks it + * is. + * + * @api private + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString + * @param {*} value The value to test. + * @returns {string} + * @example + * type({}) // 'object' + * type([]) // 'array' + * type(1) // 'number' + * type(false) // 'boolean' + * type(Infinity) // 'number' + * type(null) // 'null' + * type(new Date()) // 'date' + * type(/foo/) // 'regexp' + * type('type') // 'string' + * type(global) // 'global' + */ +exports.type = function type(value) { + if (value === undefined) { + return 'undefined'; + } else if (value === null) { + return 'null'; + } else if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) { + return 'buffer'; + } + return Object.prototype.toString.call(value) + .replace(/^\[.+\s(.+?)\]$/, '$1') + .toLowerCase(); +}; + +/** + * Stringify `value`. Different behavior depending on type of value: + * + * - If `value` is undefined or null, return `'[undefined]'` or `'[null]'`, respectively. + * - If `value` is not an object, function or array, return result of `value.toString()` wrapped in double-quotes. + * - If `value` is an *empty* object, function, or array, return result of function + * {@link emptyRepresentation}. + * - If `value` has properties, call {@link exports.canonicalize} on it, then return result of + * JSON.stringify(). + * + * @api private + * @see exports.type + * @param {*} value + * @return {string} + */ +exports.stringify = function(value) { + var type = exports.type(value); + + if (!~exports.indexOf(['object', 'array', 'function'], type)) { + if (type !== 'buffer') { + return jsonStringify(value); + } + var json = value.toJSON(); + // Based on the toJSON result + return jsonStringify(json.data && json.type ? json.data : json, 2) + .replace(/,(\n|$)/g, '$1'); + } + + for (var prop in value) { + if (Object.prototype.hasOwnProperty.call(value, prop)) { + return jsonStringify(exports.canonicalize(value), 2).replace(/,(\n|$)/g, '$1'); + } + } + + return emptyRepresentation(value, type); +}; + +/** + * like JSON.stringify but more sense. + * + * @api private + * @param {Object} object + * @param {number=} spaces + * @param {number=} depth + * @returns {*} + */ +function jsonStringify(object, spaces, depth) { + if (typeof spaces === 'undefined') { + // primitive types + return _stringify(object); + } + + depth = depth || 1; + var space = spaces * depth; + var str = isArray(object) ? '[' : '{'; + var end = isArray(object) ? ']' : '}'; + var length = object.length || exports.keys(object).length; + // `.repeat()` polyfill + function repeat(s, n) { + return new Array(n).join(s); + } + + function _stringify(val) { + switch (exports.type(val)) { + case 'null': + case 'undefined': + val = '[' + val + ']'; + break; + case 'array': + case 'object': + val = jsonStringify(val, spaces, depth + 1); + break; + case 'boolean': + case 'regexp': + case 'number': + val = val === 0 && (1 / val) === -Infinity // `-0` + ? '-0' + : val.toString(); + break; + case 'date': + var sDate = isNaN(val.getTime()) // Invalid date + ? val.toString() + : val.toISOString(); + val = '[Date: ' + sDate + ']'; + break; + case 'buffer': + var json = val.toJSON(); + // Based on the toJSON result + json = json.data && json.type ? json.data : json; + val = '[Buffer: ' + jsonStringify(json, 2, depth + 1) + ']'; + break; + default: + val = (val === '[Function]' || val === '[Circular]') + ? val + : JSON.stringify(val); // string + } + return val; + } + + for (var i in object) { + if (!object.hasOwnProperty(i)) { + continue; // not my business + } + --length; + str += '\n ' + repeat(' ', space) + + (isArray(object) ? '' : '"' + i + '": ') // key + + _stringify(object[i]) // value + + (length ? ',' : ''); // comma + } + + return str + // [], {} + + (str.length !== 1 ? '\n' + repeat(' ', --space) + end : end); +} + +/** + * Test if a value is a buffer. + * + * @api private + * @param {*} value The value to test. + * @return {boolean} True if `value` is a buffer, otherwise false + */ +exports.isBuffer = function(value) { + return typeof Buffer !== 'undefined' && Buffer.isBuffer(value); +}; + +/** + * Return a new Thing that has the keys in sorted order. Recursive. + * + * If the Thing... + * - has already been seen, return string `'[Circular]'` + * - is `undefined`, return string `'[undefined]'` + * - is `null`, return value `null` + * - is some other primitive, return the value + * - is not a primitive or an `Array`, `Object`, or `Function`, return the value of the Thing's `toString()` method + * - is a non-empty `Array`, `Object`, or `Function`, return the result of calling this function again. + * - is an empty `Array`, `Object`, or `Function`, return the result of calling `emptyRepresentation()` + * + * @api private + * @see {@link exports.stringify} + * @param {*} value Thing to inspect. May or may not have properties. + * @param {Array} [stack=[]] Stack of seen values + * @return {(Object|Array|Function|string|undefined)} + */ +exports.canonicalize = function(value, stack) { + var canonicalizedObj; + /* eslint-disable no-unused-vars */ + var prop; + /* eslint-enable no-unused-vars */ + var type = exports.type(value); + function withStack(value, fn) { + stack.push(value); + fn(); + stack.pop(); + } + + stack = stack || []; + + if (exports.indexOf(stack, value) !== -1) { + return '[Circular]'; + } + + switch (type) { + case 'undefined': + case 'buffer': + case 'null': + canonicalizedObj = value; + break; + case 'array': + withStack(value, function() { + canonicalizedObj = exports.map(value, function(item) { + return exports.canonicalize(item, stack); + }); + }); + break; + case 'function': + /* eslint-disable guard-for-in */ + for (prop in value) { + canonicalizedObj = {}; + break; + } + /* eslint-enable guard-for-in */ + if (!canonicalizedObj) { + canonicalizedObj = emptyRepresentation(value, type); + break; + } + /* falls through */ + case 'object': + canonicalizedObj = canonicalizedObj || {}; + withStack(value, function() { + exports.forEach(exports.keys(value).sort(), function(key) { + canonicalizedObj[key] = exports.canonicalize(value[key], stack); + }); + }); + break; + case 'date': + case 'number': + case 'regexp': + case 'boolean': + canonicalizedObj = value; + break; + default: + canonicalizedObj = value.toString(); + } + + return canonicalizedObj; +}; + +/** + * Lookup file names at the given `path`. + * + * @api public + * @param {string} path Base path to start searching from. + * @param {string[]} extensions File extensions to look for. + * @param {boolean} recursive Whether or not to recurse into subdirectories. + * @return {string[]} An array of paths. + */ +exports.lookupFiles = function lookupFiles(path, extensions, recursive) { + var files = []; + var re = new RegExp('\\.(' + extensions.join('|') + ')$'); + + if (!exists(path)) { + if (exists(path + '.js')) { + path += '.js'; + } else { + files = glob.sync(path); + if (!files.length) { + throw new Error("cannot resolve path (or pattern) '" + path + "'"); + } + return files; + } + } + + try { + var stat = statSync(path); + if (stat.isFile()) { + return path; + } + } catch (err) { + // ignore error + return; + } + + readdirSync(path).forEach(function(file) { + file = join(path, file); + try { + var stat = statSync(file); + if (stat.isDirectory()) { + if (recursive) { + files = files.concat(lookupFiles(file, extensions, recursive)); + } + return; + } + } catch (err) { + // ignore error + return; + } + if (!stat.isFile() || !re.test(file) || basename(file)[0] === '.') { + return; + } + files.push(file); + }); + + return files; +}; + +/** + * Generate an undefined error with a message warning the user. + * + * @return {Error} + */ + +exports.undefinedError = function() { + return new Error('Caught undefined error, did you throw without specifying what?'); +}; + +/** + * Generate an undefined error if `err` is not defined. + * + * @param {Error} err + * @return {Error} + */ + +exports.getError = function(err) { + return err || exports.undefinedError(); +}; + +/** + * @summary + * This Filter based on `mocha-clean` module.(see: `github.com/rstacruz/mocha-clean`) + * @description + * When invoking this function you get a filter function that get the Error.stack as an input, + * and return a prettify output. + * (i.e: strip Mocha, node_modules, bower and componentJS from stack trace). + * @returns {Function} + */ +exports.stackTraceFilter = function() { + // TODO: Replace with `process.browser` + var slash = '/'; + var is = typeof document === 'undefined' ? { node: true } : { browser: true }; + var cwd = is.node + ? process.cwd() + slash + : (typeof location === 'undefined' ? window.location : location).href.replace(/\/[^\/]*$/, '/'); + + function isMochaInternal(line) { + return (~line.indexOf('node_modules' + slash + 'mocha')) + || (~line.indexOf('components' + slash + 'mochajs')) + || (~line.indexOf('components' + slash + 'mocha')); + } + + // node_modules, bower, componentJS + function isBrowserModule(line) { + return (~line.indexOf('node_modules')) || (~line.indexOf('components')); + } + + function isNodeInternal(line) { + return (~line.indexOf('(timers.js:')) + || (~line.indexOf('(events.js:')) + || (~line.indexOf('(node.js:')) + || (~line.indexOf('(module.js:')) + || (~line.indexOf('GeneratorFunctionPrototype.next (native)')) + || false; + } + + return function(stack) { + stack = stack.split('\n'); + + stack = exports.reduce(stack, function(list, line) { + if (is.node && (isMochaInternal(line) || isNodeInternal(line))) { + return list; + } + + if (is.browser && (isBrowserModule(line))) { + return list; + } + + // Clean up cwd(absolute) + list.push(line.replace(cwd, '')); + return list; + }, []); + + return stack.join('\n'); + }; +}; + +}).call(this,require('_process'),require("buffer").Buffer) +},{"_process":50,"buffer":43,"debug":2,"fs":41,"glob":41,"path":41}],40:[function(require,module,exports){ +(function (process){ +var WritableStream = require('stream').Writable +var inherits = require('util').inherits + +module.exports = BrowserStdout + + +inherits(BrowserStdout, WritableStream) + +function BrowserStdout(opts) { + if (!(this instanceof BrowserStdout)) return new BrowserStdout(opts) + + opts = opts || {} + WritableStream.call(this, opts) + this.label = (opts.label !== undefined) ? opts.label : 'stdout' +} + +BrowserStdout.prototype._write = function(chunks, encoding, cb) { + var output = chunks.toString ? chunks.toString() : chunks + console.log(this.label+':', output) + process.nextTick(cb) +} +}).call(this,require('_process')) +},{"_process":50,"stream":62,"util":65}],41:[function(require,module,exports){ + +},{}],42:[function(require,module,exports){ +arguments[4][41][0].apply(exports,arguments) +},{"dup":41}],43:[function(require,module,exports){ +/*! + * The buffer module from node.js, for the browser. + * + * @author Feross Aboukhadijeh + * @license MIT + */ + +var base64 = require('base64-js') +var ieee754 = require('ieee754') +var isArray = require('is-array') + +exports.Buffer = Buffer +exports.SlowBuffer = SlowBuffer +exports.INSPECT_MAX_BYTES = 50 +Buffer.poolSize = 8192 // not used by this implementation + +var rootParent = {} + +/** + * If `Buffer.TYPED_ARRAY_SUPPORT`: + * === true Use Uint8Array implementation (fastest) + * === false Use Object implementation (most compatible, even IE6) + * + * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+, + * Opera 11.6+, iOS 4.2+. + * + * Note: + * + * - Implementation must support adding new properties to `Uint8Array` instances. + * Firefox 4-29 lacked support, fixed in Firefox 30+. + * See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438. + * + * - Chrome 9-10 is missing the `TypedArray.prototype.subarray` function. + * + * - IE10 has a broken `TypedArray.prototype.subarray` function which returns arrays of + * incorrect length in some situations. + * + * We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they will + * get the Object implementation, which is slower but will work correctly. + */ +Buffer.TYPED_ARRAY_SUPPORT = (function () { + try { + var buf = new ArrayBuffer(0) + var arr = new Uint8Array(buf) + arr.foo = function () { return 42 } + return arr.foo() === 42 && // typed array instances can be augmented + typeof arr.subarray === 'function' && // chrome 9-10 lack `subarray` + new Uint8Array(1).subarray(1, 1).byteLength === 0 // ie10 has broken `subarray` + } catch (e) { + return false + } +})() + +function kMaxLength () { + return Buffer.TYPED_ARRAY_SUPPORT + ? 0x7fffffff + : 0x3fffffff +} + +/** + * Class: Buffer + * ============= + * + * The Buffer constructor returns instances of `Uint8Array` that are augmented + * with function properties for all the node `Buffer` API functions. We use + * `Uint8Array` so that square bracket notation works as expected -- it returns + * a single octet. + * + * By augmenting the instances, we can avoid modifying the `Uint8Array` + * prototype. + */ +function Buffer (arg) { + if (!(this instanceof Buffer)) { + // Avoid going through an ArgumentsAdaptorTrampoline in the common case. + if (arguments.length > 1) return new Buffer(arg, arguments[1]) + return new Buffer(arg) + } + + this.length = 0 + this.parent = undefined + + // Common case. + if (typeof arg === 'number') { + return fromNumber(this, arg) + } + + // Slightly less common case. + if (typeof arg === 'string') { + return fromString(this, arg, arguments.length > 1 ? arguments[1] : 'utf8') + } + + // Unusual. + return fromObject(this, arg) +} + +function fromNumber (that, length) { + that = allocate(that, length < 0 ? 0 : checked(length) | 0) + if (!Buffer.TYPED_ARRAY_SUPPORT) { + for (var i = 0; i < length; i++) { + that[i] = 0 + } + } + return that +} + +function fromString (that, string, encoding) { + if (typeof encoding !== 'string' || encoding === '') encoding = 'utf8' + + // Assumption: byteLength() return value is always < kMaxLength. + var length = byteLength(string, encoding) | 0 + that = allocate(that, length) + + that.write(string, encoding) + return that +} + +function fromObject (that, object) { + if (Buffer.isBuffer(object)) return fromBuffer(that, object) + + if (isArray(object)) return fromArray(that, object) + + if (object == null) { + throw new TypeError('must start with number, buffer, array or string') + } + + if (typeof ArrayBuffer !== 'undefined' && object.buffer instanceof ArrayBuffer) { + return fromTypedArray(that, object) + } + + if (object.length) return fromArrayLike(that, object) + + return fromJsonObject(that, object) +} + +function fromBuffer (that, buffer) { + var length = checked(buffer.length) | 0 + that = allocate(that, length) + buffer.copy(that, 0, 0, length) + return that +} + +function fromArray (that, array) { + var length = checked(array.length) | 0 + that = allocate(that, length) + for (var i = 0; i < length; i += 1) { + that[i] = array[i] & 255 + } + return that +} + +// Duplicate of fromArray() to keep fromArray() monomorphic. +function fromTypedArray (that, array) { + var length = checked(array.length) | 0 + that = allocate(that, length) + // Truncating the elements is probably not what people expect from typed + // arrays with BYTES_PER_ELEMENT > 1 but it's compatible with the behavior + // of the old Buffer constructor. + for (var i = 0; i < length; i += 1) { + that[i] = array[i] & 255 + } + return that +} + +function fromArrayLike (that, array) { + var length = checked(array.length) | 0 + that = allocate(that, length) + for (var i = 0; i < length; i += 1) { + that[i] = array[i] & 255 + } + return that +} + +// Deserialize { type: 'Buffer', data: [1,2,3,...] } into a Buffer object. +// Returns a zero-length buffer for inputs that don't conform to the spec. +function fromJsonObject (that, object) { + var array + var length = 0 + + if (object.type === 'Buffer' && isArray(object.data)) { + array = object.data + length = checked(array.length) | 0 + } + that = allocate(that, length) + + for (var i = 0; i < length; i += 1) { + that[i] = array[i] & 255 + } + return that +} + +function allocate (that, length) { + if (Buffer.TYPED_ARRAY_SUPPORT) { + // Return an augmented `Uint8Array` instance, for best performance + that = Buffer._augment(new Uint8Array(length)) + } else { + // Fallback: Return an object instance of the Buffer class + that.length = length + that._isBuffer = true + } + + var fromPool = length !== 0 && length <= Buffer.poolSize >>> 1 + if (fromPool) that.parent = rootParent + + return that +} + +function checked (length) { + // Note: cannot use `length < kMaxLength` here because that fails when + // length is NaN (which is otherwise coerced to zero.) + if (length >= kMaxLength()) { + throw new RangeError('Attempt to allocate Buffer larger than maximum ' + + 'size: 0x' + kMaxLength().toString(16) + ' bytes') + } + return length | 0 +} + +function SlowBuffer (subject, encoding) { + if (!(this instanceof SlowBuffer)) return new SlowBuffer(subject, encoding) + + var buf = new Buffer(subject, encoding) + delete buf.parent + return buf +} + +Buffer.isBuffer = function isBuffer (b) { + return !!(b != null && b._isBuffer) +} + +Buffer.compare = function compare (a, b) { + if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) { + throw new TypeError('Arguments must be Buffers') + } + + if (a === b) return 0 + + var x = a.length + var y = b.length + + var i = 0 + var len = Math.min(x, y) + while (i < len) { + if (a[i] !== b[i]) break + + ++i + } + + if (i !== len) { + x = a[i] + y = b[i] + } + + if (x < y) return -1 + if (y < x) return 1 + return 0 +} + +Buffer.isEncoding = function isEncoding (encoding) { + switch (String(encoding).toLowerCase()) { + case 'hex': + case 'utf8': + case 'utf-8': + case 'ascii': + case 'binary': + case 'base64': + case 'raw': + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return true + default: + return false + } +} + +Buffer.concat = function concat (list, length) { + if (!isArray(list)) throw new TypeError('list argument must be an Array of Buffers.') + + if (list.length === 0) { + return new Buffer(0) + } else if (list.length === 1) { + return list[0] + } + + var i + if (length === undefined) { + length = 0 + for (i = 0; i < list.length; i++) { + length += list[i].length + } + } + + var buf = new Buffer(length) + var pos = 0 + for (i = 0; i < list.length; i++) { + var item = list[i] + item.copy(buf, pos) + pos += item.length + } + return buf +} + +function byteLength (string, encoding) { + if (typeof string !== 'string') string = '' + string + + var len = string.length + if (len === 0) return 0 + + // Use a for loop to avoid recursion + var loweredCase = false + for (;;) { + switch (encoding) { + case 'ascii': + case 'binary': + // Deprecated + case 'raw': + case 'raws': + return len + case 'utf8': + case 'utf-8': + return utf8ToBytes(string).length + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return len * 2 + case 'hex': + return len >>> 1 + case 'base64': + return base64ToBytes(string).length + default: + if (loweredCase) return utf8ToBytes(string).length // assume utf8 + encoding = ('' + encoding).toLowerCase() + loweredCase = true + } + } +} +Buffer.byteLength = byteLength + +// pre-set for values that may exist in the future +Buffer.prototype.length = undefined +Buffer.prototype.parent = undefined + +function slowToString (encoding, start, end) { + var loweredCase = false + + start = start | 0 + end = end === undefined || end === Infinity ? this.length : end | 0 + + if (!encoding) encoding = 'utf8' + if (start < 0) start = 0 + if (end > this.length) end = this.length + if (end <= start) return '' + + while (true) { + switch (encoding) { + case 'hex': + return hexSlice(this, start, end) + + case 'utf8': + case 'utf-8': + return utf8Slice(this, start, end) + + case 'ascii': + return asciiSlice(this, start, end) + + case 'binary': + return binarySlice(this, start, end) + + case 'base64': + return base64Slice(this, start, end) + + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return utf16leSlice(this, start, end) + + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = (encoding + '').toLowerCase() + loweredCase = true + } + } +} + +Buffer.prototype.toString = function toString () { + var length = this.length | 0 + if (length === 0) return '' + if (arguments.length === 0) return utf8Slice(this, 0, length) + return slowToString.apply(this, arguments) +} + +Buffer.prototype.equals = function equals (b) { + if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer') + if (this === b) return true + return Buffer.compare(this, b) === 0 +} + +Buffer.prototype.inspect = function inspect () { + var str = '' + var max = exports.INSPECT_MAX_BYTES + if (this.length > 0) { + str = this.toString('hex', 0, max).match(/.{2}/g).join(' ') + if (this.length > max) str += ' ... ' + } + return '' +} + +Buffer.prototype.compare = function compare (b) { + if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer') + if (this === b) return 0 + return Buffer.compare(this, b) +} + +Buffer.prototype.indexOf = function indexOf (val, byteOffset) { + if (byteOffset > 0x7fffffff) byteOffset = 0x7fffffff + else if (byteOffset < -0x80000000) byteOffset = -0x80000000 + byteOffset >>= 0 + + if (this.length === 0) return -1 + if (byteOffset >= this.length) return -1 + + // Negative offsets start from the end of the buffer + if (byteOffset < 0) byteOffset = Math.max(this.length + byteOffset, 0) + + if (typeof val === 'string') { + if (val.length === 0) return -1 // special case: looking for empty string always fails + return String.prototype.indexOf.call(this, val, byteOffset) + } + if (Buffer.isBuffer(val)) { + return arrayIndexOf(this, val, byteOffset) + } + if (typeof val === 'number') { + if (Buffer.TYPED_ARRAY_SUPPORT && Uint8Array.prototype.indexOf === 'function') { + return Uint8Array.prototype.indexOf.call(this, val, byteOffset) + } + return arrayIndexOf(this, [ val ], byteOffset) + } + + function arrayIndexOf (arr, val, byteOffset) { + var foundIndex = -1 + for (var i = 0; byteOffset + i < arr.length; i++) { + if (arr[byteOffset + i] === val[foundIndex === -1 ? 0 : i - foundIndex]) { + if (foundIndex === -1) foundIndex = i + if (i - foundIndex + 1 === val.length) return byteOffset + foundIndex + } else { + foundIndex = -1 + } + } + return -1 + } + + throw new TypeError('val must be string, number or Buffer') +} + +// `get` will be removed in Node 0.13+ +Buffer.prototype.get = function get (offset) { + console.log('.get() is deprecated. Access using array indexes instead.') + return this.readUInt8(offset) +} + +// `set` will be removed in Node 0.13+ +Buffer.prototype.set = function set (v, offset) { + console.log('.set() is deprecated. Access using array indexes instead.') + return this.writeUInt8(v, offset) +} + +function hexWrite (buf, string, offset, length) { + offset = Number(offset) || 0 + var remaining = buf.length - offset + if (!length) { + length = remaining + } else { + length = Number(length) + if (length > remaining) { + length = remaining + } + } + + // must be an even number of digits + var strLen = string.length + if (strLen % 2 !== 0) throw new Error('Invalid hex string') + + if (length > strLen / 2) { + length = strLen / 2 + } + for (var i = 0; i < length; i++) { + var parsed = parseInt(string.substr(i * 2, 2), 16) + if (isNaN(parsed)) throw new Error('Invalid hex string') + buf[offset + i] = parsed + } + return i +} + +function utf8Write (buf, string, offset, length) { + return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length) +} + +function asciiWrite (buf, string, offset, length) { + return blitBuffer(asciiToBytes(string), buf, offset, length) +} + +function binaryWrite (buf, string, offset, length) { + return asciiWrite(buf, string, offset, length) +} + +function base64Write (buf, string, offset, length) { + return blitBuffer(base64ToBytes(string), buf, offset, length) +} + +function ucs2Write (buf, string, offset, length) { + return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length) +} + +Buffer.prototype.write = function write (string, offset, length, encoding) { + // Buffer#write(string) + if (offset === undefined) { + encoding = 'utf8' + length = this.length + offset = 0 + // Buffer#write(string, encoding) + } else if (length === undefined && typeof offset === 'string') { + encoding = offset + length = this.length + offset = 0 + // Buffer#write(string, offset[, length][, encoding]) + } else if (isFinite(offset)) { + offset = offset | 0 + if (isFinite(length)) { + length = length | 0 + if (encoding === undefined) encoding = 'utf8' + } else { + encoding = length + length = undefined + } + // legacy write(string, encoding, offset, length) - remove in v0.13 + } else { + var swap = encoding + encoding = offset + offset = length | 0 + length = swap + } + + var remaining = this.length - offset + if (length === undefined || length > remaining) length = remaining + + if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) { + throw new RangeError('attempt to write outside buffer bounds') + } + + if (!encoding) encoding = 'utf8' + + var loweredCase = false + for (;;) { + switch (encoding) { + case 'hex': + return hexWrite(this, string, offset, length) + + case 'utf8': + case 'utf-8': + return utf8Write(this, string, offset, length) + + case 'ascii': + return asciiWrite(this, string, offset, length) + + case 'binary': + return binaryWrite(this, string, offset, length) + + case 'base64': + // Warning: maxLength not taken into account in base64Write + return base64Write(this, string, offset, length) + + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return ucs2Write(this, string, offset, length) + + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = ('' + encoding).toLowerCase() + loweredCase = true + } + } +} + +Buffer.prototype.toJSON = function toJSON () { + return { + type: 'Buffer', + data: Array.prototype.slice.call(this._arr || this, 0) + } +} + +function base64Slice (buf, start, end) { + if (start === 0 && end === buf.length) { + return base64.fromByteArray(buf) + } else { + return base64.fromByteArray(buf.slice(start, end)) + } +} + +function utf8Slice (buf, start, end) { + var res = '' + var tmp = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; i++) { + if (buf[i] <= 0x7F) { + res += decodeUtf8Char(tmp) + String.fromCharCode(buf[i]) + tmp = '' + } else { + tmp += '%' + buf[i].toString(16) + } + } + + return res + decodeUtf8Char(tmp) +} + +function asciiSlice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; i++) { + ret += String.fromCharCode(buf[i] & 0x7F) + } + return ret +} + +function binarySlice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; i++) { + ret += String.fromCharCode(buf[i]) + } + return ret +} + +function hexSlice (buf, start, end) { + var len = buf.length + + if (!start || start < 0) start = 0 + if (!end || end < 0 || end > len) end = len + + var out = '' + for (var i = start; i < end; i++) { + out += toHex(buf[i]) + } + return out +} + +function utf16leSlice (buf, start, end) { + var bytes = buf.slice(start, end) + var res = '' + for (var i = 0; i < bytes.length; i += 2) { + res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256) + } + return res +} + +Buffer.prototype.slice = function slice (start, end) { + var len = this.length + start = ~~start + end = end === undefined ? len : ~~end + + if (start < 0) { + start += len + if (start < 0) start = 0 + } else if (start > len) { + start = len + } + + if (end < 0) { + end += len + if (end < 0) end = 0 + } else if (end > len) { + end = len + } + + if (end < start) end = start + + var newBuf + if (Buffer.TYPED_ARRAY_SUPPORT) { + newBuf = Buffer._augment(this.subarray(start, end)) + } else { + var sliceLen = end - start + newBuf = new Buffer(sliceLen, undefined) + for (var i = 0; i < sliceLen; i++) { + newBuf[i] = this[i + start] + } + } + + if (newBuf.length) newBuf.parent = this.parent || this + + return newBuf +} + +/* + * Need to make sure that buffer isn't trying to write out of bounds. + */ +function checkOffset (offset, ext, length) { + if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint') + if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length') +} + +Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) { + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + + return val +} + +Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) { + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) { + checkOffset(offset, byteLength, this.length) + } + + var val = this[offset + --byteLength] + var mul = 1 + while (byteLength > 0 && (mul *= 0x100)) { + val += this[offset + --byteLength] * mul + } + + return val +} + +Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) { + if (!noAssert) checkOffset(offset, 1, this.length) + return this[offset] +} + +Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 2, this.length) + return this[offset] | (this[offset + 1] << 8) +} + +Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 2, this.length) + return (this[offset] << 8) | this[offset + 1] +} + +Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + + return ((this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16)) + + (this[offset + 3] * 0x1000000) +} + +Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset] * 0x1000000) + + ((this[offset + 1] << 16) | + (this[offset + 2] << 8) | + this[offset + 3]) +} + +Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) { + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + mul *= 0x80 + + if (val >= mul) val -= Math.pow(2, 8 * byteLength) + + return val +} + +Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) { + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var i = byteLength + var mul = 1 + var val = this[offset + --i] + while (i > 0 && (mul *= 0x100)) { + val += this[offset + --i] * mul + } + mul *= 0x80 + + if (val >= mul) val -= Math.pow(2, 8 * byteLength) + + return val +} + +Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) { + if (!noAssert) checkOffset(offset, 1, this.length) + if (!(this[offset] & 0x80)) return (this[offset]) + return ((0xff - this[offset] + 1) * -1) +} + +Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset] | (this[offset + 1] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val +} + +Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset + 1] | (this[offset] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val +} + +Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16) | + (this[offset + 3] << 24) +} + +Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset] << 24) | + (this[offset + 1] << 16) | + (this[offset + 2] << 8) | + (this[offset + 3]) +} + +Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, true, 23, 4) +} + +Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, false, 23, 4) +} + +Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, true, 52, 8) +} + +Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, false, 52, 8) +} + +function checkInt (buf, value, offset, ext, max, min) { + if (!Buffer.isBuffer(buf)) throw new TypeError('buffer must be a Buffer instance') + if (value > max || value < min) throw new RangeError('value is out of bounds') + if (offset + ext > buf.length) throw new RangeError('index out of range') +} + +Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) checkInt(this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0) + + var mul = 1 + var i = 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) checkInt(this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0) + + var i = byteLength - 1 + var mul = 1 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0) + if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value) + this[offset] = value + return offset + 1 +} + +function objectWriteUInt16 (buf, value, offset, littleEndian) { + if (value < 0) value = 0xffff + value + 1 + for (var i = 0, j = Math.min(buf.length - offset, 2); i < j; i++) { + buf[offset + i] = (value & (0xff << (8 * (littleEndian ? i : 1 - i)))) >>> + (littleEndian ? i : 1 - i) * 8 + } +} + +Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = value + this[offset + 1] = (value >>> 8) + } else { + objectWriteUInt16(this, value, offset, true) + } + return offset + 2 +} + +Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value >>> 8) + this[offset + 1] = value + } else { + objectWriteUInt16(this, value, offset, false) + } + return offset + 2 +} + +function objectWriteUInt32 (buf, value, offset, littleEndian) { + if (value < 0) value = 0xffffffff + value + 1 + for (var i = 0, j = Math.min(buf.length - offset, 4); i < j; i++) { + buf[offset + i] = (value >>> (littleEndian ? i : 3 - i) * 8) & 0xff + } +} + +Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset + 3] = (value >>> 24) + this[offset + 2] = (value >>> 16) + this[offset + 1] = (value >>> 8) + this[offset] = value + } else { + objectWriteUInt32(this, value, offset, true) + } + return offset + 4 +} + +Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = value + } else { + objectWriteUInt32(this, value, offset, false) + } + return offset + 4 +} + +Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) { + var limit = Math.pow(2, 8 * byteLength - 1) + + checkInt(this, value, offset, byteLength, limit - 1, -limit) + } + + var i = 0 + var mul = 1 + var sub = value < 0 ? 1 : 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) { + var limit = Math.pow(2, 8 * byteLength - 1) + + checkInt(this, value, offset, byteLength, limit - 1, -limit) + } + + var i = byteLength - 1 + var mul = 1 + var sub = value < 0 ? 1 : 0 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80) + if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value) + if (value < 0) value = 0xff + value + 1 + this[offset] = value + return offset + 1 +} + +Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = value + this[offset + 1] = (value >>> 8) + } else { + objectWriteUInt16(this, value, offset, true) + } + return offset + 2 +} + +Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value >>> 8) + this[offset + 1] = value + } else { + objectWriteUInt16(this, value, offset, false) + } + return offset + 2 +} + +Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = value + this[offset + 1] = (value >>> 8) + this[offset + 2] = (value >>> 16) + this[offset + 3] = (value >>> 24) + } else { + objectWriteUInt32(this, value, offset, true) + } + return offset + 4 +} + +Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + if (value < 0) value = 0xffffffff + value + 1 + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = value + } else { + objectWriteUInt32(this, value, offset, false) + } + return offset + 4 +} + +function checkIEEE754 (buf, value, offset, ext, max, min) { + if (value > max || value < min) throw new RangeError('value is out of bounds') + if (offset + ext > buf.length) throw new RangeError('index out of range') + if (offset < 0) throw new RangeError('index out of range') +} + +function writeFloat (buf, value, offset, littleEndian, noAssert) { + if (!noAssert) { + checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38) + } + ieee754.write(buf, value, offset, littleEndian, 23, 4) + return offset + 4 +} + +Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) { + return writeFloat(this, value, offset, true, noAssert) +} + +Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) { + return writeFloat(this, value, offset, false, noAssert) +} + +function writeDouble (buf, value, offset, littleEndian, noAssert) { + if (!noAssert) { + checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308) + } + ieee754.write(buf, value, offset, littleEndian, 52, 8) + return offset + 8 +} + +Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) { + return writeDouble(this, value, offset, true, noAssert) +} + +Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) { + return writeDouble(this, value, offset, false, noAssert) +} + +// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length) +Buffer.prototype.copy = function copy (target, targetStart, start, end) { + if (!start) start = 0 + if (!end && end !== 0) end = this.length + if (targetStart >= target.length) targetStart = target.length + if (!targetStart) targetStart = 0 + if (end > 0 && end < start) end = start + + // Copy 0 bytes; we're done + if (end === start) return 0 + if (target.length === 0 || this.length === 0) return 0 + + // Fatal error conditions + if (targetStart < 0) { + throw new RangeError('targetStart out of bounds') + } + if (start < 0 || start >= this.length) throw new RangeError('sourceStart out of bounds') + if (end < 0) throw new RangeError('sourceEnd out of bounds') + + // Are we oob? + if (end > this.length) end = this.length + if (target.length - targetStart < end - start) { + end = target.length - targetStart + start + } + + var len = end - start + + if (len < 1000 || !Buffer.TYPED_ARRAY_SUPPORT) { + for (var i = 0; i < len; i++) { + target[i + targetStart] = this[i + start] + } + } else { + target._set(this.subarray(start, start + len), targetStart) + } + + return len +} + +// fill(value, start=0, end=buffer.length) +Buffer.prototype.fill = function fill (value, start, end) { + if (!value) value = 0 + if (!start) start = 0 + if (!end) end = this.length + + if (end < start) throw new RangeError('end < start') + + // Fill 0 bytes; we're done + if (end === start) return + if (this.length === 0) return + + if (start < 0 || start >= this.length) throw new RangeError('start out of bounds') + if (end < 0 || end > this.length) throw new RangeError('end out of bounds') + + var i + if (typeof value === 'number') { + for (i = start; i < end; i++) { + this[i] = value + } + } else { + var bytes = utf8ToBytes(value.toString()) + var len = bytes.length + for (i = start; i < end; i++) { + this[i] = bytes[i % len] + } + } + + return this +} + +/** + * Creates a new `ArrayBuffer` with the *copied* memory of the buffer instance. + * Added in Node 0.12. Only available in browsers that support ArrayBuffer. + */ +Buffer.prototype.toArrayBuffer = function toArrayBuffer () { + if (typeof Uint8Array !== 'undefined') { + if (Buffer.TYPED_ARRAY_SUPPORT) { + return (new Buffer(this)).buffer + } else { + var buf = new Uint8Array(this.length) + for (var i = 0, len = buf.length; i < len; i += 1) { + buf[i] = this[i] + } + return buf.buffer + } + } else { + throw new TypeError('Buffer.toArrayBuffer not supported in this browser') + } +} + +// HELPER FUNCTIONS +// ================ + +var BP = Buffer.prototype + +/** + * Augment a Uint8Array *instance* (not the Uint8Array class!) with Buffer methods + */ +Buffer._augment = function _augment (arr) { + arr.constructor = Buffer + arr._isBuffer = true + + // save reference to original Uint8Array set method before overwriting + arr._set = arr.set + + // deprecated, will be removed in node 0.13+ + arr.get = BP.get + arr.set = BP.set + + arr.write = BP.write + arr.toString = BP.toString + arr.toLocaleString = BP.toString + arr.toJSON = BP.toJSON + arr.equals = BP.equals + arr.compare = BP.compare + arr.indexOf = BP.indexOf + arr.copy = BP.copy + arr.slice = BP.slice + arr.readUIntLE = BP.readUIntLE + arr.readUIntBE = BP.readUIntBE + arr.readUInt8 = BP.readUInt8 + arr.readUInt16LE = BP.readUInt16LE + arr.readUInt16BE = BP.readUInt16BE + arr.readUInt32LE = BP.readUInt32LE + arr.readUInt32BE = BP.readUInt32BE + arr.readIntLE = BP.readIntLE + arr.readIntBE = BP.readIntBE + arr.readInt8 = BP.readInt8 + arr.readInt16LE = BP.readInt16LE + arr.readInt16BE = BP.readInt16BE + arr.readInt32LE = BP.readInt32LE + arr.readInt32BE = BP.readInt32BE + arr.readFloatLE = BP.readFloatLE + arr.readFloatBE = BP.readFloatBE + arr.readDoubleLE = BP.readDoubleLE + arr.readDoubleBE = BP.readDoubleBE + arr.writeUInt8 = BP.writeUInt8 + arr.writeUIntLE = BP.writeUIntLE + arr.writeUIntBE = BP.writeUIntBE + arr.writeUInt16LE = BP.writeUInt16LE + arr.writeUInt16BE = BP.writeUInt16BE + arr.writeUInt32LE = BP.writeUInt32LE + arr.writeUInt32BE = BP.writeUInt32BE + arr.writeIntLE = BP.writeIntLE + arr.writeIntBE = BP.writeIntBE + arr.writeInt8 = BP.writeInt8 + arr.writeInt16LE = BP.writeInt16LE + arr.writeInt16BE = BP.writeInt16BE + arr.writeInt32LE = BP.writeInt32LE + arr.writeInt32BE = BP.writeInt32BE + arr.writeFloatLE = BP.writeFloatLE + arr.writeFloatBE = BP.writeFloatBE + arr.writeDoubleLE = BP.writeDoubleLE + arr.writeDoubleBE = BP.writeDoubleBE + arr.fill = BP.fill + arr.inspect = BP.inspect + arr.toArrayBuffer = BP.toArrayBuffer + + return arr +} + +var INVALID_BASE64_RE = /[^+\/0-9A-z\-]/g + +function base64clean (str) { + // Node strips out invalid characters like \n and \t from the string, base64-js does not + str = stringtrim(str).replace(INVALID_BASE64_RE, '') + // Node converts strings with length < 2 to '' + if (str.length < 2) return '' + // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not + while (str.length % 4 !== 0) { + str = str + '=' + } + return str +} + +function stringtrim (str) { + if (str.trim) return str.trim() + return str.replace(/^\s+|\s+$/g, '') +} + +function toHex (n) { + if (n < 16) return '0' + n.toString(16) + return n.toString(16) +} + +function utf8ToBytes (string, units) { + units = units || Infinity + var codePoint + var length = string.length + var leadSurrogate = null + var bytes = [] + var i = 0 + + for (; i < length; i++) { + codePoint = string.charCodeAt(i) + + // is surrogate component + if (codePoint > 0xD7FF && codePoint < 0xE000) { + // last char was a lead + if (leadSurrogate) { + // 2 leads in a row + if (codePoint < 0xDC00) { + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + leadSurrogate = codePoint + continue + } else { + // valid surrogate pair + codePoint = leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00 | 0x10000 + leadSurrogate = null + } + } else { + // no lead yet + + if (codePoint > 0xDBFF) { + // unexpected trail + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } else if (i + 1 === length) { + // unpaired lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } else { + // valid lead + leadSurrogate = codePoint + continue + } + } + } else if (leadSurrogate) { + // valid bmp char, but last char was a lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + leadSurrogate = null + } + + // encode utf8 + if (codePoint < 0x80) { + if ((units -= 1) < 0) break + bytes.push(codePoint) + } else if (codePoint < 0x800) { + if ((units -= 2) < 0) break + bytes.push( + codePoint >> 0x6 | 0xC0, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x10000) { + if ((units -= 3) < 0) break + bytes.push( + codePoint >> 0xC | 0xE0, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x200000) { + if ((units -= 4) < 0) break + bytes.push( + codePoint >> 0x12 | 0xF0, + codePoint >> 0xC & 0x3F | 0x80, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else { + throw new Error('Invalid code point') + } + } + + return bytes +} + +function asciiToBytes (str) { + var byteArray = [] + for (var i = 0; i < str.length; i++) { + // Node's code seems to be doing this and not & 0x7F.. + byteArray.push(str.charCodeAt(i) & 0xFF) + } + return byteArray +} + +function utf16leToBytes (str, units) { + var c, hi, lo + var byteArray = [] + for (var i = 0; i < str.length; i++) { + if ((units -= 2) < 0) break + + c = str.charCodeAt(i) + hi = c >> 8 + lo = c % 256 + byteArray.push(lo) + byteArray.push(hi) + } + + return byteArray +} + +function base64ToBytes (str) { + return base64.toByteArray(base64clean(str)) +} + +function blitBuffer (src, dst, offset, length) { + for (var i = 0; i < length; i++) { + if ((i + offset >= dst.length) || (i >= src.length)) break + dst[i + offset] = src[i] + } + return i +} + +function decodeUtf8Char (str) { + try { + return decodeURIComponent(str) + } catch (err) { + return String.fromCharCode(0xFFFD) // UTF 8 invalid char + } +} + +},{"base64-js":44,"ieee754":45,"is-array":46}],44:[function(require,module,exports){ +var lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + +;(function (exports) { + 'use strict'; + + var Arr = (typeof Uint8Array !== 'undefined') + ? Uint8Array + : Array + + var PLUS = '+'.charCodeAt(0) + var SLASH = '/'.charCodeAt(0) + var NUMBER = '0'.charCodeAt(0) + var LOWER = 'a'.charCodeAt(0) + var UPPER = 'A'.charCodeAt(0) + var PLUS_URL_SAFE = '-'.charCodeAt(0) + var SLASH_URL_SAFE = '_'.charCodeAt(0) + + function decode (elt) { + var code = elt.charCodeAt(0) + if (code === PLUS || + code === PLUS_URL_SAFE) + return 62 // '+' + if (code === SLASH || + code === SLASH_URL_SAFE) + return 63 // '/' + if (code < NUMBER) + return -1 //no match + if (code < NUMBER + 10) + return code - NUMBER + 26 + 26 + if (code < UPPER + 26) + return code - UPPER + if (code < LOWER + 26) + return code - LOWER + 26 + } + + function b64ToByteArray (b64) { + var i, j, l, tmp, placeHolders, arr + + if (b64.length % 4 > 0) { + throw new Error('Invalid string. Length must be a multiple of 4') + } + + // the number of equal signs (place holders) + // if there are two placeholders, than the two characters before it + // represent one byte + // if there is only one, then the three characters before it represent 2 bytes + // this is just a cheap hack to not do indexOf twice + var len = b64.length + placeHolders = '=' === b64.charAt(len - 2) ? 2 : '=' === b64.charAt(len - 1) ? 1 : 0 + + // base64 is 4/3 + up to two characters of the original data + arr = new Arr(b64.length * 3 / 4 - placeHolders) + + // if there are placeholders, only get up to the last complete 4 chars + l = placeHolders > 0 ? b64.length - 4 : b64.length + + var L = 0 + + function push (v) { + arr[L++] = v + } + + for (i = 0, j = 0; i < l; i += 4, j += 3) { + tmp = (decode(b64.charAt(i)) << 18) | (decode(b64.charAt(i + 1)) << 12) | (decode(b64.charAt(i + 2)) << 6) | decode(b64.charAt(i + 3)) + push((tmp & 0xFF0000) >> 16) + push((tmp & 0xFF00) >> 8) + push(tmp & 0xFF) + } + + if (placeHolders === 2) { + tmp = (decode(b64.charAt(i)) << 2) | (decode(b64.charAt(i + 1)) >> 4) + push(tmp & 0xFF) + } else if (placeHolders === 1) { + tmp = (decode(b64.charAt(i)) << 10) | (decode(b64.charAt(i + 1)) << 4) | (decode(b64.charAt(i + 2)) >> 2) + push((tmp >> 8) & 0xFF) + push(tmp & 0xFF) + } + + return arr + } + + function uint8ToBase64 (uint8) { + var i, + extraBytes = uint8.length % 3, // if we have 1 byte left, pad 2 bytes + output = "", + temp, length + + function encode (num) { + return lookup.charAt(num) + } + + function tripletToBase64 (num) { + return encode(num >> 18 & 0x3F) + encode(num >> 12 & 0x3F) + encode(num >> 6 & 0x3F) + encode(num & 0x3F) + } + + // go through the array every three bytes, we'll deal with trailing stuff later + for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) { + temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]) + output += tripletToBase64(temp) + } + + // pad the end with zeros, but make sure to not forget the extra bytes + switch (extraBytes) { + case 1: + temp = uint8[uint8.length - 1] + output += encode(temp >> 2) + output += encode((temp << 4) & 0x3F) + output += '==' + break + case 2: + temp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1]) + output += encode(temp >> 10) + output += encode((temp >> 4) & 0x3F) + output += encode((temp << 2) & 0x3F) + output += '=' + break + } + + return output + } + + exports.toByteArray = b64ToByteArray + exports.fromByteArray = uint8ToBase64 +}(typeof exports === 'undefined' ? (this.base64js = {}) : exports)) + +},{}],45:[function(require,module,exports){ +exports.read = function (buffer, offset, isLE, mLen, nBytes) { + var e, m + var eLen = nBytes * 8 - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var nBits = -7 + var i = isLE ? (nBytes - 1) : 0 + var d = isLE ? -1 : 1 + var s = buffer[offset + i] + + i += d + + e = s & ((1 << (-nBits)) - 1) + s >>= (-nBits) + nBits += eLen + for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {} + + m = e & ((1 << (-nBits)) - 1) + e >>= (-nBits) + nBits += mLen + for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {} + + if (e === 0) { + e = 1 - eBias + } else if (e === eMax) { + return m ? NaN : ((s ? -1 : 1) * Infinity) + } else { + m = m + Math.pow(2, mLen) + e = e - eBias + } + return (s ? -1 : 1) * m * Math.pow(2, e - mLen) +} + +exports.write = function (buffer, value, offset, isLE, mLen, nBytes) { + var e, m, c + var eLen = nBytes * 8 - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0) + var i = isLE ? 0 : (nBytes - 1) + var d = isLE ? 1 : -1 + var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0 + + value = Math.abs(value) + + if (isNaN(value) || value === Infinity) { + m = isNaN(value) ? 1 : 0 + e = eMax + } else { + e = Math.floor(Math.log(value) / Math.LN2) + if (value * (c = Math.pow(2, -e)) < 1) { + e-- + c *= 2 + } + if (e + eBias >= 1) { + value += rt / c + } else { + value += rt * Math.pow(2, 1 - eBias) + } + if (value * c >= 2) { + e++ + c /= 2 + } + + if (e + eBias >= eMax) { + m = 0 + e = eMax + } else if (e + eBias >= 1) { + m = (value * c - 1) * Math.pow(2, mLen) + e = e + eBias + } else { + m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen) + e = 0 + } + } + + for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {} + + e = (e << mLen) | m + eLen += mLen + for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {} + + buffer[offset + i - d] |= s * 128 +} + +},{}],46:[function(require,module,exports){ + +/** + * isArray + */ + +var isArray = Array.isArray; + +/** + * toString + */ + +var str = Object.prototype.toString; + +/** + * Whether or not the given `val` + * is an array. + * + * example: + * + * isArray([]); + * // > true + * isArray(arguments); + * // > false + * isArray(''); + * // > false + * + * @param {mixed} val + * @return {bool} + */ + +module.exports = isArray || function (val) { + return !! val && '[object Array]' == str.call(val); +}; + +},{}],47:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// 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. + +function EventEmitter() { + this._events = this._events || {}; + this._maxListeners = this._maxListeners || undefined; +} +module.exports = EventEmitter; + +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +EventEmitter.defaultMaxListeners = 10; + +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +EventEmitter.prototype.setMaxListeners = function(n) { + if (!isNumber(n) || n < 0 || isNaN(n)) + throw TypeError('n must be a positive number'); + this._maxListeners = n; + return this; +}; + +EventEmitter.prototype.emit = function(type) { + var er, handler, len, args, i, listeners; + + if (!this._events) + this._events = {}; + + // If there is no 'error' event listener then throw. + if (type === 'error') { + if (!this._events.error || + (isObject(this._events.error) && !this._events.error.length)) { + er = arguments[1]; + if (er instanceof Error) { + throw er; // Unhandled 'error' event + } + throw TypeError('Uncaught, unspecified "error" event.'); + } + } + + handler = this._events[type]; + + if (isUndefined(handler)) + return false; + + if (isFunction(handler)) { + switch (arguments.length) { + // fast cases + case 1: + handler.call(this); + break; + case 2: + handler.call(this, arguments[1]); + break; + case 3: + handler.call(this, arguments[1], arguments[2]); + break; + // slower + default: + len = arguments.length; + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + handler.apply(this, args); + } + } else if (isObject(handler)) { + len = arguments.length; + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + + listeners = handler.slice(); + len = listeners.length; + for (i = 0; i < len; i++) + listeners[i].apply(this, args); + } + + return true; +}; + +EventEmitter.prototype.addListener = function(type, listener) { + var m; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events) + this._events = {}; + + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (this._events.newListener) + this.emit('newListener', type, + isFunction(listener.listener) ? + listener.listener : listener); + + if (!this._events[type]) + // Optimize the case of one listener. Don't need the extra array object. + this._events[type] = listener; + else if (isObject(this._events[type])) + // If we've already got an array, just append. + this._events[type].push(listener); + else + // Adding the second element, need to change to array. + this._events[type] = [this._events[type], listener]; + + // Check for listener leak + if (isObject(this._events[type]) && !this._events[type].warned) { + var m; + if (!isUndefined(this._maxListeners)) { + m = this._maxListeners; + } else { + m = EventEmitter.defaultMaxListeners; + } + + if (m && m > 0 && this._events[type].length > m) { + this._events[type].warned = true; + console.error('(node) warning: possible EventEmitter memory ' + + 'leak detected. %d listeners added. ' + + 'Use emitter.setMaxListeners() to increase limit.', + this._events[type].length); + if (typeof console.trace === 'function') { + // not supported in IE 10 + console.trace(); + } + } + } + + return this; +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.once = function(type, listener) { + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + var fired = false; + + function g() { + this.removeListener(type, g); + + if (!fired) { + fired = true; + listener.apply(this, arguments); + } + } + + g.listener = listener; + this.on(type, g); + + return this; +}; + +// emits a 'removeListener' event iff the listener was removed +EventEmitter.prototype.removeListener = function(type, listener) { + var list, position, length, i; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events || !this._events[type]) + return this; + + list = this._events[type]; + length = list.length; + position = -1; + + if (list === listener || + (isFunction(list.listener) && list.listener === listener)) { + delete this._events[type]; + if (this._events.removeListener) + this.emit('removeListener', type, listener); + + } else if (isObject(list)) { + for (i = length; i-- > 0;) { + if (list[i] === listener || + (list[i].listener && list[i].listener === listener)) { + position = i; + break; + } + } + + if (position < 0) + return this; + + if (list.length === 1) { + list.length = 0; + delete this._events[type]; + } else { + list.splice(position, 1); + } + + if (this._events.removeListener) + this.emit('removeListener', type, listener); + } + + return this; +}; + +EventEmitter.prototype.removeAllListeners = function(type) { + var key, listeners; + + if (!this._events) + return this; + + // not listening for removeListener, no need to emit + if (!this._events.removeListener) { + if (arguments.length === 0) + this._events = {}; + else if (this._events[type]) + delete this._events[type]; + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + for (key in this._events) { + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = {}; + return this; + } + + listeners = this._events[type]; + + if (isFunction(listeners)) { + this.removeListener(type, listeners); + } else { + // LIFO order + while (listeners.length) + this.removeListener(type, listeners[listeners.length - 1]); + } + delete this._events[type]; + + return this; +}; + +EventEmitter.prototype.listeners = function(type) { + var ret; + if (!this._events || !this._events[type]) + ret = []; + else if (isFunction(this._events[type])) + ret = [this._events[type]]; + else + ret = this._events[type].slice(); + return ret; +}; + +EventEmitter.listenerCount = function(emitter, type) { + var ret; + if (!emitter._events || !emitter._events[type]) + ret = 0; + else if (isFunction(emitter._events[type])) + ret = 1; + else + ret = emitter._events[type].length; + return ret; +}; + +function isFunction(arg) { + return typeof arg === 'function'; +} + +function isNumber(arg) { + return typeof arg === 'number'; +} + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} + +function isUndefined(arg) { + return arg === void 0; +} + +},{}],48:[function(require,module,exports){ +module.exports = Array.isArray || function (arr) { + return Object.prototype.toString.call(arr) == '[object Array]'; +}; + +},{}],49:[function(require,module,exports){ +exports.endianness = function () { return 'LE' }; + +exports.hostname = function () { + if (typeof location !== 'undefined') { + return location.hostname + } + else return ''; +}; + +exports.loadavg = function () { return [] }; + +exports.uptime = function () { return 0 }; + +exports.freemem = function () { + return Number.MAX_VALUE; +}; + +exports.totalmem = function () { + return Number.MAX_VALUE; +}; + +exports.cpus = function () { return [] }; + +exports.type = function () { return 'Browser' }; + +exports.release = function () { + if (typeof navigator !== 'undefined') { + return navigator.appVersion; + } + return ''; +}; + +exports.networkInterfaces += exports.getNetworkInterfaces += function () { return {} }; + +exports.arch = function () { return 'javascript' }; + +exports.platform = function () { return 'browser' }; + +exports.tmpdir = exports.tmpDir = function () { + return '/tmp'; +}; + +exports.EOL = '\n'; + +},{}],50:[function(require,module,exports){ +// shim for using process in browser + +var process = module.exports = {}; +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; + +function cleanUpNextTick() { + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } +} + +function drainQueue() { + if (draining) { + return; + } + var timeout = setTimeout(cleanUpNextTick); + draining = true; + + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + currentQueue[queueIndex].run(); + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + clearTimeout(timeout); +} + +process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + setTimeout(drainQueue, 0); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +// TODO(shtylman) +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; + +},{}],51:[function(require,module,exports){ +module.exports = require("./lib/_stream_duplex.js") + +},{"./lib/_stream_duplex.js":52}],52:[function(require,module,exports){ +(function (process){ +// Copyright Joyent, Inc. and other Node contributors. +// +// 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. + +// a duplex stream is just a stream that is both readable and writable. +// Since JS doesn't have multiple prototypal inheritance, this class +// prototypally inherits from Readable, and then parasitically from +// Writable. + +module.exports = Duplex; + +/**/ +var objectKeys = Object.keys || function (obj) { + var keys = []; + for (var key in obj) keys.push(key); + return keys; +} +/**/ + + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +var Readable = require('./_stream_readable'); +var Writable = require('./_stream_writable'); + +util.inherits(Duplex, Readable); + +forEach(objectKeys(Writable.prototype), function(method) { + if (!Duplex.prototype[method]) + Duplex.prototype[method] = Writable.prototype[method]; +}); + +function Duplex(options) { + if (!(this instanceof Duplex)) + return new Duplex(options); + + Readable.call(this, options); + Writable.call(this, options); + + if (options && options.readable === false) + this.readable = false; + + if (options && options.writable === false) + this.writable = false; + + this.allowHalfOpen = true; + if (options && options.allowHalfOpen === false) + this.allowHalfOpen = false; + + this.once('end', onend); +} + +// the no-half-open enforcer +function onend() { + // if we allow half-open state, or if the writable side ended, + // then we're ok. + if (this.allowHalfOpen || this._writableState.ended) + return; + + // no more data can be written. + // But allow more writes to happen in this tick. + process.nextTick(this.end.bind(this)); +} + +function forEach (xs, f) { + for (var i = 0, l = xs.length; i < l; i++) { + f(xs[i], i); + } +} + +}).call(this,require('_process')) +},{"./_stream_readable":54,"./_stream_writable":56,"_process":50,"core-util-is":57,"inherits":69}],53:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// 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. + +// a passthrough stream. +// basically just the most minimal sort of Transform stream. +// Every written chunk gets output as-is. + +module.exports = PassThrough; + +var Transform = require('./_stream_transform'); + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +util.inherits(PassThrough, Transform); + +function PassThrough(options) { + if (!(this instanceof PassThrough)) + return new PassThrough(options); + + Transform.call(this, options); +} + +PassThrough.prototype._transform = function(chunk, encoding, cb) { + cb(null, chunk); +}; + +},{"./_stream_transform":55,"core-util-is":57,"inherits":69}],54:[function(require,module,exports){ +(function (process){ +// Copyright Joyent, Inc. and other Node contributors. +// +// 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. + +module.exports = Readable; + +/**/ +var isArray = require('isarray'); +/**/ + + +/**/ +var Buffer = require('buffer').Buffer; +/**/ + +Readable.ReadableState = ReadableState; + +var EE = require('events').EventEmitter; + +/**/ +if (!EE.listenerCount) EE.listenerCount = function(emitter, type) { + return emitter.listeners(type).length; +}; +/**/ + +var Stream = require('stream'); + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +var StringDecoder; + + +/**/ +var debug = require('util'); +if (debug && debug.debuglog) { + debug = debug.debuglog('stream'); +} else { + debug = function () {}; +} +/**/ + + +util.inherits(Readable, Stream); + +function ReadableState(options, stream) { + var Duplex = require('./_stream_duplex'); + + options = options || {}; + + // the point at which it stops calling _read() to fill the buffer + // Note: 0 is a valid value, means "don't call _read preemptively ever" + var hwm = options.highWaterMark; + var defaultHwm = options.objectMode ? 16 : 16 * 1024; + this.highWaterMark = (hwm || hwm === 0) ? hwm : defaultHwm; + + // cast to ints. + this.highWaterMark = ~~this.highWaterMark; + + this.buffer = []; + this.length = 0; + this.pipes = null; + this.pipesCount = 0; + this.flowing = null; + this.ended = false; + this.endEmitted = false; + this.reading = false; + + // a flag to be able to tell if the onwrite cb is called immediately, + // or on a later tick. We set this to true at first, because any + // actions that shouldn't happen until "later" should generally also + // not happen before the first write call. + this.sync = true; + + // whenever we return null, then we set a flag to say + // that we're awaiting a 'readable' event emission. + this.needReadable = false; + this.emittedReadable = false; + this.readableListening = false; + + + // object stream flag. Used to make read(n) ignore n and to + // make all the buffer merging and length checks go away + this.objectMode = !!options.objectMode; + + if (stream instanceof Duplex) + this.objectMode = this.objectMode || !!options.readableObjectMode; + + // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + this.defaultEncoding = options.defaultEncoding || 'utf8'; + + // when piping, we only care about 'readable' events that happen + // after read()ing all the bytes and not getting any pushback. + this.ranOut = false; + + // the number of writers that are awaiting a drain event in .pipe()s + this.awaitDrain = 0; + + // if true, a maybeReadMore has been scheduled + this.readingMore = false; + + this.decoder = null; + this.encoding = null; + if (options.encoding) { + if (!StringDecoder) + StringDecoder = require('string_decoder/').StringDecoder; + this.decoder = new StringDecoder(options.encoding); + this.encoding = options.encoding; + } +} + +function Readable(options) { + var Duplex = require('./_stream_duplex'); + + if (!(this instanceof Readable)) + return new Readable(options); + + this._readableState = new ReadableState(options, this); + + // legacy + this.readable = true; + + Stream.call(this); +} + +// Manually shove something into the read() buffer. +// This returns true if the highWaterMark has not been hit yet, +// similar to how Writable.write() returns true if you should +// write() some more. +Readable.prototype.push = function(chunk, encoding) { + var state = this._readableState; + + if (util.isString(chunk) && !state.objectMode) { + encoding = encoding || state.defaultEncoding; + if (encoding !== state.encoding) { + chunk = new Buffer(chunk, encoding); + encoding = ''; + } + } + + return readableAddChunk(this, state, chunk, encoding, false); +}; + +// Unshift should *always* be something directly out of read() +Readable.prototype.unshift = function(chunk) { + var state = this._readableState; + return readableAddChunk(this, state, chunk, '', true); +}; + +function readableAddChunk(stream, state, chunk, encoding, addToFront) { + var er = chunkInvalid(state, chunk); + if (er) { + stream.emit('error', er); + } else if (util.isNullOrUndefined(chunk)) { + state.reading = false; + if (!state.ended) + onEofChunk(stream, state); + } else if (state.objectMode || chunk && chunk.length > 0) { + if (state.ended && !addToFront) { + var e = new Error('stream.push() after EOF'); + stream.emit('error', e); + } else if (state.endEmitted && addToFront) { + var e = new Error('stream.unshift() after end event'); + stream.emit('error', e); + } else { + if (state.decoder && !addToFront && !encoding) + chunk = state.decoder.write(chunk); + + if (!addToFront) + state.reading = false; + + // if we want the data now, just emit it. + if (state.flowing && state.length === 0 && !state.sync) { + stream.emit('data', chunk); + stream.read(0); + } else { + // update the buffer info. + state.length += state.objectMode ? 1 : chunk.length; + if (addToFront) + state.buffer.unshift(chunk); + else + state.buffer.push(chunk); + + if (state.needReadable) + emitReadable(stream); + } + + maybeReadMore(stream, state); + } + } else if (!addToFront) { + state.reading = false; + } + + return needMoreData(state); +} + + + +// if it's past the high water mark, we can push in some more. +// Also, if we have no data yet, we can stand some +// more bytes. This is to work around cases where hwm=0, +// such as the repl. Also, if the push() triggered a +// readable event, and the user called read(largeNumber) such that +// needReadable was set, then we ought to push more, so that another +// 'readable' event will be triggered. +function needMoreData(state) { + return !state.ended && + (state.needReadable || + state.length < state.highWaterMark || + state.length === 0); +} + +// backwards compatibility. +Readable.prototype.setEncoding = function(enc) { + if (!StringDecoder) + StringDecoder = require('string_decoder/').StringDecoder; + this._readableState.decoder = new StringDecoder(enc); + this._readableState.encoding = enc; + return this; +}; + +// Don't raise the hwm > 128MB +var MAX_HWM = 0x800000; +function roundUpToNextPowerOf2(n) { + if (n >= MAX_HWM) { + n = MAX_HWM; + } else { + // Get the next highest power of 2 + n--; + for (var p = 1; p < 32; p <<= 1) n |= n >> p; + n++; + } + return n; +} + +function howMuchToRead(n, state) { + if (state.length === 0 && state.ended) + return 0; + + if (state.objectMode) + return n === 0 ? 0 : 1; + + if (isNaN(n) || util.isNull(n)) { + // only flow one buffer at a time + if (state.flowing && state.buffer.length) + return state.buffer[0].length; + else + return state.length; + } + + if (n <= 0) + return 0; + + // If we're asking for more than the target buffer level, + // then raise the water mark. Bump up to the next highest + // power of 2, to prevent increasing it excessively in tiny + // amounts. + if (n > state.highWaterMark) + state.highWaterMark = roundUpToNextPowerOf2(n); + + // don't have that much. return null, unless we've ended. + if (n > state.length) { + if (!state.ended) { + state.needReadable = true; + return 0; + } else + return state.length; + } + + return n; +} + +// you can override either this method, or the async _read(n) below. +Readable.prototype.read = function(n) { + debug('read', n); + var state = this._readableState; + var nOrig = n; + + if (!util.isNumber(n) || n > 0) + state.emittedReadable = false; + + // if we're doing read(0) to trigger a readable event, but we + // already have a bunch of data in the buffer, then just trigger + // the 'readable' event and move on. + if (n === 0 && + state.needReadable && + (state.length >= state.highWaterMark || state.ended)) { + debug('read: emitReadable', state.length, state.ended); + if (state.length === 0 && state.ended) + endReadable(this); + else + emitReadable(this); + return null; + } + + n = howMuchToRead(n, state); + + // if we've ended, and we're now clear, then finish it up. + if (n === 0 && state.ended) { + if (state.length === 0) + endReadable(this); + return null; + } + + // All the actual chunk generation logic needs to be + // *below* the call to _read. The reason is that in certain + // synthetic stream cases, such as passthrough streams, _read + // may be a completely synchronous operation which may change + // the state of the read buffer, providing enough data when + // before there was *not* enough. + // + // So, the steps are: + // 1. Figure out what the state of things will be after we do + // a read from the buffer. + // + // 2. If that resulting state will trigger a _read, then call _read. + // Note that this may be asynchronous, or synchronous. Yes, it is + // deeply ugly to write APIs this way, but that still doesn't mean + // that the Readable class should behave improperly, as streams are + // designed to be sync/async agnostic. + // Take note if the _read call is sync or async (ie, if the read call + // has returned yet), so that we know whether or not it's safe to emit + // 'readable' etc. + // + // 3. Actually pull the requested chunks out of the buffer and return. + + // if we need a readable event, then we need to do some reading. + var doRead = state.needReadable; + debug('need readable', doRead); + + // if we currently have less than the highWaterMark, then also read some + if (state.length === 0 || state.length - n < state.highWaterMark) { + doRead = true; + debug('length less than watermark', doRead); + } + + // however, if we've ended, then there's no point, and if we're already + // reading, then it's unnecessary. + if (state.ended || state.reading) { + doRead = false; + debug('reading or ended', doRead); + } + + if (doRead) { + debug('do read'); + state.reading = true; + state.sync = true; + // if the length is currently zero, then we *need* a readable event. + if (state.length === 0) + state.needReadable = true; + // call internal read method + this._read(state.highWaterMark); + state.sync = false; + } + + // If _read pushed data synchronously, then `reading` will be false, + // and we need to re-evaluate how much data we can return to the user. + if (doRead && !state.reading) + n = howMuchToRead(nOrig, state); + + var ret; + if (n > 0) + ret = fromList(n, state); + else + ret = null; + + if (util.isNull(ret)) { + state.needReadable = true; + n = 0; + } + + state.length -= n; + + // If we have nothing in the buffer, then we want to know + // as soon as we *do* get something into the buffer. + if (state.length === 0 && !state.ended) + state.needReadable = true; + + // If we tried to read() past the EOF, then emit end on the next tick. + if (nOrig !== n && state.ended && state.length === 0) + endReadable(this); + + if (!util.isNull(ret)) + this.emit('data', ret); + + return ret; +}; + +function chunkInvalid(state, chunk) { + var er = null; + if (!util.isBuffer(chunk) && + !util.isString(chunk) && + !util.isNullOrUndefined(chunk) && + !state.objectMode) { + er = new TypeError('Invalid non-string/buffer chunk'); + } + return er; +} + + +function onEofChunk(stream, state) { + if (state.decoder && !state.ended) { + var chunk = state.decoder.end(); + if (chunk && chunk.length) { + state.buffer.push(chunk); + state.length += state.objectMode ? 1 : chunk.length; + } + } + state.ended = true; + + // emit 'readable' now to make sure it gets picked up. + emitReadable(stream); +} + +// Don't emit readable right away in sync mode, because this can trigger +// another read() call => stack overflow. This way, it might trigger +// a nextTick recursion warning, but that's not so bad. +function emitReadable(stream) { + var state = stream._readableState; + state.needReadable = false; + if (!state.emittedReadable) { + debug('emitReadable', state.flowing); + state.emittedReadable = true; + if (state.sync) + process.nextTick(function() { + emitReadable_(stream); + }); + else + emitReadable_(stream); + } +} + +function emitReadable_(stream) { + debug('emit readable'); + stream.emit('readable'); + flow(stream); +} + + +// at this point, the user has presumably seen the 'readable' event, +// and called read() to consume some data. that may have triggered +// in turn another _read(n) call, in which case reading = true if +// it's in progress. +// However, if we're not ended, or reading, and the length < hwm, +// then go ahead and try to read some more preemptively. +function maybeReadMore(stream, state) { + if (!state.readingMore) { + state.readingMore = true; + process.nextTick(function() { + maybeReadMore_(stream, state); + }); + } +} + +function maybeReadMore_(stream, state) { + var len = state.length; + while (!state.reading && !state.flowing && !state.ended && + state.length < state.highWaterMark) { + debug('maybeReadMore read 0'); + stream.read(0); + if (len === state.length) + // didn't get any data, stop spinning. + break; + else + len = state.length; + } + state.readingMore = false; +} + +// abstract method. to be overridden in specific implementation classes. +// call cb(er, data) where data is <= n in length. +// for virtual (non-string, non-buffer) streams, "length" is somewhat +// arbitrary, and perhaps not very meaningful. +Readable.prototype._read = function(n) { + this.emit('error', new Error('not implemented')); +}; + +Readable.prototype.pipe = function(dest, pipeOpts) { + var src = this; + var state = this._readableState; + + switch (state.pipesCount) { + case 0: + state.pipes = dest; + break; + case 1: + state.pipes = [state.pipes, dest]; + break; + default: + state.pipes.push(dest); + break; + } + state.pipesCount += 1; + debug('pipe count=%d opts=%j', state.pipesCount, pipeOpts); + + var doEnd = (!pipeOpts || pipeOpts.end !== false) && + dest !== process.stdout && + dest !== process.stderr; + + var endFn = doEnd ? onend : cleanup; + if (state.endEmitted) + process.nextTick(endFn); + else + src.once('end', endFn); + + dest.on('unpipe', onunpipe); + function onunpipe(readable) { + debug('onunpipe'); + if (readable === src) { + cleanup(); + } + } + + function onend() { + debug('onend'); + dest.end(); + } + + // when the dest drains, it reduces the awaitDrain counter + // on the source. This would be more elegant with a .once() + // handler in flow(), but adding and removing repeatedly is + // too slow. + var ondrain = pipeOnDrain(src); + dest.on('drain', ondrain); + + function cleanup() { + debug('cleanup'); + // cleanup event handlers once the pipe is broken + dest.removeListener('close', onclose); + dest.removeListener('finish', onfinish); + dest.removeListener('drain', ondrain); + dest.removeListener('error', onerror); + dest.removeListener('unpipe', onunpipe); + src.removeListener('end', onend); + src.removeListener('end', cleanup); + src.removeListener('data', ondata); + + // if the reader is waiting for a drain event from this + // specific writer, then it would cause it to never start + // flowing again. + // So, if this is awaiting a drain, then we just call it now. + // If we don't know, then assume that we are waiting for one. + if (state.awaitDrain && + (!dest._writableState || dest._writableState.needDrain)) + ondrain(); + } + + src.on('data', ondata); + function ondata(chunk) { + debug('ondata'); + var ret = dest.write(chunk); + if (false === ret) { + debug('false write response, pause', + src._readableState.awaitDrain); + src._readableState.awaitDrain++; + src.pause(); + } + } + + // if the dest has an error, then stop piping into it. + // however, don't suppress the throwing behavior for this. + function onerror(er) { + debug('onerror', er); + unpipe(); + dest.removeListener('error', onerror); + if (EE.listenerCount(dest, 'error') === 0) + dest.emit('error', er); + } + // This is a brutally ugly hack to make sure that our error handler + // is attached before any userland ones. NEVER DO THIS. + if (!dest._events || !dest._events.error) + dest.on('error', onerror); + else if (isArray(dest._events.error)) + dest._events.error.unshift(onerror); + else + dest._events.error = [onerror, dest._events.error]; + + + + // Both close and finish should trigger unpipe, but only once. + function onclose() { + dest.removeListener('finish', onfinish); + unpipe(); + } + dest.once('close', onclose); + function onfinish() { + debug('onfinish'); + dest.removeListener('close', onclose); + unpipe(); + } + dest.once('finish', onfinish); + + function unpipe() { + debug('unpipe'); + src.unpipe(dest); + } + + // tell the dest that it's being piped to + dest.emit('pipe', src); + + // start the flow if it hasn't been started already. + if (!state.flowing) { + debug('pipe resume'); + src.resume(); + } + + return dest; +}; + +function pipeOnDrain(src) { + return function() { + var state = src._readableState; + debug('pipeOnDrain', state.awaitDrain); + if (state.awaitDrain) + state.awaitDrain--; + if (state.awaitDrain === 0 && EE.listenerCount(src, 'data')) { + state.flowing = true; + flow(src); + } + }; +} + + +Readable.prototype.unpipe = function(dest) { + var state = this._readableState; + + // if we're not piping anywhere, then do nothing. + if (state.pipesCount === 0) + return this; + + // just one destination. most common case. + if (state.pipesCount === 1) { + // passed in one, but it's not the right one. + if (dest && dest !== state.pipes) + return this; + + if (!dest) + dest = state.pipes; + + // got a match. + state.pipes = null; + state.pipesCount = 0; + state.flowing = false; + if (dest) + dest.emit('unpipe', this); + return this; + } + + // slow case. multiple pipe destinations. + + if (!dest) { + // remove all. + var dests = state.pipes; + var len = state.pipesCount; + state.pipes = null; + state.pipesCount = 0; + state.flowing = false; + + for (var i = 0; i < len; i++) + dests[i].emit('unpipe', this); + return this; + } + + // try to find the right one. + var i = indexOf(state.pipes, dest); + if (i === -1) + return this; + + state.pipes.splice(i, 1); + state.pipesCount -= 1; + if (state.pipesCount === 1) + state.pipes = state.pipes[0]; + + dest.emit('unpipe', this); + + return this; +}; + +// set up data events if they are asked for +// Ensure readable listeners eventually get something +Readable.prototype.on = function(ev, fn) { + var res = Stream.prototype.on.call(this, ev, fn); + + // If listening to data, and it has not explicitly been paused, + // then call resume to start the flow of data on the next tick. + if (ev === 'data' && false !== this._readableState.flowing) { + this.resume(); + } + + if (ev === 'readable' && this.readable) { + var state = this._readableState; + if (!state.readableListening) { + state.readableListening = true; + state.emittedReadable = false; + state.needReadable = true; + if (!state.reading) { + var self = this; + process.nextTick(function() { + debug('readable nexttick read 0'); + self.read(0); + }); + } else if (state.length) { + emitReadable(this, state); + } + } + } + + return res; +}; +Readable.prototype.addListener = Readable.prototype.on; + +// pause() and resume() are remnants of the legacy readable stream API +// If the user uses them, then switch into old mode. +Readable.prototype.resume = function() { + var state = this._readableState; + if (!state.flowing) { + debug('resume'); + state.flowing = true; + if (!state.reading) { + debug('resume read 0'); + this.read(0); + } + resume(this, state); + } + return this; +}; + +function resume(stream, state) { + if (!state.resumeScheduled) { + state.resumeScheduled = true; + process.nextTick(function() { + resume_(stream, state); + }); + } +} + +function resume_(stream, state) { + state.resumeScheduled = false; + stream.emit('resume'); + flow(stream); + if (state.flowing && !state.reading) + stream.read(0); +} + +Readable.prototype.pause = function() { + debug('call pause flowing=%j', this._readableState.flowing); + if (false !== this._readableState.flowing) { + debug('pause'); + this._readableState.flowing = false; + this.emit('pause'); + } + return this; +}; + +function flow(stream) { + var state = stream._readableState; + debug('flow', state.flowing); + if (state.flowing) { + do { + var chunk = stream.read(); + } while (null !== chunk && state.flowing); + } +} + +// wrap an old-style stream as the async data source. +// This is *not* part of the readable stream interface. +// It is an ugly unfortunate mess of history. +Readable.prototype.wrap = function(stream) { + var state = this._readableState; + var paused = false; + + var self = this; + stream.on('end', function() { + debug('wrapped end'); + if (state.decoder && !state.ended) { + var chunk = state.decoder.end(); + if (chunk && chunk.length) + self.push(chunk); + } + + self.push(null); + }); + + stream.on('data', function(chunk) { + debug('wrapped data'); + if (state.decoder) + chunk = state.decoder.write(chunk); + if (!chunk || !state.objectMode && !chunk.length) + return; + + var ret = self.push(chunk); + if (!ret) { + paused = true; + stream.pause(); + } + }); + + // proxy all the other methods. + // important when wrapping filters and duplexes. + for (var i in stream) { + if (util.isFunction(stream[i]) && util.isUndefined(this[i])) { + this[i] = function(method) { return function() { + return stream[method].apply(stream, arguments); + }}(i); + } + } + + // proxy certain important events. + var events = ['error', 'close', 'destroy', 'pause', 'resume']; + forEach(events, function(ev) { + stream.on(ev, self.emit.bind(self, ev)); + }); + + // when we try to consume some more bytes, simply unpause the + // underlying stream. + self._read = function(n) { + debug('wrapped _read', n); + if (paused) { + paused = false; + stream.resume(); + } + }; + + return self; +}; + + + +// exposed for testing purposes only. +Readable._fromList = fromList; + +// Pluck off n bytes from an array of buffers. +// Length is the combined lengths of all the buffers in the list. +function fromList(n, state) { + var list = state.buffer; + var length = state.length; + var stringMode = !!state.decoder; + var objectMode = !!state.objectMode; + var ret; + + // nothing in the list, definitely empty. + if (list.length === 0) + return null; + + if (length === 0) + ret = null; + else if (objectMode) + ret = list.shift(); + else if (!n || n >= length) { + // read it all, truncate the array. + if (stringMode) + ret = list.join(''); + else + ret = Buffer.concat(list, length); + list.length = 0; + } else { + // read just some of it. + if (n < list[0].length) { + // just take a part of the first list item. + // slice is the same for buffers and strings. + var buf = list[0]; + ret = buf.slice(0, n); + list[0] = buf.slice(n); + } else if (n === list[0].length) { + // first list is a perfect match + ret = list.shift(); + } else { + // complex case. + // we have enough to cover it, but it spans past the first buffer. + if (stringMode) + ret = ''; + else + ret = new Buffer(n); + + var c = 0; + for (var i = 0, l = list.length; i < l && c < n; i++) { + var buf = list[0]; + var cpy = Math.min(n - c, buf.length); + + if (stringMode) + ret += buf.slice(0, cpy); + else + buf.copy(ret, c, 0, cpy); + + if (cpy < buf.length) + list[0] = buf.slice(cpy); + else + list.shift(); + + c += cpy; + } + } + } + + return ret; +} + +function endReadable(stream) { + var state = stream._readableState; + + // If we get here before consuming all the bytes, then that is a + // bug in node. Should never happen. + if (state.length > 0) + throw new Error('endReadable called on non-empty stream'); + + if (!state.endEmitted) { + state.ended = true; + process.nextTick(function() { + // Check that we didn't get one last unshift. + if (!state.endEmitted && state.length === 0) { + state.endEmitted = true; + stream.readable = false; + stream.emit('end'); + } + }); + } +} + +function forEach (xs, f) { + for (var i = 0, l = xs.length; i < l; i++) { + f(xs[i], i); + } +} + +function indexOf (xs, x) { + for (var i = 0, l = xs.length; i < l; i++) { + if (xs[i] === x) return i; + } + return -1; +} + +}).call(this,require('_process')) +},{"./_stream_duplex":52,"_process":50,"buffer":43,"core-util-is":57,"events":47,"inherits":69,"isarray":48,"stream":62,"string_decoder/":63,"util":42}],55:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// 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. + + +// a transform stream is a readable/writable stream where you do +// something with the data. Sometimes it's called a "filter", +// but that's not a great name for it, since that implies a thing where +// some bits pass through, and others are simply ignored. (That would +// be a valid example of a transform, of course.) +// +// While the output is causally related to the input, it's not a +// necessarily symmetric or synchronous transformation. For example, +// a zlib stream might take multiple plain-text writes(), and then +// emit a single compressed chunk some time in the future. +// +// Here's how this works: +// +// The Transform stream has all the aspects of the readable and writable +// stream classes. When you write(chunk), that calls _write(chunk,cb) +// internally, and returns false if there's a lot of pending writes +// buffered up. When you call read(), that calls _read(n) until +// there's enough pending readable data buffered up. +// +// In a transform stream, the written data is placed in a buffer. When +// _read(n) is called, it transforms the queued up data, calling the +// buffered _write cb's as it consumes chunks. If consuming a single +// written chunk would result in multiple output chunks, then the first +// outputted bit calls the readcb, and subsequent chunks just go into +// the read buffer, and will cause it to emit 'readable' if necessary. +// +// This way, back-pressure is actually determined by the reading side, +// since _read has to be called to start processing a new chunk. However, +// a pathological inflate type of transform can cause excessive buffering +// here. For example, imagine a stream where every byte of input is +// interpreted as an integer from 0-255, and then results in that many +// bytes of output. Writing the 4 bytes {ff,ff,ff,ff} would result in +// 1kb of data being output. In this case, you could write a very small +// amount of input, and end up with a very large amount of output. In +// such a pathological inflating mechanism, there'd be no way to tell +// the system to stop doing the transform. A single 4MB write could +// cause the system to run out of memory. +// +// However, even in such a pathological case, only a single written chunk +// would be consumed, and then the rest would wait (un-transformed) until +// the results of the previous transformed chunk were consumed. + +module.exports = Transform; + +var Duplex = require('./_stream_duplex'); + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +util.inherits(Transform, Duplex); + + +function TransformState(options, stream) { + this.afterTransform = function(er, data) { + return afterTransform(stream, er, data); + }; + + this.needTransform = false; + this.transforming = false; + this.writecb = null; + this.writechunk = null; +} + +function afterTransform(stream, er, data) { + var ts = stream._transformState; + ts.transforming = false; + + var cb = ts.writecb; + + if (!cb) + return stream.emit('error', new Error('no writecb in Transform class')); + + ts.writechunk = null; + ts.writecb = null; + + if (!util.isNullOrUndefined(data)) + stream.push(data); + + if (cb) + cb(er); + + var rs = stream._readableState; + rs.reading = false; + if (rs.needReadable || rs.length < rs.highWaterMark) { + stream._read(rs.highWaterMark); + } +} + + +function Transform(options) { + if (!(this instanceof Transform)) + return new Transform(options); + + Duplex.call(this, options); + + this._transformState = new TransformState(options, this); + + // when the writable side finishes, then flush out anything remaining. + var stream = this; + + // start out asking for a readable event once data is transformed. + this._readableState.needReadable = true; + + // we have implemented the _read method, and done the other things + // that Readable wants before the first _read call, so unset the + // sync guard flag. + this._readableState.sync = false; + + this.once('prefinish', function() { + if (util.isFunction(this._flush)) + this._flush(function(er) { + done(stream, er); + }); + else + done(stream); + }); +} + +Transform.prototype.push = function(chunk, encoding) { + this._transformState.needTransform = false; + return Duplex.prototype.push.call(this, chunk, encoding); +}; + +// This is the part where you do stuff! +// override this function in implementation classes. +// 'chunk' is an input chunk. +// +// Call `push(newChunk)` to pass along transformed output +// to the readable side. You may call 'push' zero or more times. +// +// Call `cb(err)` when you are done with this chunk. If you pass +// an error, then that'll put the hurt on the whole operation. If you +// never call cb(), then you'll never get another chunk. +Transform.prototype._transform = function(chunk, encoding, cb) { + throw new Error('not implemented'); +}; + +Transform.prototype._write = function(chunk, encoding, cb) { + var ts = this._transformState; + ts.writecb = cb; + ts.writechunk = chunk; + ts.writeencoding = encoding; + if (!ts.transforming) { + var rs = this._readableState; + if (ts.needTransform || + rs.needReadable || + rs.length < rs.highWaterMark) + this._read(rs.highWaterMark); + } +}; + +// Doesn't matter what the args are here. +// _transform does all the work. +// That we got here means that the readable side wants more data. +Transform.prototype._read = function(n) { + var ts = this._transformState; + + if (!util.isNull(ts.writechunk) && ts.writecb && !ts.transforming) { + ts.transforming = true; + this._transform(ts.writechunk, ts.writeencoding, ts.afterTransform); + } else { + // mark that we need a transform, so that any data that comes in + // will get processed, now that we've asked for it. + ts.needTransform = true; + } +}; + + +function done(stream, er) { + if (er) + return stream.emit('error', er); + + // if there's nothing in the write buffer, then that means + // that nothing more will ever be provided + var ws = stream._writableState; + var ts = stream._transformState; + + if (ws.length) + throw new Error('calling transform done when ws.length != 0'); + + if (ts.transforming) + throw new Error('calling transform done when still transforming'); + + return stream.push(null); +} + +},{"./_stream_duplex":52,"core-util-is":57,"inherits":69}],56:[function(require,module,exports){ +(function (process){ +// Copyright Joyent, Inc. and other Node contributors. +// +// 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. + +// A bit simpler than readable streams. +// Implement an async ._write(chunk, cb), and it'll handle all +// the drain event emission and buffering. + +module.exports = Writable; + +/**/ +var Buffer = require('buffer').Buffer; +/**/ + +Writable.WritableState = WritableState; + + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +var Stream = require('stream'); + +util.inherits(Writable, Stream); + +function WriteReq(chunk, encoding, cb) { + this.chunk = chunk; + this.encoding = encoding; + this.callback = cb; +} + +function WritableState(options, stream) { + var Duplex = require('./_stream_duplex'); + + options = options || {}; + + // the point at which write() starts returning false + // Note: 0 is a valid value, means that we always return false if + // the entire buffer is not flushed immediately on write() + var hwm = options.highWaterMark; + var defaultHwm = options.objectMode ? 16 : 16 * 1024; + this.highWaterMark = (hwm || hwm === 0) ? hwm : defaultHwm; + + // object stream flag to indicate whether or not this stream + // contains buffers or objects. + this.objectMode = !!options.objectMode; + + if (stream instanceof Duplex) + this.objectMode = this.objectMode || !!options.writableObjectMode; + + // cast to ints. + this.highWaterMark = ~~this.highWaterMark; + + this.needDrain = false; + // at the start of calling end() + this.ending = false; + // when end() has been called, and returned + this.ended = false; + // when 'finish' is emitted + this.finished = false; + + // should we decode strings into buffers before passing to _write? + // this is here so that some node-core streams can optimize string + // handling at a lower level. + var noDecode = options.decodeStrings === false; + this.decodeStrings = !noDecode; + + // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + this.defaultEncoding = options.defaultEncoding || 'utf8'; + + // not an actual buffer we keep track of, but a measurement + // of how much we're waiting to get pushed to some underlying + // socket or file. + this.length = 0; + + // a flag to see when we're in the middle of a write. + this.writing = false; + + // when true all writes will be buffered until .uncork() call + this.corked = 0; + + // a flag to be able to tell if the onwrite cb is called immediately, + // or on a later tick. We set this to true at first, because any + // actions that shouldn't happen until "later" should generally also + // not happen before the first write call. + this.sync = true; + + // a flag to know if we're processing previously buffered items, which + // may call the _write() callback in the same tick, so that we don't + // end up in an overlapped onwrite situation. + this.bufferProcessing = false; + + // the callback that's passed to _write(chunk,cb) + this.onwrite = function(er) { + onwrite(stream, er); + }; + + // the callback that the user supplies to write(chunk,encoding,cb) + this.writecb = null; + + // the amount that is being written when _write is called. + this.writelen = 0; + + this.buffer = []; + + // number of pending user-supplied write callbacks + // this must be 0 before 'finish' can be emitted + this.pendingcb = 0; + + // emit prefinish if the only thing we're waiting for is _write cbs + // This is relevant for synchronous Transform streams + this.prefinished = false; + + // True if the error was already emitted and should not be thrown again + this.errorEmitted = false; +} + +function Writable(options) { + var Duplex = require('./_stream_duplex'); + + // Writable ctor is applied to Duplexes, though they're not + // instanceof Writable, they're instanceof Readable. + if (!(this instanceof Writable) && !(this instanceof Duplex)) + return new Writable(options); + + this._writableState = new WritableState(options, this); + + // legacy. + this.writable = true; + + Stream.call(this); +} + +// Otherwise people can pipe Writable streams, which is just wrong. +Writable.prototype.pipe = function() { + this.emit('error', new Error('Cannot pipe. Not readable.')); +}; + + +function writeAfterEnd(stream, state, cb) { + var er = new Error('write after end'); + // TODO: defer error events consistently everywhere, not just the cb + stream.emit('error', er); + process.nextTick(function() { + cb(er); + }); +} + +// If we get something that is not a buffer, string, null, or undefined, +// and we're not in objectMode, then that's an error. +// Otherwise stream chunks are all considered to be of length=1, and the +// watermarks determine how many objects to keep in the buffer, rather than +// how many bytes or characters. +function validChunk(stream, state, chunk, cb) { + var valid = true; + if (!util.isBuffer(chunk) && + !util.isString(chunk) && + !util.isNullOrUndefined(chunk) && + !state.objectMode) { + var er = new TypeError('Invalid non-string/buffer chunk'); + stream.emit('error', er); + process.nextTick(function() { + cb(er); + }); + valid = false; + } + return valid; +} + +Writable.prototype.write = function(chunk, encoding, cb) { + var state = this._writableState; + var ret = false; + + if (util.isFunction(encoding)) { + cb = encoding; + encoding = null; + } + + if (util.isBuffer(chunk)) + encoding = 'buffer'; + else if (!encoding) + encoding = state.defaultEncoding; + + if (!util.isFunction(cb)) + cb = function() {}; + + if (state.ended) + writeAfterEnd(this, state, cb); + else if (validChunk(this, state, chunk, cb)) { + state.pendingcb++; + ret = writeOrBuffer(this, state, chunk, encoding, cb); + } + + return ret; +}; + +Writable.prototype.cork = function() { + var state = this._writableState; + + state.corked++; +}; + +Writable.prototype.uncork = function() { + var state = this._writableState; + + if (state.corked) { + state.corked--; + + if (!state.writing && + !state.corked && + !state.finished && + !state.bufferProcessing && + state.buffer.length) + clearBuffer(this, state); + } +}; + +function decodeChunk(state, chunk, encoding) { + if (!state.objectMode && + state.decodeStrings !== false && + util.isString(chunk)) { + chunk = new Buffer(chunk, encoding); + } + return chunk; +} + +// if we're already writing something, then just put this +// in the queue, and wait our turn. Otherwise, call _write +// If we return false, then we need a drain event, so set that flag. +function writeOrBuffer(stream, state, chunk, encoding, cb) { + chunk = decodeChunk(state, chunk, encoding); + if (util.isBuffer(chunk)) + encoding = 'buffer'; + var len = state.objectMode ? 1 : chunk.length; + + state.length += len; + + var ret = state.length < state.highWaterMark; + // we must ensure that previous needDrain will not be reset to false. + if (!ret) + state.needDrain = true; + + if (state.writing || state.corked) + state.buffer.push(new WriteReq(chunk, encoding, cb)); + else + doWrite(stream, state, false, len, chunk, encoding, cb); + + return ret; +} + +function doWrite(stream, state, writev, len, chunk, encoding, cb) { + state.writelen = len; + state.writecb = cb; + state.writing = true; + state.sync = true; + if (writev) + stream._writev(chunk, state.onwrite); + else + stream._write(chunk, encoding, state.onwrite); + state.sync = false; +} + +function onwriteError(stream, state, sync, er, cb) { + if (sync) + process.nextTick(function() { + state.pendingcb--; + cb(er); + }); + else { + state.pendingcb--; + cb(er); + } + + stream._writableState.errorEmitted = true; + stream.emit('error', er); +} + +function onwriteStateUpdate(state) { + state.writing = false; + state.writecb = null; + state.length -= state.writelen; + state.writelen = 0; +} + +function onwrite(stream, er) { + var state = stream._writableState; + var sync = state.sync; + var cb = state.writecb; + + onwriteStateUpdate(state); + + if (er) + onwriteError(stream, state, sync, er, cb); + else { + // Check if we're actually ready to finish, but don't emit yet + var finished = needFinish(stream, state); + + if (!finished && + !state.corked && + !state.bufferProcessing && + state.buffer.length) { + clearBuffer(stream, state); + } + + if (sync) { + process.nextTick(function() { + afterWrite(stream, state, finished, cb); + }); + } else { + afterWrite(stream, state, finished, cb); + } + } +} + +function afterWrite(stream, state, finished, cb) { + if (!finished) + onwriteDrain(stream, state); + state.pendingcb--; + cb(); + finishMaybe(stream, state); +} + +// Must force callback to be called on nextTick, so that we don't +// emit 'drain' before the write() consumer gets the 'false' return +// value, and has a chance to attach a 'drain' listener. +function onwriteDrain(stream, state) { + if (state.length === 0 && state.needDrain) { + state.needDrain = false; + stream.emit('drain'); + } +} + + +// if there's something in the buffer waiting, then process it +function clearBuffer(stream, state) { + state.bufferProcessing = true; + + if (stream._writev && state.buffer.length > 1) { + // Fast case, write everything using _writev() + var cbs = []; + for (var c = 0; c < state.buffer.length; c++) + cbs.push(state.buffer[c].callback); + + // count the one we are adding, as well. + // TODO(isaacs) clean this up + state.pendingcb++; + doWrite(stream, state, true, state.length, state.buffer, '', function(err) { + for (var i = 0; i < cbs.length; i++) { + state.pendingcb--; + cbs[i](err); + } + }); + + // Clear buffer + state.buffer = []; + } else { + // Slow case, write chunks one-by-one + for (var c = 0; c < state.buffer.length; c++) { + var entry = state.buffer[c]; + var chunk = entry.chunk; + var encoding = entry.encoding; + var cb = entry.callback; + var len = state.objectMode ? 1 : chunk.length; + + doWrite(stream, state, false, len, chunk, encoding, cb); + + // if we didn't call the onwrite immediately, then + // it means that we need to wait until it does. + // also, that means that the chunk and cb are currently + // being processed, so move the buffer counter past them. + if (state.writing) { + c++; + break; + } + } + + if (c < state.buffer.length) + state.buffer = state.buffer.slice(c); + else + state.buffer.length = 0; + } + + state.bufferProcessing = false; +} + +Writable.prototype._write = function(chunk, encoding, cb) { + cb(new Error('not implemented')); + +}; + +Writable.prototype._writev = null; + +Writable.prototype.end = function(chunk, encoding, cb) { + var state = this._writableState; + + if (util.isFunction(chunk)) { + cb = chunk; + chunk = null; + encoding = null; + } else if (util.isFunction(encoding)) { + cb = encoding; + encoding = null; + } + + if (!util.isNullOrUndefined(chunk)) + this.write(chunk, encoding); + + // .end() fully uncorks + if (state.corked) { + state.corked = 1; + this.uncork(); + } + + // ignore unnecessary end() calls. + if (!state.ending && !state.finished) + endWritable(this, state, cb); +}; + + +function needFinish(stream, state) { + return (state.ending && + state.length === 0 && + !state.finished && + !state.writing); +} + +function prefinish(stream, state) { + if (!state.prefinished) { + state.prefinished = true; + stream.emit('prefinish'); + } +} + +function finishMaybe(stream, state) { + var need = needFinish(stream, state); + if (need) { + if (state.pendingcb === 0) { + prefinish(stream, state); + state.finished = true; + stream.emit('finish'); + } else + prefinish(stream, state); + } + return need; +} + +function endWritable(stream, state, cb) { + state.ending = true; + finishMaybe(stream, state); + if (cb) { + if (state.finished) + process.nextTick(cb); + else + stream.once('finish', cb); + } + state.ended = true; +} + +}).call(this,require('_process')) +},{"./_stream_duplex":52,"_process":50,"buffer":43,"core-util-is":57,"inherits":69,"stream":62}],57:[function(require,module,exports){ +(function (Buffer){ +// Copyright Joyent, Inc. and other Node contributors. +// +// 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. + +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. +function isArray(ar) { + return Array.isArray(ar); +} +exports.isArray = isArray; + +function isBoolean(arg) { + return typeof arg === 'boolean'; +} +exports.isBoolean = isBoolean; + +function isNull(arg) { + return arg === null; +} +exports.isNull = isNull; + +function isNullOrUndefined(arg) { + return arg == null; +} +exports.isNullOrUndefined = isNullOrUndefined; + +function isNumber(arg) { + return typeof arg === 'number'; +} +exports.isNumber = isNumber; + +function isString(arg) { + return typeof arg === 'string'; +} +exports.isString = isString; + +function isSymbol(arg) { + return typeof arg === 'symbol'; +} +exports.isSymbol = isSymbol; + +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; + +function isRegExp(re) { + return isObject(re) && objectToString(re) === '[object RegExp]'; +} +exports.isRegExp = isRegExp; + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} +exports.isObject = isObject; + +function isDate(d) { + return isObject(d) && objectToString(d) === '[object Date]'; +} +exports.isDate = isDate; + +function isError(e) { + return isObject(e) && + (objectToString(e) === '[object Error]' || e instanceof Error); +} +exports.isError = isError; + +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; + +function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} +exports.isPrimitive = isPrimitive; + +function isBuffer(arg) { + return Buffer.isBuffer(arg); +} +exports.isBuffer = isBuffer; + +function objectToString(o) { + return Object.prototype.toString.call(o); +} +}).call(this,require("buffer").Buffer) +},{"buffer":43}],58:[function(require,module,exports){ +module.exports = require("./lib/_stream_passthrough.js") + +},{"./lib/_stream_passthrough.js":53}],59:[function(require,module,exports){ +exports = module.exports = require('./lib/_stream_readable.js'); +exports.Stream = require('stream'); +exports.Readable = exports; +exports.Writable = require('./lib/_stream_writable.js'); +exports.Duplex = require('./lib/_stream_duplex.js'); +exports.Transform = require('./lib/_stream_transform.js'); +exports.PassThrough = require('./lib/_stream_passthrough.js'); + +},{"./lib/_stream_duplex.js":52,"./lib/_stream_passthrough.js":53,"./lib/_stream_readable.js":54,"./lib/_stream_transform.js":55,"./lib/_stream_writable.js":56,"stream":62}],60:[function(require,module,exports){ +module.exports = require("./lib/_stream_transform.js") + +},{"./lib/_stream_transform.js":55}],61:[function(require,module,exports){ +module.exports = require("./lib/_stream_writable.js") + +},{"./lib/_stream_writable.js":56}],62:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// 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. + +module.exports = Stream; + +var EE = require('events').EventEmitter; +var inherits = require('inherits'); + +inherits(Stream, EE); +Stream.Readable = require('readable-stream/readable.js'); +Stream.Writable = require('readable-stream/writable.js'); +Stream.Duplex = require('readable-stream/duplex.js'); +Stream.Transform = require('readable-stream/transform.js'); +Stream.PassThrough = require('readable-stream/passthrough.js'); + +// Backwards-compat with node 0.4.x +Stream.Stream = Stream; + + + +// old-style streams. Note that the pipe method (the only relevant +// part of this class) is overridden in the Readable class. + +function Stream() { + EE.call(this); +} + +Stream.prototype.pipe = function(dest, options) { + var source = this; + + function ondata(chunk) { + if (dest.writable) { + if (false === dest.write(chunk) && source.pause) { + source.pause(); + } + } + } + + source.on('data', ondata); + + function ondrain() { + if (source.readable && source.resume) { + source.resume(); + } + } + + dest.on('drain', ondrain); + + // If the 'end' option is not supplied, dest.end() will be called when + // source gets the 'end' or 'close' events. Only dest.end() once. + if (!dest._isStdio && (!options || options.end !== false)) { + source.on('end', onend); + source.on('close', onclose); + } + + var didOnEnd = false; + function onend() { + if (didOnEnd) return; + didOnEnd = true; + + dest.end(); + } + + + function onclose() { + if (didOnEnd) return; + didOnEnd = true; + + if (typeof dest.destroy === 'function') dest.destroy(); + } + + // don't leave dangling pipes when there are errors. + function onerror(er) { + cleanup(); + if (EE.listenerCount(this, 'error') === 0) { + throw er; // Unhandled stream error in pipe. + } + } + + source.on('error', onerror); + dest.on('error', onerror); + + // remove all the event listeners that were added. + function cleanup() { + source.removeListener('data', ondata); + dest.removeListener('drain', ondrain); + + source.removeListener('end', onend); + source.removeListener('close', onclose); + + source.removeListener('error', onerror); + dest.removeListener('error', onerror); + + source.removeListener('end', cleanup); + source.removeListener('close', cleanup); + + dest.removeListener('close', cleanup); + } + + source.on('end', cleanup); + source.on('close', cleanup); + + dest.on('close', cleanup); + + dest.emit('pipe', source); + + // Allow for unix-like usage: A.pipe(B).pipe(C) + return dest; +}; + +},{"events":47,"inherits":69,"readable-stream/duplex.js":51,"readable-stream/passthrough.js":58,"readable-stream/readable.js":59,"readable-stream/transform.js":60,"readable-stream/writable.js":61}],63:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// 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. + +var Buffer = require('buffer').Buffer; + +var isBufferEncoding = Buffer.isEncoding + || function(encoding) { + switch (encoding && encoding.toLowerCase()) { + case 'hex': case 'utf8': case 'utf-8': case 'ascii': case 'binary': case 'base64': case 'ucs2': case 'ucs-2': case 'utf16le': case 'utf-16le': case 'raw': return true; + default: return false; + } + } + + +function assertEncoding(encoding) { + if (encoding && !isBufferEncoding(encoding)) { + throw new Error('Unknown encoding: ' + encoding); + } +} + +// StringDecoder provides an interface for efficiently splitting a series of +// buffers into a series of JS strings without breaking apart multi-byte +// characters. CESU-8 is handled as part of the UTF-8 encoding. +// +// @TODO Handling all encodings inside a single object makes it very difficult +// to reason about this code, so it should be split up in the future. +// @TODO There should be a utf8-strict encoding that rejects invalid UTF-8 code +// points as used by CESU-8. +var StringDecoder = exports.StringDecoder = function(encoding) { + this.encoding = (encoding || 'utf8').toLowerCase().replace(/[-_]/, ''); + assertEncoding(encoding); + switch (this.encoding) { + case 'utf8': + // CESU-8 represents each of Surrogate Pair by 3-bytes + this.surrogateSize = 3; + break; + case 'ucs2': + case 'utf16le': + // UTF-16 represents each of Surrogate Pair by 2-bytes + this.surrogateSize = 2; + this.detectIncompleteChar = utf16DetectIncompleteChar; + break; + case 'base64': + // Base-64 stores 3 bytes in 4 chars, and pads the remainder. + this.surrogateSize = 3; + this.detectIncompleteChar = base64DetectIncompleteChar; + break; + default: + this.write = passThroughWrite; + return; + } + + // Enough space to store all bytes of a single character. UTF-8 needs 4 + // bytes, but CESU-8 may require up to 6 (3 bytes per surrogate). + this.charBuffer = new Buffer(6); + // Number of bytes received for the current incomplete multi-byte character. + this.charReceived = 0; + // Number of bytes expected for the current incomplete multi-byte character. + this.charLength = 0; +}; + + +// write decodes the given buffer and returns it as JS string that is +// guaranteed to not contain any partial multi-byte characters. Any partial +// character found at the end of the buffer is buffered up, and will be +// returned when calling write again with the remaining bytes. +// +// Note: Converting a Buffer containing an orphan surrogate to a String +// currently works, but converting a String to a Buffer (via `new Buffer`, or +// Buffer#write) will replace incomplete surrogates with the unicode +// replacement character. See https://codereview.chromium.org/121173009/ . +StringDecoder.prototype.write = function(buffer) { + var charStr = ''; + // if our last write ended with an incomplete multibyte character + while (this.charLength) { + // determine how many remaining bytes this buffer has to offer for this char + var available = (buffer.length >= this.charLength - this.charReceived) ? + this.charLength - this.charReceived : + buffer.length; + + // add the new bytes to the char buffer + buffer.copy(this.charBuffer, this.charReceived, 0, available); + this.charReceived += available; + + if (this.charReceived < this.charLength) { + // still not enough chars in this buffer? wait for more ... + return ''; + } + + // remove bytes belonging to the current character from the buffer + buffer = buffer.slice(available, buffer.length); + + // get the character that was split + charStr = this.charBuffer.slice(0, this.charLength).toString(this.encoding); + + // CESU-8: lead surrogate (D800-DBFF) is also the incomplete character + var charCode = charStr.charCodeAt(charStr.length - 1); + if (charCode >= 0xD800 && charCode <= 0xDBFF) { + this.charLength += this.surrogateSize; + charStr = ''; + continue; + } + this.charReceived = this.charLength = 0; + + // if there are no more bytes in this buffer, just emit our char + if (buffer.length === 0) { + return charStr; + } + break; + } + + // determine and set charLength / charReceived + this.detectIncompleteChar(buffer); + + var end = buffer.length; + if (this.charLength) { + // buffer the incomplete character bytes we got + buffer.copy(this.charBuffer, 0, buffer.length - this.charReceived, end); + end -= this.charReceived; + } + + charStr += buffer.toString(this.encoding, 0, end); + + var end = charStr.length - 1; + var charCode = charStr.charCodeAt(end); + // CESU-8: lead surrogate (D800-DBFF) is also the incomplete character + if (charCode >= 0xD800 && charCode <= 0xDBFF) { + var size = this.surrogateSize; + this.charLength += size; + this.charReceived += size; + this.charBuffer.copy(this.charBuffer, size, 0, size); + buffer.copy(this.charBuffer, 0, 0, size); + return charStr.substring(0, end); + } + + // or just emit the charStr + return charStr; +}; + +// detectIncompleteChar determines if there is an incomplete UTF-8 character at +// the end of the given buffer. If so, it sets this.charLength to the byte +// length that character, and sets this.charReceived to the number of bytes +// that are available for this character. +StringDecoder.prototype.detectIncompleteChar = function(buffer) { + // determine how many bytes we have to check at the end of this buffer + var i = (buffer.length >= 3) ? 3 : buffer.length; + + // Figure out if one of the last i bytes of our buffer announces an + // incomplete char. + for (; i > 0; i--) { + var c = buffer[buffer.length - i]; + + // See http://en.wikipedia.org/wiki/UTF-8#Description + + // 110XXXXX + if (i == 1 && c >> 5 == 0x06) { + this.charLength = 2; + break; + } + + // 1110XXXX + if (i <= 2 && c >> 4 == 0x0E) { + this.charLength = 3; + break; + } + + // 11110XXX + if (i <= 3 && c >> 3 == 0x1E) { + this.charLength = 4; + break; + } + } + this.charReceived = i; +}; + +StringDecoder.prototype.end = function(buffer) { + var res = ''; + if (buffer && buffer.length) + res = this.write(buffer); + + if (this.charReceived) { + var cr = this.charReceived; + var buf = this.charBuffer; + var enc = this.encoding; + res += buf.slice(0, cr).toString(enc); + } + + return res; +}; + +function passThroughWrite(buffer) { + return buffer.toString(this.encoding); +} + +function utf16DetectIncompleteChar(buffer) { + this.charReceived = buffer.length % 2; + this.charLength = this.charReceived ? 2 : 0; +} + +function base64DetectIncompleteChar(buffer) { + this.charReceived = buffer.length % 3; + this.charLength = this.charReceived ? 3 : 0; +} + +},{"buffer":43}],64:[function(require,module,exports){ +module.exports = function isBuffer(arg) { + return arg && typeof arg === 'object' + && typeof arg.copy === 'function' + && typeof arg.fill === 'function' + && typeof arg.readUInt8 === 'function'; +} +},{}],65:[function(require,module,exports){ +(function (process,global){ +// Copyright Joyent, Inc. and other Node contributors. +// +// 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. + +var formatRegExp = /%[sdj%]/g; +exports.format = function(f) { + if (!isString(f)) { + var objects = []; + for (var i = 0; i < arguments.length; i++) { + objects.push(inspect(arguments[i])); + } + return objects.join(' '); + } + + var i = 1; + var args = arguments; + var len = args.length; + var str = String(f).replace(formatRegExp, function(x) { + if (x === '%%') return '%'; + if (i >= len) return x; + switch (x) { + case '%s': return String(args[i++]); + case '%d': return Number(args[i++]); + case '%j': + try { + return JSON.stringify(args[i++]); + } catch (_) { + return '[Circular]'; + } + default: + return x; + } + }); + for (var x = args[i]; i < len; x = args[++i]) { + if (isNull(x) || !isObject(x)) { + str += ' ' + x; + } else { + str += ' ' + inspect(x); + } + } + return str; +}; + + +// Mark that a method should not be used. +// Returns a modified function which warns once by default. +// If --no-deprecation is set, then it is a no-op. +exports.deprecate = function(fn, msg) { + // Allow for deprecating things in the process of starting up. + if (isUndefined(global.process)) { + return function() { + return exports.deprecate(fn, msg).apply(this, arguments); + }; + } + + if (process.noDeprecation === true) { + return fn; + } + + var warned = false; + function deprecated() { + if (!warned) { + if (process.throwDeprecation) { + throw new Error(msg); + } else if (process.traceDeprecation) { + console.trace(msg); + } else { + console.error(msg); + } + warned = true; + } + return fn.apply(this, arguments); + } + + return deprecated; +}; + + +var debugs = {}; +var debugEnviron; +exports.debuglog = function(set) { + if (isUndefined(debugEnviron)) + debugEnviron = process.env.NODE_DEBUG || ''; + set = set.toUpperCase(); + if (!debugs[set]) { + if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) { + var pid = process.pid; + debugs[set] = function() { + var msg = exports.format.apply(exports, arguments); + console.error('%s %d: %s', set, pid, msg); + }; + } else { + debugs[set] = function() {}; + } + } + return debugs[set]; +}; + + +/** + * Echos the value of a value. Trys to print the value out + * in the best way possible given the different types. + * + * @param {Object} obj The object to print out. + * @param {Object} opts Optional options object that alters the output. + */ +/* legacy: obj, showHidden, depth, colors*/ +function inspect(obj, opts) { + // default options + var ctx = { + seen: [], + stylize: stylizeNoColor + }; + // legacy... + if (arguments.length >= 3) ctx.depth = arguments[2]; + if (arguments.length >= 4) ctx.colors = arguments[3]; + if (isBoolean(opts)) { + // legacy... + ctx.showHidden = opts; + } else if (opts) { + // got an "options" object + exports._extend(ctx, opts); + } + // set default options + if (isUndefined(ctx.showHidden)) ctx.showHidden = false; + if (isUndefined(ctx.depth)) ctx.depth = 2; + if (isUndefined(ctx.colors)) ctx.colors = false; + if (isUndefined(ctx.customInspect)) ctx.customInspect = true; + if (ctx.colors) ctx.stylize = stylizeWithColor; + return formatValue(ctx, obj, ctx.depth); +} +exports.inspect = inspect; + + +// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics +inspect.colors = { + 'bold' : [1, 22], + 'italic' : [3, 23], + 'underline' : [4, 24], + 'inverse' : [7, 27], + 'white' : [37, 39], + 'grey' : [90, 39], + 'black' : [30, 39], + 'blue' : [34, 39], + 'cyan' : [36, 39], + 'green' : [32, 39], + 'magenta' : [35, 39], + 'red' : [31, 39], + 'yellow' : [33, 39] +}; + +// Don't use 'blue' not visible on cmd.exe +inspect.styles = { + 'special': 'cyan', + 'number': 'yellow', + 'boolean': 'yellow', + 'undefined': 'grey', + 'null': 'bold', + 'string': 'green', + 'date': 'magenta', + // "name": intentionally not styling + 'regexp': 'red' +}; + + +function stylizeWithColor(str, styleType) { + var style = inspect.styles[styleType]; + + if (style) { + return '\u001b[' + inspect.colors[style][0] + 'm' + str + + '\u001b[' + inspect.colors[style][1] + 'm'; + } else { + return str; + } +} + + +function stylizeNoColor(str, styleType) { + return str; +} + + +function arrayToHash(array) { + var hash = {}; + + array.forEach(function(val, idx) { + hash[val] = true; + }); + + return hash; +} + + +function formatValue(ctx, value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if (ctx.customInspect && + value && + isFunction(value.inspect) && + // Filter out the util module, it's inspect function is special + value.inspect !== exports.inspect && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + var ret = value.inspect(recurseTimes, ctx); + if (!isString(ret)) { + ret = formatValue(ctx, ret, recurseTimes); + } + return ret; + } + + // Primitive types cannot have properties + var primitive = formatPrimitive(ctx, value); + if (primitive) { + return primitive; + } + + // Look up the keys of the object. + var keys = Object.keys(value); + var visibleKeys = arrayToHash(keys); + + if (ctx.showHidden) { + keys = Object.getOwnPropertyNames(value); + } + + // IE doesn't make error fields non-enumerable + // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx + if (isError(value) + && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) { + return formatError(value); + } + + // Some type of object without properties can be shortcutted. + if (keys.length === 0) { + if (isFunction(value)) { + var name = value.name ? ': ' + value.name : ''; + return ctx.stylize('[Function' + name + ']', 'special'); + } + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } + if (isDate(value)) { + return ctx.stylize(Date.prototype.toString.call(value), 'date'); + } + if (isError(value)) { + return formatError(value); + } + } + + var base = '', array = false, braces = ['{', '}']; + + // Make Array say that they are Array + if (isArray(value)) { + array = true; + braces = ['[', ']']; + } + + // Make functions say that they are functions + if (isFunction(value)) { + var n = value.name ? ': ' + value.name : ''; + base = ' [Function' + n + ']'; + } + + // Make RegExps say that they are RegExps + if (isRegExp(value)) { + base = ' ' + RegExp.prototype.toString.call(value); + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + Date.prototype.toUTCString.call(value); + } + + // Make error with message first say the error + if (isError(value)) { + base = ' ' + formatError(value); + } + + if (keys.length === 0 && (!array || value.length == 0)) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } else { + return ctx.stylize('[Object]', 'special'); + } + } + + ctx.seen.push(value); + + var output; + if (array) { + output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); + } else { + output = keys.map(function(key) { + return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); + }); + } + + ctx.seen.pop(); + + return reduceToSingleString(output, base, braces); +} + + +function formatPrimitive(ctx, value) { + if (isUndefined(value)) + return ctx.stylize('undefined', 'undefined'); + if (isString(value)) { + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return ctx.stylize(simple, 'string'); + } + if (isNumber(value)) + return ctx.stylize('' + value, 'number'); + if (isBoolean(value)) + return ctx.stylize('' + value, 'boolean'); + // For some reason typeof null is "object", so special case here. + if (isNull(value)) + return ctx.stylize('null', 'null'); +} + + +function formatError(value) { + return '[' + Error.prototype.toString.call(value) + ']'; +} + + +function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { + var output = []; + for (var i = 0, l = value.length; i < l; ++i) { + if (hasOwnProperty(value, String(i))) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + String(i), true)); + } else { + output.push(''); + } + } + keys.forEach(function(key) { + if (!key.match(/^\d+$/)) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + key, true)); + } + }); + return output; +} + + +function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { + var name, str, desc; + desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; + if (desc.get) { + if (desc.set) { + str = ctx.stylize('[Getter/Setter]', 'special'); + } else { + str = ctx.stylize('[Getter]', 'special'); + } + } else { + if (desc.set) { + str = ctx.stylize('[Setter]', 'special'); + } + } + if (!hasOwnProperty(visibleKeys, key)) { + name = '[' + key + ']'; + } + if (!str) { + if (ctx.seen.indexOf(desc.value) < 0) { + if (isNull(recurseTimes)) { + str = formatValue(ctx, desc.value, null); + } else { + str = formatValue(ctx, desc.value, recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (array) { + str = str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n').substr(2); + } else { + str = '\n' + str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n'); + } + } + } else { + str = ctx.stylize('[Circular]', 'special'); + } + } + if (isUndefined(name)) { + if (array && key.match(/^\d+$/)) { + return str; + } + name = JSON.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = ctx.stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = ctx.stylize(name, 'string'); + } + } + + return name + ': ' + str; +} + + +function reduceToSingleString(output, base, braces) { + var numLinesEst = 0; + var length = output.reduce(function(prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; + }, 0); + + if (length > 60) { + return braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; + } + + return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; +} + + +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. +function isArray(ar) { + return Array.isArray(ar); +} +exports.isArray = isArray; + +function isBoolean(arg) { + return typeof arg === 'boolean'; +} +exports.isBoolean = isBoolean; + +function isNull(arg) { + return arg === null; +} +exports.isNull = isNull; + +function isNullOrUndefined(arg) { + return arg == null; +} +exports.isNullOrUndefined = isNullOrUndefined; + +function isNumber(arg) { + return typeof arg === 'number'; +} +exports.isNumber = isNumber; + +function isString(arg) { + return typeof arg === 'string'; +} +exports.isString = isString; + +function isSymbol(arg) { + return typeof arg === 'symbol'; +} +exports.isSymbol = isSymbol; + +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; + +function isRegExp(re) { + return isObject(re) && objectToString(re) === '[object RegExp]'; +} +exports.isRegExp = isRegExp; + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} +exports.isObject = isObject; + +function isDate(d) { + return isObject(d) && objectToString(d) === '[object Date]'; +} +exports.isDate = isDate; + +function isError(e) { + return isObject(e) && + (objectToString(e) === '[object Error]' || e instanceof Error); +} +exports.isError = isError; + +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; + +function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} +exports.isPrimitive = isPrimitive; + +exports.isBuffer = require('./support/isBuffer'); + +function objectToString(o) { + return Object.prototype.toString.call(o); +} + + +function pad(n) { + return n < 10 ? '0' + n.toString(10) : n.toString(10); +} + + +var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', + 'Oct', 'Nov', 'Dec']; + +// 26 Feb 16:19:34 +function timestamp() { + var d = new Date(); + var time = [pad(d.getHours()), + pad(d.getMinutes()), + pad(d.getSeconds())].join(':'); + return [d.getDate(), months[d.getMonth()], time].join(' '); +} + + +// log is just a thin wrapper to console.log that prepends a timestamp +exports.log = function() { + console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments)); +}; + + +/** + * Inherit the prototype methods from one constructor into another. + * + * The Function.prototype.inherits from lang.js rewritten as a standalone + * function (not on Function.prototype). NOTE: If this file is to be loaded + * during bootstrapping this function needs to be rewritten using some native + * functions as prototype setup using normal JavaScript does not work as + * expected during bootstrapping (see mirror.js in r114903). + * + * @param {function} ctor Constructor function which needs to inherit the + * prototype. + * @param {function} superCtor Constructor function to inherit prototype from. + */ +exports.inherits = require('inherits'); + +exports._extend = function(origin, add) { + // Don't do anything if add isn't an object + if (!add || !isObject(add)) return origin; + + var keys = Object.keys(add); + var i = keys.length; + while (i--) { + origin[keys[i]] = add[keys[i]]; + } + return origin; +}; + +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./support/isBuffer":64,"_process":50,"inherits":69}],66:[function(require,module,exports){ +/* See LICENSE file for terms of use */ + +/* + * Text diff implementation. + * + * This library supports the following APIS: + * JsDiff.diffChars: Character by character diff + * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace + * JsDiff.diffLines: Line based diff + * + * JsDiff.diffCss: Diff targeted at CSS content + * + * These methods are based on the implementation proposed in + * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986). + * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927 + */ +(function(global, undefined) { + var objectPrototypeToString = Object.prototype.toString; + + /*istanbul ignore next*/ + function map(arr, mapper, that) { + if (Array.prototype.map) { + return Array.prototype.map.call(arr, mapper, that); + } + + var other = new Array(arr.length); + + for (var i = 0, n = arr.length; i < n; i++) { + other[i] = mapper.call(that, arr[i], i, arr); + } + return other; + } + function clonePath(path) { + return { newPos: path.newPos, components: path.components.slice(0) }; + } + function removeEmpty(array) { + var ret = []; + for (var i = 0; i < array.length; i++) { + if (array[i]) { + ret.push(array[i]); + } + } + return ret; + } + function escapeHTML(s) { + var n = s; + n = n.replace(/&/g, '&'); + n = n.replace(//g, '>'); + n = n.replace(/"/g, '"'); + + return n; + } + + // This function handles the presence of circular references by bailing out when encountering an + // object that is already on the "stack" of items being processed. + function canonicalize(obj, stack, replacementStack) { + stack = stack || []; + replacementStack = replacementStack || []; + + var i; + + for (i = 0; i < stack.length; i += 1) { + if (stack[i] === obj) { + return replacementStack[i]; + } + } + + var canonicalizedObj; + + if ('[object Array]' === objectPrototypeToString.call(obj)) { + stack.push(obj); + canonicalizedObj = new Array(obj.length); + replacementStack.push(canonicalizedObj); + for (i = 0; i < obj.length; i += 1) { + canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack); + } + stack.pop(); + replacementStack.pop(); + } else if (typeof obj === 'object' && obj !== null) { + stack.push(obj); + canonicalizedObj = {}; + replacementStack.push(canonicalizedObj); + var sortedKeys = [], + key; + for (key in obj) { + sortedKeys.push(key); + } + sortedKeys.sort(); + for (i = 0; i < sortedKeys.length; i += 1) { + key = sortedKeys[i]; + canonicalizedObj[key] = canonicalize(obj[key], stack, replacementStack); + } + stack.pop(); + replacementStack.pop(); + } else { + canonicalizedObj = obj; + } + return canonicalizedObj; + } + + function buildValues(components, newString, oldString, useLongestToken) { + var componentPos = 0, + componentLen = components.length, + newPos = 0, + oldPos = 0; + + for (; componentPos < componentLen; componentPos++) { + var component = components[componentPos]; + if (!component.removed) { + if (!component.added && useLongestToken) { + var value = newString.slice(newPos, newPos + component.count); + value = map(value, function(value, i) { + var oldValue = oldString[oldPos + i]; + return oldValue.length > value.length ? oldValue : value; + }); + + component.value = value.join(''); + } else { + component.value = newString.slice(newPos, newPos + component.count).join(''); + } + newPos += component.count; + + // Common case + if (!component.added) { + oldPos += component.count; + } + } else { + component.value = oldString.slice(oldPos, oldPos + component.count).join(''); + oldPos += component.count; + + // Reverse add and remove so removes are output first to match common convention + // The diffing algorithm is tied to add then remove output and this is the simplest + // route to get the desired output with minimal overhead. + if (componentPos && components[componentPos - 1].added) { + var tmp = components[componentPos - 1]; + components[componentPos - 1] = components[componentPos]; + components[componentPos] = tmp; + } + } + } + + return components; + } + + function Diff(ignoreWhitespace) { + this.ignoreWhitespace = ignoreWhitespace; + } + Diff.prototype = { + diff: function(oldString, newString, callback) { + var self = this; + + function done(value) { + if (callback) { + setTimeout(function() { callback(undefined, value); }, 0); + return true; + } else { + return value; + } + } + + // Handle the identity case (this is due to unrolling editLength == 0 + if (newString === oldString) { + return done([{ value: newString }]); + } + if (!newString) { + return done([{ value: oldString, removed: true }]); + } + if (!oldString) { + return done([{ value: newString, added: true }]); + } + + newString = this.tokenize(newString); + oldString = this.tokenize(oldString); + + var newLen = newString.length, oldLen = oldString.length; + var editLength = 1; + var maxEditLength = newLen + oldLen; + var bestPath = [{ newPos: -1, components: [] }]; + + // Seed editLength = 0, i.e. the content starts with the same values + var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0); + if (bestPath[0].newPos + 1 >= newLen && oldPos + 1 >= oldLen) { + // Identity per the equality and tokenizer + return done([{value: newString.join('')}]); + } + + // Main worker method. checks all permutations of a given edit length for acceptance. + function execEditLength() { + for (var diagonalPath = -1 * editLength; diagonalPath <= editLength; diagonalPath += 2) { + var basePath; + var addPath = bestPath[diagonalPath - 1], + removePath = bestPath[diagonalPath + 1], + oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; + if (addPath) { + // No one else is going to attempt to use this value, clear it + bestPath[diagonalPath - 1] = undefined; + } + + var canAdd = addPath && addPath.newPos + 1 < newLen, + canRemove = removePath && 0 <= oldPos && oldPos < oldLen; + if (!canAdd && !canRemove) { + // If this path is a terminal then prune + bestPath[diagonalPath] = undefined; + continue; + } + + // Select the diagonal that we want to branch from. We select the prior + // path whose position in the new string is the farthest from the origin + // and does not pass the bounds of the diff graph + if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) { + basePath = clonePath(removePath); + self.pushComponent(basePath.components, undefined, true); + } else { + basePath = addPath; // No need to clone, we've pulled it from the list + basePath.newPos++; + self.pushComponent(basePath.components, true, undefined); + } + + oldPos = self.extractCommon(basePath, newString, oldString, diagonalPath); + + // If we have hit the end of both strings, then we are done + if (basePath.newPos + 1 >= newLen && oldPos + 1 >= oldLen) { + return done(buildValues(basePath.components, newString, oldString, self.useLongestToken)); + } else { + // Otherwise track this path as a potential candidate and continue. + bestPath[diagonalPath] = basePath; + } + } + + editLength++; + } + + // Performs the length of edit iteration. Is a bit fugly as this has to support the + // sync and async mode which is never fun. Loops over execEditLength until a value + // is produced. + if (callback) { + (function exec() { + setTimeout(function() { + // This should not happen, but we want to be safe. + /*istanbul ignore next */ + if (editLength > maxEditLength) { + return callback(); + } + + if (!execEditLength()) { + exec(); + } + }, 0); + }()); + } else { + while (editLength <= maxEditLength) { + var ret = execEditLength(); + if (ret) { + return ret; + } + } + } + }, + + pushComponent: function(components, added, removed) { + var last = components[components.length - 1]; + if (last && last.added === added && last.removed === removed) { + // We need to clone here as the component clone operation is just + // as shallow array clone + components[components.length - 1] = {count: last.count + 1, added: added, removed: removed }; + } else { + components.push({count: 1, added: added, removed: removed }); + } + }, + extractCommon: function(basePath, newString, oldString, diagonalPath) { + var newLen = newString.length, + oldLen = oldString.length, + newPos = basePath.newPos, + oldPos = newPos - diagonalPath, + + commonCount = 0; + while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(newString[newPos + 1], oldString[oldPos + 1])) { + newPos++; + oldPos++; + commonCount++; + } + + if (commonCount) { + basePath.components.push({count: commonCount}); + } + + basePath.newPos = newPos; + return oldPos; + }, + + equals: function(left, right) { + var reWhitespace = /\S/; + return left === right || (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)); + }, + tokenize: function(value) { + return value.split(''); + } + }; + + var CharDiff = new Diff(); + + var WordDiff = new Diff(true); + var WordWithSpaceDiff = new Diff(); + WordDiff.tokenize = WordWithSpaceDiff.tokenize = function(value) { + return removeEmpty(value.split(/(\s+|\b)/)); + }; + + var CssDiff = new Diff(true); + CssDiff.tokenize = function(value) { + return removeEmpty(value.split(/([{}:;,]|\s+)/)); + }; + + var LineDiff = new Diff(); + + var TrimmedLineDiff = new Diff(); + TrimmedLineDiff.ignoreTrim = true; + + LineDiff.tokenize = TrimmedLineDiff.tokenize = function(value) { + var retLines = [], + lines = value.split(/^/m); + for (var i = 0; i < lines.length; i++) { + var line = lines[i], + lastLine = lines[i - 1], + lastLineLastChar = lastLine && lastLine[lastLine.length - 1]; + + // Merge lines that may contain windows new lines + if (line === '\n' && lastLineLastChar === '\r') { + retLines[retLines.length - 1] = retLines[retLines.length - 1].slice(0, -1) + '\r\n'; + } else { + if (this.ignoreTrim) { + line = line.trim(); + // add a newline unless this is the last line. + if (i < lines.length - 1) { + line += '\n'; + } + } + retLines.push(line); + } + } + + return retLines; + }; + + var PatchDiff = new Diff(); + PatchDiff.tokenize = function(value) { + var ret = [], + linesAndNewlines = value.split(/(\n|\r\n)/); + + // Ignore the final empty token that occurs if the string ends with a new line + if (!linesAndNewlines[linesAndNewlines.length - 1]) { + linesAndNewlines.pop(); + } + + // Merge the content and line separators into single tokens + for (var i = 0; i < linesAndNewlines.length; i++) { + var line = linesAndNewlines[i]; + + if (i % 2) { + ret[ret.length - 1] += line; + } else { + ret.push(line); + } + } + return ret; + }; + + var SentenceDiff = new Diff(); + SentenceDiff.tokenize = function(value) { + return removeEmpty(value.split(/(\S.+?[.!?])(?=\s+|$)/)); + }; + + var JsonDiff = new Diff(); + // Discriminate between two lines of pretty-printed, serialized JSON where one of them has a + // dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output: + JsonDiff.useLongestToken = true; + JsonDiff.tokenize = LineDiff.tokenize; + JsonDiff.equals = function(left, right) { + return LineDiff.equals(left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1')); + }; + + var JsDiff = { + Diff: Diff, + + diffChars: function(oldStr, newStr, callback) { return CharDiff.diff(oldStr, newStr, callback); }, + diffWords: function(oldStr, newStr, callback) { return WordDiff.diff(oldStr, newStr, callback); }, + diffWordsWithSpace: function(oldStr, newStr, callback) { return WordWithSpaceDiff.diff(oldStr, newStr, callback); }, + diffLines: function(oldStr, newStr, callback) { return LineDiff.diff(oldStr, newStr, callback); }, + diffTrimmedLines: function(oldStr, newStr, callback) { return TrimmedLineDiff.diff(oldStr, newStr, callback); }, + + diffSentences: function(oldStr, newStr, callback) { return SentenceDiff.diff(oldStr, newStr, callback); }, + + diffCss: function(oldStr, newStr, callback) { return CssDiff.diff(oldStr, newStr, callback); }, + diffJson: function(oldObj, newObj, callback) { + return JsonDiff.diff( + typeof oldObj === 'string' ? oldObj : JSON.stringify(canonicalize(oldObj), undefined, ' '), + typeof newObj === 'string' ? newObj : JSON.stringify(canonicalize(newObj), undefined, ' '), + callback + ); + }, + + createTwoFilesPatch: function(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader) { + var ret = []; + + if (oldFileName == newFileName) { + ret.push('Index: ' + oldFileName); + } + ret.push('==================================================================='); + ret.push('--- ' + oldFileName + (typeof oldHeader === 'undefined' ? '' : '\t' + oldHeader)); + ret.push('+++ ' + newFileName + (typeof newHeader === 'undefined' ? '' : '\t' + newHeader)); + + var diff = PatchDiff.diff(oldStr, newStr); + diff.push({value: '', lines: []}); // Append an empty value to make cleanup easier + + // Formats a given set of lines for printing as context lines in a patch + function contextLines(lines) { + return map(lines, function(entry) { return ' ' + entry; }); + } + + // Outputs the no newline at end of file warning if needed + function eofNL(curRange, i, current) { + var last = diff[diff.length - 2], + isLast = i === diff.length - 2, + isLastOfType = i === diff.length - 3 && current.added !== last.added; + + // Figure out if this is the last line for the given file and missing NL + if (!(/\n$/.test(current.value)) && (isLast || isLastOfType)) { + curRange.push('\\ No newline at end of file'); + } + } + + var oldRangeStart = 0, newRangeStart = 0, curRange = [], + oldLine = 1, newLine = 1; + for (var i = 0; i < diff.length; i++) { + var current = diff[i], + lines = current.lines || current.value.replace(/\n$/, '').split('\n'); + current.lines = lines; + + if (current.added || current.removed) { + // If we have previous context, start with that + if (!oldRangeStart) { + var prev = diff[i - 1]; + oldRangeStart = oldLine; + newRangeStart = newLine; + + if (prev) { + curRange = contextLines(prev.lines.slice(-4)); + oldRangeStart -= curRange.length; + newRangeStart -= curRange.length; + } + } + + // Output our changes + curRange.push.apply(curRange, map(lines, function(entry) { + return (current.added ? '+' : '-') + entry; + })); + eofNL(curRange, i, current); + + // Track the updated file position + if (current.added) { + newLine += lines.length; + } else { + oldLine += lines.length; + } + } else { + // Identical context lines. Track line changes + if (oldRangeStart) { + // Close out any changes that have been output (or join overlapping) + if (lines.length <= 8 && i < diff.length - 2) { + // Overlapping + curRange.push.apply(curRange, contextLines(lines)); + } else { + // end the range and output + var contextSize = Math.min(lines.length, 4); + ret.push( + '@@ -' + oldRangeStart + ',' + (oldLine - oldRangeStart + contextSize) + + ' +' + newRangeStart + ',' + (newLine - newRangeStart + contextSize) + + ' @@'); + ret.push.apply(ret, curRange); + ret.push.apply(ret, contextLines(lines.slice(0, contextSize))); + if (lines.length <= 4) { + eofNL(ret, i, current); + } + + oldRangeStart = 0; + newRangeStart = 0; + curRange = []; + } + } + oldLine += lines.length; + newLine += lines.length; + } + } + + return ret.join('\n') + '\n'; + }, + + createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) { + return JsDiff.createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader); + }, + + applyPatch: function(oldStr, uniDiff) { + var diffstr = uniDiff.split('\n'), + hunks = [], + i = 0, + remEOFNL = false, + addEOFNL = false; + + // Skip to the first change hunk + while (i < diffstr.length && !(/^@@/.test(diffstr[i]))) { + i++; + } + + // Parse the unified diff + for (; i < diffstr.length; i++) { + if (diffstr[i][0] === '@') { + var chnukHeader = diffstr[i].split(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/); + hunks.unshift({ + start: chnukHeader[3], + oldlength: +chnukHeader[2], + removed: [], + newlength: chnukHeader[4], + added: [] + }); + } else if (diffstr[i][0] === '+') { + hunks[0].added.push(diffstr[i].substr(1)); + } else if (diffstr[i][0] === '-') { + hunks[0].removed.push(diffstr[i].substr(1)); + } else if (diffstr[i][0] === ' ') { + hunks[0].added.push(diffstr[i].substr(1)); + hunks[0].removed.push(diffstr[i].substr(1)); + } else if (diffstr[i][0] === '\\') { + if (diffstr[i - 1][0] === '+') { + remEOFNL = true; + } else if (diffstr[i - 1][0] === '-') { + addEOFNL = true; + } + } + } + + // Apply the diff to the input + var lines = oldStr.split('\n'); + for (i = hunks.length - 1; i >= 0; i--) { + var hunk = hunks[i]; + // Sanity check the input string. Bail if we don't match. + for (var j = 0; j < hunk.oldlength; j++) { + if (lines[hunk.start - 1 + j] !== hunk.removed[j]) { + return false; + } + } + Array.prototype.splice.apply(lines, [hunk.start - 1, hunk.oldlength].concat(hunk.added)); + } + + // Handle EOFNL insertion/removal + if (remEOFNL) { + while (!lines[lines.length - 1]) { + lines.pop(); + } + } else if (addEOFNL) { + lines.push(''); + } + return lines.join('\n'); + }, + + convertChangesToXML: function(changes) { + var ret = []; + for (var i = 0; i < changes.length; i++) { + var change = changes[i]; + if (change.added) { + ret.push(''); + } else if (change.removed) { + ret.push(''); + } + + ret.push(escapeHTML(change.value)); + + if (change.added) { + ret.push(''); + } else if (change.removed) { + ret.push(''); + } + } + return ret.join(''); + }, + + // See: http://code.google.com/p/google-diff-match-patch/wiki/API + convertChangesToDMP: function(changes) { + var ret = [], + change, + operation; + for (var i = 0; i < changes.length; i++) { + change = changes[i]; + if (change.added) { + operation = 1; + } else if (change.removed) { + operation = -1; + } else { + operation = 0; + } + + ret.push([operation, change.value]); + } + return ret; + }, + + canonicalize: canonicalize + }; + + /*istanbul ignore next */ + /*global module */ + if (typeof module !== 'undefined' && module.exports) { + module.exports = JsDiff; + } else if (typeof define === 'function' && define.amd) { + /*global define */ + define([], function() { return JsDiff; }); + } else if (typeof global.JsDiff === 'undefined') { + global.JsDiff = JsDiff; + } +}(this)); + +},{}],67:[function(require,module,exports){ +'use strict'; + +var matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g; + +module.exports = function (str) { + if (typeof str !== 'string') { + throw new TypeError('Expected a string'); + } + + return str.replace(matchOperatorsRe, '\\$&'); +}; + +},{}],68:[function(require,module,exports){ +(function (process){ +// Growl - Copyright TJ Holowaychuk (MIT Licensed) + +/** + * Module dependencies. + */ + +var exec = require('child_process').exec + , fs = require('fs') + , path = require('path') + , exists = fs.existsSync || path.existsSync + , os = require('os') + , quote = JSON.stringify + , cmd; + +function which(name) { + var paths = process.env.PATH.split(':'); + var loc; + + for (var i = 0, len = paths.length; i < len; ++i) { + loc = path.join(paths[i], name); + if (exists(loc)) return loc; + } +} + +switch(os.type()) { + case 'Darwin': + if (which('terminal-notifier')) { + cmd = { + type: "Darwin-NotificationCenter" + , pkg: "terminal-notifier" + , msg: '-message' + , title: '-title' + , subtitle: '-subtitle' + , priority: { + cmd: '-execute' + , range: [] + } + }; + } else { + cmd = { + type: "Darwin-Growl" + , pkg: "growlnotify" + , msg: '-m' + , sticky: '--sticky' + , priority: { + cmd: '--priority' + , range: [ + -2 + , -1 + , 0 + , 1 + , 2 + , "Very Low" + , "Moderate" + , "Normal" + , "High" + , "Emergency" + ] + } + }; + } + break; + case 'Linux': + cmd = { + type: "Linux" + , pkg: "notify-send" + , msg: '' + , sticky: '-t 0' + , icon: '-i' + , priority: { + cmd: '-u' + , range: [ + "low" + , "normal" + , "critical" + ] + } + }; + break; + case 'Windows_NT': + cmd = { + type: "Windows" + , pkg: "growlnotify" + , msg: '' + , sticky: '/s:true' + , title: '/t:' + , icon: '/i:' + , priority: { + cmd: '/p:' + , range: [ + -2 + , -1 + , 0 + , 1 + , 2 + ] + } + }; + break; +} + +/** + * Expose `growl`. + */ + +exports = module.exports = growl; + +/** + * Node-growl version. + */ + +exports.version = '1.4.1' + +/** + * Send growl notification _msg_ with _options_. + * + * Options: + * + * - title Notification title + * - sticky Make the notification stick (defaults to false) + * - priority Specify an int or named key (default is 0) + * - name Application name (defaults to growlnotify) + * - image + * - path to an icon sets --iconpath + * - path to an image sets --image + * - capitalized word sets --appIcon + * - filename uses extname as --icon + * - otherwise treated as --icon + * + * Examples: + * + * growl('New email') + * growl('5 new emails', { title: 'Thunderbird' }) + * growl('Email sent', function(){ + * // ... notification sent + * }) + * + * @param {string} msg + * @param {object} options + * @param {function} fn + * @api public + */ + +function growl(msg, options, fn) { + var image + , args + , options = options || {} + , fn = fn || function(){}; + + // noop + if (!cmd) return fn(new Error('growl not supported on this platform')); + args = [cmd.pkg]; + + // image + if (image = options.image) { + switch(cmd.type) { + case 'Darwin-Growl': + var flag, ext = path.extname(image).substr(1) + flag = flag || ext == 'icns' && 'iconpath' + flag = flag || /^[A-Z]/.test(image) && 'appIcon' + flag = flag || /^png|gif|jpe?g$/.test(ext) && 'image' + flag = flag || ext && (image = ext) && 'icon' + flag = flag || 'icon' + args.push('--' + flag, quote(image)) + break; + case 'Linux': + args.push(cmd.icon, quote(image)); + // libnotify defaults to sticky, set a hint for transient notifications + if (!options.sticky) args.push('--hint=int:transient:1'); + break; + case 'Windows': + args.push(cmd.icon + quote(image)); + break; + } + } + + // sticky + if (options.sticky) args.push(cmd.sticky); + + // priority + if (options.priority) { + var priority = options.priority + ''; + var checkindexOf = cmd.priority.range.indexOf(priority); + if (~cmd.priority.range.indexOf(priority)) { + args.push(cmd.priority, options.priority); + } + } + + // name + if (options.name && cmd.type === "Darwin-Growl") { + args.push('--name', options.name); + } + + switch(cmd.type) { + case 'Darwin-Growl': + args.push(cmd.msg); + args.push(quote(msg)); + if (options.title) args.push(quote(options.title)); + break; + case 'Darwin-NotificationCenter': + args.push(cmd.msg); + args.push(quote(msg)); + if (options.title) { + args.push(cmd.title); + args.push(quote(options.title)); + } + if (options.subtitle) { + args.push(cmd.subtitle); + args.push(quote(options.subtitle)); + } + break; + case 'Darwin-Growl': + args.push(cmd.msg); + args.push(quote(msg)); + if (options.title) args.push(quote(options.title)); + break; + case 'Linux': + if (options.title) { + args.push(quote(options.title)); + args.push(cmd.msg); + args.push(quote(msg)); + } else { + args.push(quote(msg)); + } + break; + case 'Windows': + args.push(quote(msg)); + if (options.title) args.push(cmd.title + quote(options.title)); + break; + } + + // execute + exec(args.join(' '), fn); +}; + +}).call(this,require('_process')) +},{"_process":50,"child_process":41,"fs":41,"os":49,"path":41}],69:[function(require,module,exports){ +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } +} + +},{}],70:[function(require,module,exports){ +/** + * lodash 3.1.1 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.8.3 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseAssign = require('lodash._baseassign'), + baseCreate = require('lodash._basecreate'), + isIterateeCall = require('lodash._isiterateecall'); + +/** + * Creates an object that inherits from the given `prototype` object. If a + * `properties` object is provided its own enumerable properties are assigned + * to the created object. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} prototype The object to inherit from. + * @param {Object} [properties] The properties to assign to the object. + * @param- {Object} [guard] Enables use as a callback for functions like `_.map`. + * @returns {Object} Returns the new object. + * @example + * + * function Shape() { + * this.x = 0; + * this.y = 0; + * } + * + * function Circle() { + * Shape.call(this); + * } + * + * Circle.prototype = _.create(Shape.prototype, { + * 'constructor': Circle + * }); + * + * var circle = new Circle; + * circle instanceof Circle; + * // => true + * + * circle instanceof Shape; + * // => true + */ +function create(prototype, properties, guard) { + var result = baseCreate(prototype); + if (guard && isIterateeCall(prototype, properties, guard)) { + properties = undefined; + } + return properties ? baseAssign(result, properties) : result; +} + +module.exports = create; + +},{"lodash._baseassign":71,"lodash._basecreate":77,"lodash._isiterateecall":78}],71:[function(require,module,exports){ +/** + * lodash 3.2.0 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.8.3 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var baseCopy = require('lodash._basecopy'), + keys = require('lodash.keys'); + +/** + * The base implementation of `_.assign` without support for argument juggling, + * multiple sources, and `customizer` functions. + * + * @private + * @param {Object} object The destination object. + * @param {Object} source The source object. + * @returns {Object} Returns `object`. + */ +function baseAssign(object, source) { + return source == null + ? object + : baseCopy(source, keys(source), object); +} + +module.exports = baseAssign; + +},{"lodash._basecopy":72,"lodash.keys":73}],72:[function(require,module,exports){ +/** + * lodash 3.0.1 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.8.3 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** + * Copies properties of `source` to `object`. + * + * @private + * @param {Object} source The object to copy properties from. + * @param {Array} props The property names to copy. + * @param {Object} [object={}] The object to copy properties to. + * @returns {Object} Returns `object`. + */ +function baseCopy(source, props, object) { + object || (object = {}); + + var index = -1, + length = props.length; + + while (++index < length) { + var key = props[index]; + object[key] = source[key]; + } + return object; +} + +module.exports = baseCopy; + +},{}],73:[function(require,module,exports){ +/** + * lodash 3.1.2 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.8.3 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var getNative = require('lodash._getnative'), + isArguments = require('lodash.isarguments'), + isArray = require('lodash.isarray'); + +/** Used to detect unsigned integer values. */ +var reIsUint = /^\d+$/; + +/** Used for native method references. */ +var objectProto = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty = objectProto.hasOwnProperty; + +/* Native method references for those with the same name as other `lodash` methods. */ +var nativeKeys = getNative(Object, 'keys'); + +/** + * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer) + * of an array-like value. + */ +var MAX_SAFE_INTEGER = 9007199254740991; + +/** + * The base implementation of `_.property` without support for deep paths. + * + * @private + * @param {string} key The key of the property to get. + * @returns {Function} Returns the new function. + */ +function baseProperty(key) { + return function(object) { + return object == null ? undefined : object[key]; + }; +} + +/** + * Gets the "length" property value of `object`. + * + * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792) + * that affects Safari on at least iOS 8.1-8.3 ARM64. + * + * @private + * @param {Object} object The object to query. + * @returns {*} Returns the "length" value. + */ +var getLength = baseProperty('length'); + +/** + * Checks if `value` is array-like. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is array-like, else `false`. + */ +function isArrayLike(value) { + return value != null && isLength(getLength(value)); +} + +/** + * Checks if `value` is a valid array-like index. + * + * @private + * @param {*} value The value to check. + * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. + * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. + */ +function isIndex(value, length) { + value = (typeof value == 'number' || reIsUint.test(value)) ? +value : -1; + length = length == null ? MAX_SAFE_INTEGER : length; + return value > -1 && value % 1 == 0 && value < length; +} + +/** + * Checks if `value` is a valid array-like length. + * + * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength). + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. + */ +function isLength(value) { + return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; +} + +/** + * A fallback implementation of `Object.keys` which creates an array of the + * own enumerable property names of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + */ +function shimKeys(object) { + var props = keysIn(object), + propsLength = props.length, + length = propsLength && object.length; + + var allowIndexes = !!length && isLength(length) && + (isArray(object) || isArguments(object)); + + var index = -1, + result = []; + + while (++index < propsLength) { + var key = props[index]; + if ((allowIndexes && isIndex(key, length)) || hasOwnProperty.call(object, key)) { + result.push(key); + } + } + return result; +} + +/** + * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(1); + * // => false + */ +function isObject(value) { + // Avoid a V8 JIT bug in Chrome 19-20. + // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. + var type = typeof value; + return !!value && (type == 'object' || type == 'function'); +} + +/** + * Creates an array of the own enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. See the + * [ES spec](http://ecma-international.org/ecma-262/6.0/#sec-object.keys) + * for more details. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keys(new Foo); + * // => ['a', 'b'] (iteration order is not guaranteed) + * + * _.keys('hi'); + * // => ['0', '1'] + */ +var keys = !nativeKeys ? shimKeys : function(object) { + var Ctor = object == null ? undefined : object.constructor; + if ((typeof Ctor == 'function' && Ctor.prototype === object) || + (typeof object != 'function' && isArrayLike(object))) { + return shimKeys(object); + } + return isObject(object) ? nativeKeys(object) : []; +}; + +/** + * Creates an array of the own and inherited enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keysIn(new Foo); + * // => ['a', 'b', 'c'] (iteration order is not guaranteed) + */ +function keysIn(object) { + if (object == null) { + return []; + } + if (!isObject(object)) { + object = Object(object); + } + var length = object.length; + length = (length && isLength(length) && + (isArray(object) || isArguments(object)) && length) || 0; + + var Ctor = object.constructor, + index = -1, + isProto = typeof Ctor == 'function' && Ctor.prototype === object, + result = Array(length), + skipIndexes = length > 0; + + while (++index < length) { + result[index] = (index + ''); + } + for (var key in object) { + if (!(skipIndexes && isIndex(key, length)) && + !(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) { + result.push(key); + } + } + return result; +} + +module.exports = keys; + +},{"lodash._getnative":74,"lodash.isarguments":75,"lodash.isarray":76}],74:[function(require,module,exports){ +/** + * lodash 3.9.1 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.8.3 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** `Object#toString` result references. */ +var funcTag = '[object Function]'; + +/** Used to detect host constructors (Safari > 5). */ +var reIsHostCtor = /^\[object .+?Constructor\]$/; + +/** + * Checks if `value` is object-like. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + */ +function isObjectLike(value) { + return !!value && typeof value == 'object'; +} + +/** Used for native method references. */ +var objectProto = Object.prototype; + +/** Used to resolve the decompiled source of functions. */ +var fnToString = Function.prototype.toString; + +/** Used to check objects for own properties. */ +var hasOwnProperty = objectProto.hasOwnProperty; + +/** + * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) + * of values. + */ +var objToString = objectProto.toString; + +/** Used to detect if a method is native. */ +var reIsNative = RegExp('^' + + fnToString.call(hasOwnProperty).replace(/[\\^$.*+?()[\]{}|]/g, '\\$&') + .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' +); + +/** + * Gets the native function at `key` of `object`. + * + * @private + * @param {Object} object The object to query. + * @param {string} key The key of the method to get. + * @returns {*} Returns the function if it's native, else `undefined`. + */ +function getNative(object, key) { + var value = object == null ? undefined : object[key]; + return isNative(value) ? value : undefined; +} + +/** + * Checks if `value` is classified as a `Function` object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isFunction(_); + * // => true + * + * _.isFunction(/abc/); + * // => false + */ +function isFunction(value) { + // The use of `Object#toString` avoids issues with the `typeof` operator + // in older versions of Chrome and Safari which return 'function' for regexes + // and Safari 8 equivalents which return 'object' for typed array constructors. + return isObject(value) && objToString.call(value) == funcTag; +} + +/** + * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(1); + * // => false + */ +function isObject(value) { + // Avoid a V8 JIT bug in Chrome 19-20. + // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. + var type = typeof value; + return !!value && (type == 'object' || type == 'function'); +} + +/** + * Checks if `value` is a native function. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a native function, else `false`. + * @example + * + * _.isNative(Array.prototype.push); + * // => true + * + * _.isNative(_); + * // => false + */ +function isNative(value) { + if (value == null) { + return false; + } + if (isFunction(value)) { + return reIsNative.test(fnToString.call(value)); + } + return isObjectLike(value) && reIsHostCtor.test(value); +} + +module.exports = getNative; + +},{}],75:[function(require,module,exports){ +/** + * lodash 3.0.4 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.8.3 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** + * Checks if `value` is object-like. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + */ +function isObjectLike(value) { + return !!value && typeof value == 'object'; +} + +/** Used for native method references. */ +var objectProto = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty = objectProto.hasOwnProperty; + +/** Native method references. */ +var propertyIsEnumerable = objectProto.propertyIsEnumerable; + +/** + * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer) + * of an array-like value. + */ +var MAX_SAFE_INTEGER = 9007199254740991; + +/** + * The base implementation of `_.property` without support for deep paths. + * + * @private + * @param {string} key The key of the property to get. + * @returns {Function} Returns the new function. + */ +function baseProperty(key) { + return function(object) { + return object == null ? undefined : object[key]; + }; +} + +/** + * Gets the "length" property value of `object`. + * + * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792) + * that affects Safari on at least iOS 8.1-8.3 ARM64. + * + * @private + * @param {Object} object The object to query. + * @returns {*} Returns the "length" value. + */ +var getLength = baseProperty('length'); + +/** + * Checks if `value` is array-like. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is array-like, else `false`. + */ +function isArrayLike(value) { + return value != null && isLength(getLength(value)); +} + +/** + * Checks if `value` is a valid array-like length. + * + * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength). + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. + */ +function isLength(value) { + return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; +} + +/** + * Checks if `value` is classified as an `arguments` object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isArguments(function() { return arguments; }()); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false + */ +function isArguments(value) { + return isObjectLike(value) && isArrayLike(value) && + hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee'); +} + +module.exports = isArguments; + +},{}],76:[function(require,module,exports){ +/** + * lodash 3.0.4 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.8.3 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** `Object#toString` result references. */ +var arrayTag = '[object Array]', + funcTag = '[object Function]'; + +/** Used to detect host constructors (Safari > 5). */ +var reIsHostCtor = /^\[object .+?Constructor\]$/; + +/** + * Checks if `value` is object-like. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + */ +function isObjectLike(value) { + return !!value && typeof value == 'object'; +} + +/** Used for native method references. */ +var objectProto = Object.prototype; + +/** Used to resolve the decompiled source of functions. */ +var fnToString = Function.prototype.toString; + +/** Used to check objects for own properties. */ +var hasOwnProperty = objectProto.hasOwnProperty; + +/** + * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) + * of values. + */ +var objToString = objectProto.toString; + +/** Used to detect if a method is native. */ +var reIsNative = RegExp('^' + + fnToString.call(hasOwnProperty).replace(/[\\^$.*+?()[\]{}|]/g, '\\$&') + .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' +); + +/* Native method references for those with the same name as other `lodash` methods. */ +var nativeIsArray = getNative(Array, 'isArray'); + +/** + * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer) + * of an array-like value. + */ +var MAX_SAFE_INTEGER = 9007199254740991; + +/** + * Gets the native function at `key` of `object`. + * + * @private + * @param {Object} object The object to query. + * @param {string} key The key of the method to get. + * @returns {*} Returns the function if it's native, else `undefined`. + */ +function getNative(object, key) { + var value = object == null ? undefined : object[key]; + return isNative(value) ? value : undefined; +} + +/** + * Checks if `value` is a valid array-like length. + * + * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength). + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. + */ +function isLength(value) { + return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; +} + +/** + * Checks if `value` is classified as an `Array` object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isArray([1, 2, 3]); + * // => true + * + * _.isArray(function() { return arguments; }()); + * // => false + */ +var isArray = nativeIsArray || function(value) { + return isObjectLike(value) && isLength(value.length) && objToString.call(value) == arrayTag; +}; + +/** + * Checks if `value` is classified as a `Function` object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isFunction(_); + * // => true + * + * _.isFunction(/abc/); + * // => false + */ +function isFunction(value) { + // The use of `Object#toString` avoids issues with the `typeof` operator + // in older versions of Chrome and Safari which return 'function' for regexes + // and Safari 8 equivalents which return 'object' for typed array constructors. + return isObject(value) && objToString.call(value) == funcTag; +} + +/** + * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(1); + * // => false + */ +function isObject(value) { + // Avoid a V8 JIT bug in Chrome 19-20. + // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. + var type = typeof value; + return !!value && (type == 'object' || type == 'function'); +} + +/** + * Checks if `value` is a native function. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a native function, else `false`. + * @example + * + * _.isNative(Array.prototype.push); + * // => true + * + * _.isNative(_); + * // => false + */ +function isNative(value) { + if (value == null) { + return false; + } + if (isFunction(value)) { + return reIsNative.test(fnToString.call(value)); + } + return isObjectLike(value) && reIsHostCtor.test(value); +} + +module.exports = isArray; + +},{}],77:[function(require,module,exports){ +/** + * lodash 3.0.3 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.8.3 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** + * The base implementation of `_.create` without support for assigning + * properties to the created object. + * + * @private + * @param {Object} prototype The object to inherit from. + * @returns {Object} Returns the new object. + */ +var baseCreate = (function() { + function object() {} + return function(prototype) { + if (isObject(prototype)) { + object.prototype = prototype; + var result = new object; + object.prototype = undefined; + } + return result || {}; + }; +}()); + +/** + * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(1); + * // => false + */ +function isObject(value) { + // Avoid a V8 JIT bug in Chrome 19-20. + // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. + var type = typeof value; + return !!value && (type == 'object' || type == 'function'); +} + +module.exports = baseCreate; + +},{}],78:[function(require,module,exports){ +/** + * lodash 3.0.9 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.8.3 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** Used to detect unsigned integer values. */ +var reIsUint = /^\d+$/; + +/** + * Used as the [maximum length](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-number.max_safe_integer) + * of an array-like value. + */ +var MAX_SAFE_INTEGER = 9007199254740991; + +/** + * The base implementation of `_.property` without support for deep paths. + * + * @private + * @param {string} key The key of the property to get. + * @returns {Function} Returns the new function. + */ +function baseProperty(key) { + return function(object) { + return object == null ? undefined : object[key]; + }; +} + +/** + * Gets the "length" property value of `object`. + * + * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792) + * that affects Safari on at least iOS 8.1-8.3 ARM64. + * + * @private + * @param {Object} object The object to query. + * @returns {*} Returns the "length" value. + */ +var getLength = baseProperty('length'); + +/** + * Checks if `value` is array-like. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is array-like, else `false`. + */ +function isArrayLike(value) { + return value != null && isLength(getLength(value)); +} + +/** + * Checks if `value` is a valid array-like index. + * + * @private + * @param {*} value The value to check. + * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. + * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. + */ +function isIndex(value, length) { + value = (typeof value == 'number' || reIsUint.test(value)) ? +value : -1; + length = length == null ? MAX_SAFE_INTEGER : length; + return value > -1 && value % 1 == 0 && value < length; +} + +/** + * Checks if the provided arguments are from an iteratee call. + * + * @private + * @param {*} value The potential iteratee value argument. + * @param {*} index The potential iteratee index or key argument. + * @param {*} object The potential iteratee object argument. + * @returns {boolean} Returns `true` if the arguments are from an iteratee call, else `false`. + */ +function isIterateeCall(value, index, object) { + if (!isObject(object)) { + return false; + } + var type = typeof index; + if (type == 'number' + ? (isArrayLike(object) && isIndex(index, object.length)) + : (type == 'string' && index in object)) { + var other = object[index]; + return value === value ? (value === other) : (other !== other); + } + return false; +} + +/** + * Checks if `value` is a valid array-like length. + * + * **Note:** This function is based on [`ToLength`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength). + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. + */ +function isLength(value) { + return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; +} + +/** + * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(1); + * // => false + */ +function isObject(value) { + // Avoid a V8 JIT bug in Chrome 19-20. + // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. + var type = typeof value; + return !!value && (type == 'object' || type == 'function'); +} + +module.exports = isIterateeCall; + +},{}],79:[function(require,module,exports){ +(function (process,global){ +var Mocha = require('../'); + +/** + * Create a Mocha instance. + * + * @return {undefined} + */ + +var mocha = new Mocha({ reporter: 'html' }); + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date; +var setTimeout = global.setTimeout; +var setInterval = global.setInterval; +var clearTimeout = global.clearTimeout; +var clearInterval = global.clearInterval; + +var uncaughtExceptionHandlers = []; + +var originalOnerrorHandler = global.onerror; + +/** + * Remove uncaughtException listener. + * Revert to original onerror handler if previously defined. + */ + +process.removeListener = function(e, fn){ + if ('uncaughtException' == e) { + if (originalOnerrorHandler) { + global.onerror = originalOnerrorHandler; + } else { + global.onerror = function() {}; + } + var i = Mocha.utils.indexOf(uncaughtExceptionHandlers, fn); + if (i != -1) { uncaughtExceptionHandlers.splice(i, 1); } + } +}; + +/** + * Implements uncaughtException listener. + */ + +process.on = function(e, fn){ + if ('uncaughtException' == e) { + global.onerror = function(err, url, line){ + fn(new Error(err + ' (' + url + ':' + line + ')')); + return true; + }; + uncaughtExceptionHandlers.push(fn); + } +}; + +// The BDD UI is registered by default, but no UI will be functional in the +// browser without an explicit call to the overridden `mocha.ui` (see below). +// Ensure that this default UI does not expose its methods to the global scope. +mocha.suite.removeAllListeners('pre-require'); + +var immediateQueue = [] + , immediateTimeout; + +function timeslice() { + var immediateStart = new Date().getTime(); + while (immediateQueue.length && (new Date().getTime() - immediateStart) < 100) { + immediateQueue.shift()(); + } + if (immediateQueue.length) { + immediateTimeout = setTimeout(timeslice, 0); + } else { + immediateTimeout = null; + } +} + +/** + * High-performance override of Runner.immediately. + */ + +Mocha.Runner.immediately = function(callback) { + immediateQueue.push(callback); + if (!immediateTimeout) { + immediateTimeout = setTimeout(timeslice, 0); + } +}; + +/** + * Function to allow assertion libraries to throw errors directly into mocha. + * This is useful when running tests in a browser because window.onerror will + * only receive the 'message' attribute of the Error. + */ +mocha.throwError = function(err) { + Mocha.utils.forEach(uncaughtExceptionHandlers, function (fn) { + fn(err); + }); + throw err; +}; + +/** + * Override ui to ensure that the ui functions are initialized. + * Normally this would happen in Mocha.prototype.loadFiles. + */ + +mocha.ui = function(ui){ + Mocha.prototype.ui.call(this, ui); + this.suite.emit('pre-require', global, null, this); + return this; +}; + +/** + * Setup mocha with the given setting options. + */ + +mocha.setup = function(opts){ + if ('string' == typeof opts) opts = { ui: opts }; + for (var opt in opts) this[opt](opts[opt]); + return this; +}; + +/** + * Run mocha, returning the Runner. + */ + +mocha.run = function(fn){ + var options = mocha.options; + mocha.globals('location'); + + var query = Mocha.utils.parseQuery(global.location.search || ''); + if (query.grep) mocha.grep(new RegExp(query.grep)); + if (query.fgrep) mocha.grep(query.fgrep); + if (query.invert) mocha.invert(); + + return Mocha.prototype.run.call(mocha, function(err){ + // The DOM Document is not available in Web Workers. + var document = global.document; + if (document && document.getElementById('mocha') && options.noHighlighting !== true) { + Mocha.utils.highlightTags('code'); + } + if (fn) fn(err); + }); +}; + +/** + * Shim process.stdout. + */ + +process.stdout = require('browser-stdout')(); + +/** + * Expose the process shim. + * https://github.com/mochajs/mocha/pull/916 + */ + +Mocha.process = process; + +/** + * Expose mocha. + */ + +window.Mocha = Mocha; +window.mocha = mocha; + +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"../":1,"_process":50,"browser-stdout":40}]},{},[79]); \ No newline at end of file From ddc357b13632a253c3699d0fc1220a2ec2a51dd5 Mon Sep 17 00:00:00 2001 From: Roy Sutton Date: Tue, 10 Nov 2015 17:12:36 -0800 Subject: [PATCH 164/195] ENYO-2808 Add warning about incorrect fixedChildSize Enyo-DCO-1.1-Signed-off-by: Roy Sutton --- src/DataList/DataList.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/DataList/DataList.js b/src/DataList/DataList.js index 38cff237f..a03dd7e3b 100644 --- a/src/DataList/DataList.js +++ b/src/DataList/DataList.js @@ -132,7 +132,8 @@ var DataList = module.exports = kind( * pixels and applied to the primary size depending on the list's `orientation` (i.e., * it will be applied to `height` when the `orientation` is `'vertical'`, and to `width` * when the `orientation` is `'horizontal'`). Note that the list does not apply this - * value to the children via CSS. + * value to the children via CSS and mis-specifying this value can cause list operations that + * take the shortcut to fail. * * @type {Number} * @default null From 6d477c1c5403b131f229d7888fe397f862e5225f Mon Sep 17 00:00:00 2001 From: Roy Sutton Date: Wed, 11 Nov 2015 16:44:18 -0800 Subject: [PATCH 165/195] ENYO-2808 Clear metrics to allow for page generation Enyo-DCO-1.1-Signed-off-by: Roy Sutton --- src/VerticalDelegate.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/VerticalDelegate.js b/src/VerticalDelegate.js index d50fd09ce..dddee10bb 100644 --- a/src/VerticalDelegate.js +++ b/src/VerticalDelegate.js @@ -359,6 +359,13 @@ module.exports = { callback(); } } else { + // In pathological cases, the page bounds may not match up with the specified + // fixedChildSize or our estimation of the child size. This would cause the + // pagePosition calculation to not match the actual page position and the pages + // we wanted would not be generated. We throw away the current page metrics and + // only rely on computed values to force the correct pages to be generated, even + // if we get the actual position wrong. + list.metrics.pages = {}; // we do this to ensure we trigger the paging event when necessary this.resetToPosition(list, this.pagePosition(list, p)); // now retry the original logic until we have this right From 94796ede902ac275ccfb0047612e9caa14ea0c4f Mon Sep 17 00:00:00 2001 From: Gray Norton Date: Thu, 12 Nov 2015 15:18:20 -0800 Subject: [PATCH 166/195] Revert "Merge pull request #1311 from enyojs/ENYO-2644-b-graynorton" This reverts commit 14fe0017b3cf20ee87077a727af251f1489f2e0e, reversing changes made to 7b738e96920b2dda961de711f295efec41491b9a. --- src/NewDataList.js | 20 ++++++++++++++++ src/ScrollMath.js | 46 ++---------------------------------- src/Scrollable/Scrollable.js | 28 ++++++++++++++++------ 3 files changed, 43 insertions(+), 51 deletions(-) diff --git a/src/NewDataList.js b/src/NewDataList.js index ca39b1ebb..eff2b158a 100644 --- a/src/NewDataList.js +++ b/src/NewDataList.js @@ -72,6 +72,26 @@ module.exports = kind({ this.scrollTo(b.x, b.y, opts); }, + /** + * This method is considered private for NewDataList, although + * the corresponding method defined in the Scrollable mixin is + * public in the more general case. For NewDataList, application + * code should generally use `scrollToItem()` instead, because it works + * regardless of whether the target item is currently "virtualized," + * whereas `scrollToControl()` works only on non-virtual items. + * + * NewDataList overrides this method because it can provide a + * more accurate, more performant implementation than the general + * one provided by enyo/Scrollable. + * + * @private + */ + scrollToControl: function (control, opts) { + if (typeof control.index === 'number' && control.parent === this.$.container) { + this.scrollToItem(control.index, opts); + } + }, + /** * @private */ diff --git a/src/ScrollMath.js b/src/ScrollMath.js index 242e4a382..8656bbfd2 100644 --- a/src/ScrollMath.js +++ b/src/ScrollMath.js @@ -158,30 +158,6 @@ module.exports = kind( * @private */ kFrictionEpsilon: platform.webos >= 4 ? 1e-1 : 1e-2, - - /** - * TODO: Document - * Experimental - * - * @private - */ - xSnapIncrement: 0, - - /** - * TODO: Document - * Experimental - * - * @private - */ - ySnapIncrement: 0, - - /** - * TODO: Document - * Experimental - * - * @private - */ - boundarySnapThreshold: 0, /** * Top snap boundary, generally `0`. @@ -707,36 +683,18 @@ module.exports = kind( var animate = !opts || opts.behavior !== 'instant', xSnap = this.xSnapIncrement, ySnap = this.ySnapIncrement, - bSnap = this.boundarySnapThreshold, allowOverScroll = opts && opts.allowOverScroll, maxX = Math.abs(Math.min(0, this.rightBoundary)), maxY = Math.abs(Math.min(0, this.bottomBoundary)); - - if (xSnap) { + if (typeof xSnap === 'number') { x = xSnap * Math.round(x / xSnap); } - if (ySnap) { + if (typeof ySnap === 'number') { y = ySnap * Math.round(y / ySnap); } - if (bSnap) { - if (x > -this.x && maxX > x && maxX - x < bSnap) { - x = maxX; - } - else if (x < -this.x && x > 0 && x < bSnap) { - x = 0; - } - - if (y > -this.y && maxY > y && maxY - y < bSnap) { - y = maxY; - } - else if (y < -this.y && y > 0 && y < bSnap) { - y = 0; - } - } - if (!animate || !allowOverScroll) { x = Math.max(0, Math.min(x, maxX)); y = Math.max(0, Math.min(y, maxY)); diff --git a/src/Scrollable/Scrollable.js b/src/Scrollable/Scrollable.js index 4df032273..3775889ef 100644 --- a/src/Scrollable/Scrollable.js +++ b/src/Scrollable/Scrollable.js @@ -327,13 +327,27 @@ module.exports = { * * @public */ - scrollToControl: function (control, opts) { - var n = control.hasNode(); - - if (n) { - this.scrollToNode(n, opts); - } - }, + scrollToControl: kind.inherit(function (sup) { + return function (control, opts) { + var n; + + // Default implementation -- in case the Control + // applying the Scrollable mixin does not supply + // its own + if (sup === utils.nop) { + n = control.hasNode(); + + if (n) { + this.scrollToNode(n, opts); + } + } + // If the Control does provide an alternative + // implementation, we use it + else { + sup.apply(this, arguments); + } + }; + }), /** * TODO: Document. Based on CSSOM View spec () From afc1bf825158554fd4b5608590335ae0e849cf07 Mon Sep 17 00:00:00 2001 From: Gray Norton Date: Thu, 12 Nov 2015 15:18:59 -0800 Subject: [PATCH 167/195] Revert "Merge pull request #1303 from enyojs/ENYO-2644-graynorton" This reverts commit d7dba4c2729964349b9e24d5da6431114b2be6a8, reversing changes made to 05c54841aca254a169035667633de37d22a1b62c. --- src/NewDataList.js | 20 -------------------- src/Scrollable/Scrollable.js | 28 +++++++--------------------- 2 files changed, 7 insertions(+), 41 deletions(-) diff --git a/src/NewDataList.js b/src/NewDataList.js index eff2b158a..ca39b1ebb 100644 --- a/src/NewDataList.js +++ b/src/NewDataList.js @@ -72,26 +72,6 @@ module.exports = kind({ this.scrollTo(b.x, b.y, opts); }, - /** - * This method is considered private for NewDataList, although - * the corresponding method defined in the Scrollable mixin is - * public in the more general case. For NewDataList, application - * code should generally use `scrollToItem()` instead, because it works - * regardless of whether the target item is currently "virtualized," - * whereas `scrollToControl()` works only on non-virtual items. - * - * NewDataList overrides this method because it can provide a - * more accurate, more performant implementation than the general - * one provided by enyo/Scrollable. - * - * @private - */ - scrollToControl: function (control, opts) { - if (typeof control.index === 'number' && control.parent === this.$.container) { - this.scrollToItem(control.index, opts); - } - }, - /** * @private */ diff --git a/src/Scrollable/Scrollable.js b/src/Scrollable/Scrollable.js index 3775889ef..4df032273 100644 --- a/src/Scrollable/Scrollable.js +++ b/src/Scrollable/Scrollable.js @@ -327,27 +327,13 @@ module.exports = { * * @public */ - scrollToControl: kind.inherit(function (sup) { - return function (control, opts) { - var n; - - // Default implementation -- in case the Control - // applying the Scrollable mixin does not supply - // its own - if (sup === utils.nop) { - n = control.hasNode(); - - if (n) { - this.scrollToNode(n, opts); - } - } - // If the Control does provide an alternative - // implementation, we use it - else { - sup.apply(this, arguments); - } - }; - }), + scrollToControl: function (control, opts) { + var n = control.hasNode(); + + if (n) { + this.scrollToNode(n, opts); + } + }, /** * TODO: Document. Based on CSSOM View spec () From 329ff12f34d721be8d2e4ab55a7c3af9e68010f6 Mon Sep 17 00:00:00 2001 From: Gray Norton Date: Fri, 13 Nov 2015 17:35:04 -0800 Subject: [PATCH 168/195] ENYO-2840: Make `accessibilityPreventScroll` work in RTL The `accessibilityPreventScroll` feature prevents the browser engine from doing bad auto-scrolling things when an item is focused in one of our scrollers. It works by listening for native scroll events and forcibly resetting native scrollTop and scrollLeft properties as needed. Our logic for resetting scrollLeft did not previously account for RTL, so we fix that. As noted inline, the current fix is specific to Blink and WebKit; we'll need to do something clever to make this work cross-browser, but not attempting that now because cross-browser support is not urgent for the imminent internal release, and this is not our only cross-browser scrolling issue. Wrote up ENYO-2841 to track future work. Enyo-DCO-1.1-Signed-Off-By: Gray Norton (gray.norton@lge.com) --- src/AccessibilitySupport/AccessibilitySupport.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/AccessibilitySupport/AccessibilitySupport.js b/src/AccessibilitySupport/AccessibilitySupport.js index 35f301ef4..068a4b974 100644 --- a/src/AccessibilitySupport/AccessibilitySupport.js +++ b/src/AccessibilitySupport/AccessibilitySupport.js @@ -54,11 +54,14 @@ var defaultObservers = [ * * @private */ -function preventScroll (node) { +function preventScroll (node, rtl) { if (node) { dispatcher.listen(node, 'scroll', function () { node.scrollTop = 0; - node.scrollLeft = 0; + // TODO: This probably won't work cross-browser, as the different + // browser engines appear to treat scrollLeft differently in RTL. + // See ENYO-2841. + node.scrollLeft = rtl ? node.scrollWidth : 0; }); } } @@ -293,7 +296,7 @@ var AccessibilitySupport = { return function () { sup.apply(this, arguments); if (this.accessibilityPreventScroll) { - preventScroll(this.hasNode()); + preventScroll(this.hasNode(), this.rtl); } }; }) From 44c25732397dca827a216225b2d15231b8a5f906 Mon Sep 17 00:00:00 2001 From: Gray Norton Date: Fri, 13 Nov 2015 16:45:04 -0800 Subject: [PATCH 169/195] ENYO-2783: Refactoring in enyo/Scrollable to allow customization The logic for scrolling to a child of Scrollable has until now been essentially monolithic, but we now refactor it to allow parts of the logic to be selectively overridden. Specifically, there are now separate methods for getting the offset coordinates of the child (`getChildOffsets()`), calculating the target scroll coordinates (`getTargetCoordinates()`), and actually effecting the scroll (`scrollToTarget()`). The immediate use case is to provide a custom implementation of `getChildOffsets()` for NewDataList, since NewDataList can get child offsets more efficiently than the default implemenation does, and can also properly account for the spacing of list items in its own implementation. (Not properly accounting for spacing was the actual root cause of ENYO-2644 and ENYO-2783, the issues that led to this work. As part of this pull request, we are also reverting earlier, flawed solutions to the problem described in ENYO-2644). Note that we also have a use case for overriding `scrollToTarget()` (see ENYO-2710 and ENYO-2729), but that work is not required to fix ENYO-2783 so we aren't doing it now. Enyo-DCO-1.1-Signed-Off-By: Gray Norton (gray.norton@lge.com) --- src/DataRepeater.js | 16 ++++ src/NewDataList.js | 34 +++++-- src/Scrollable/Scrollable.js | 170 +++++++++++++++++++++-------------- 3 files changed, 148 insertions(+), 72 deletions(-) diff --git a/src/DataRepeater.js b/src/DataRepeater.js index 39e5eaffc..49de3d17c 100644 --- a/src/DataRepeater.js +++ b/src/DataRepeater.js @@ -577,6 +577,22 @@ var DataRepeater = module.exports = kind( return this.$.container.children[idx]; }, + /** + * Returns the index of a child control. This is useful primarily when you have a reference to a Control + * that is not (or may not be) an immediate child of the repeater, but is instead a sub-child of one of the + * repeater's immediate children. + * + * @param {Control} child - The child (or sub-child) whose index you want to retrieve. + * @returns {Number} The index of the child, or -1 if the Control is not a child of the repeater. + * @public + */ + indexForChild: function (child) { + while (child && child.repeater !== this) { + child = child.parent; + } + return child ? child.index : -1; + }, + /** * Retrieves the data associated with the [repeater]{@link module:enyo/DataRepeater~DataRepeater}. * diff --git a/src/NewDataList.js b/src/NewDataList.js index ca39b1ebb..eb44b0767 100644 --- a/src/NewDataList.js +++ b/src/NewDataList.js @@ -62,14 +62,14 @@ module.exports = kind({ // If the item is near the horizontal or vertical // origin, scroll all the way there - if (b.x <= this.spacing) { - b.x = 0; + if (b.left <= this.spacing) { + b.left = 0; } - if (b.y <= this.spacing) { - b.y = 0; + if (b.top <= this.spacing) { + b.top = 0; } - this.scrollTo(b.x, b.y, opts); + this.scrollTo(b.left, b.top, opts); }, /** @@ -274,11 +274,31 @@ module.exports = kind({ p2 = sp + (g2 * this.delta2); return (this.direction == 'vertical') - ? { x: p2, y: p, w: is2, h: is } - : { x: p, y: p2, w: is, h: is2 } + ? { left: p2, top: p, width: is2, height: is } + : { left: p, top: p2, width: is, height: is2 } ; }, + /** + * Providing a NewDataList-specific implementation of the + * `getChildOffsets()` interface defined by enyo/Scrollable. This + * implemenation is less expensive than the general implementation + * provided by Scrollable, and properly accounts for item spacing. + * + * @private + */ + getChildOffsets: function (child, scrollBounds) { + var index = this.indexForChild(child), + offsets = this.getItemBounds(index), + margin = this.spacing; + + offsets.getMargin = function (side) { + return margin; + }; + + return offsets; + }, + /** * @private */ diff --git a/src/Scrollable/Scrollable.js b/src/Scrollable/Scrollable.js index 4df032273..c2eb8055e 100644 --- a/src/Scrollable/Scrollable.js +++ b/src/Scrollable/Scrollable.js @@ -5,6 +5,7 @@ var dispatcher = require('../dispatcher'); var + Control = require('../Control'), EventEmitter = require('../EventEmitter'), ScrollMath = require('../ScrollMath'); @@ -321,37 +322,64 @@ module.exports = { }, /** - * TODO: Document. Based on CSSOM View spec () - * @see {@linkplain http://dev.w3.org/csswg/cssom-view/} - * @see {@linkplain https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView} + * Leaving for legacy support; should use scrollToChild() instead * + * @deprecated * @public */ scrollToControl: function (control, opts) { - var n = control.hasNode(); + this.scrollToChild(control, opts); + }, + + /** + * Helper function for `scrollToChild()` + * + * May be overridden as needed by more specific implementations. This + * API is to be considered experimental and subject to change -- use at + * your own risk. + * + * @protected + */ + getChildOffsets: kind.inherit(function (sup) { + if (sup === utils.nop) { + return function (child, scrollBounds) { + var node = (child instanceof Control) ? child.hasNode() : child, + offsets = dom.getAbsoluteBounds(node), + viewportBounds = dom.getAbsoluteBounds(this.scrollNode); + + offsets.right = document.body.offsetWidth - offsets.right; + viewportBounds.right = document.body.offsetWidth - viewportBounds.right; + + // Make absolute controlBounds relative to scroll position + offsets.top += scrollBounds.top; + if (this.rtl) { + offsets.right += scrollBounds.left; + } else { + offsets.left += scrollBounds.left; + } - if (n) { - this.scrollToNode(n, opts); + offsets.top = offsets.top - viewportBounds.top; + offsets.left = (this.rtl ? offsets.right : offsets.left) - (this.rtl ? viewportBounds.right : viewportBounds.left); + + offsets.getMargin = function (side) { + return dom.getComputedBoxValue(node, 'margin', side); + }; + + return offsets; + }; } - }, + else { + return sup; + } + }), /** - * TODO: Document. Based on CSSOM View spec () - * @see {@linkplain http://dev.w3.org/csswg/cssom-view/} - * @see {@linkplain https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView} + * Helper function for `scrollToChild()` * - * @public + * @private */ - scrollToNode: function(node, opts) { - var nodeBounds = dom.getAbsoluteBounds(node), - absoluteBounds = dom.getAbsoluteBounds(this.scrollNode), - scrollBounds = this.getScrollBounds(), - docWidth = document.body.offsetWidth, - block, - offsetTop, - offsetLeft, - offsetHeight, - offsetWidth, + getTargetCoordinates: function (scrollBounds, offsets, opts) { + var block, xDir, yDir, x, @@ -367,30 +395,10 @@ module.exports = { block = this.block; } - - // For the calculations below, we want the `right` bound - // to be relative to the document's right edge, not its - // left edge, so we adjust it now - nodeBounds.right = docWidth - nodeBounds.right; - absoluteBounds.right = docWidth - absoluteBounds.right; - - // Make absolute controlBounds relative to scroll position - nodeBounds.top += scrollBounds.top; - if (this.rtl) { - nodeBounds.right += scrollBounds.left; - } else { - nodeBounds.left += scrollBounds.left; - } - - offsetTop = nodeBounds.top - absoluteBounds.top; - offsetLeft = (this.rtl ? nodeBounds.right : nodeBounds.left) - (this.rtl ? absoluteBounds.right : absoluteBounds.left); - offsetHeight = nodeBounds.height; - offsetWidth = nodeBounds.width; - // 0: currently visible, 1: right of viewport, -1: left of viewport - xDir = calcNodeVisibility(offsetLeft, offsetWidth, scrollBounds.left, scrollBounds.clientWidth); + xDir = calcNodeVisibility(offsets.left, offsets.width, scrollBounds.left, scrollBounds.clientWidth); // 0: currently visible, 1: below viewport, -1: above viewport - yDir = calcNodeVisibility(offsetTop, offsetHeight, scrollBounds.top, scrollBounds.clientHeight); + yDir = calcNodeVisibility(offsets.top, offsets.height, scrollBounds.top, scrollBounds.clientHeight); // If we're already scrolling and the direction the node is in is not the same as the direction we're scrolling, // we need to recalculate based on where the scroller will end up, not where it is now. This is to handle the // case where the node is currently visible but won't be once the scroller settles. @@ -401,12 +409,12 @@ module.exports = { if (this.isScrolling) { if (this.xDir !== xDir) { scrollBounds.left = this.destX; - xDir = calcNodeVisibility(offsetLeft, offsetWidth, scrollBounds.left, scrollBounds.clientWidth); + xDir = calcNodeVisibility(offsets.left, offsets.width, scrollBounds.left, scrollBounds.clientWidth); block = 'nearest'; } if (this.yDir !== yDir) { scrollBounds.top = this.destY; - yDir = calcNodeVisibility(offsetTop, offsetHeight, scrollBounds.top, scrollBounds.clientHeight); + yDir = calcNodeVisibility(offsets.top, offsets.height, scrollBounds.top, scrollBounds.clientHeight); block = 'nearest'; } } @@ -419,24 +427,24 @@ module.exports = { // If control requested to be scrolled all the way to the viewport's left, or if the control // is larger than the viewport, scroll to the control's left edge. Otherwise, scroll just // far enough to get the control into view. - if (block === 'farthest' || block === 'start' || offsetWidth > scrollBounds.clientWidth) { - x = offsetLeft; + if (block === 'farthest' || block === 'start' || offsets.width > scrollBounds.clientWidth) { + x = offsets.left; } else { - x = offsetLeft - scrollBounds.clientWidth + offsetWidth; + x = offsets.left - scrollBounds.clientWidth + offsets.width; // If nodeStyle exists, add the _marginRight_ to the scroll value. - x += dom.getComputedBoxValue(node, 'margin', 'right'); + x += offsets.getMargin('right'); } break; case -1: // If control requested to be scrolled all the way to the viewport's right, or if the control // is larger than the viewport, scroll to the control's right edge. Otherwise, scroll just // far enough to get the control into view. - if (block === 'farthest' || block === 'end' || offsetWidth > scrollBounds.clientWidth) { - x = offsetLeft - scrollBounds.clientWidth + offsetWidth; + if (block === 'farthest' || block === 'end' || offsets.width > scrollBounds.clientWidth) { + x = offsets.left - scrollBounds.clientWidth + offsets.width; } else { - x = offsetLeft; + x = offsets.left; // If nodeStyle exists, subtract the _marginLeft_ from the scroll value. - x -= dom.getComputedBoxValue(node, 'margin', 'left'); + x -= offsets.getMargin('left'); } break; } @@ -449,34 +457,66 @@ module.exports = { // If control requested to be scrolled all the way to the viewport's top, or if the control // is larger than the viewport, scroll to the control's top edge. Otherwise, scroll just // far enough to get the control into view. - if (block === 'farthest' || block === 'start' || offsetHeight > scrollBounds.clientHeight) { - y = offsetTop; + if (block === 'farthest' || block === 'start' || offsets.height > scrollBounds.clientHeight) { + y = offsets.top; // If nodeStyle exists, subtract the _marginTop_ from the scroll value. - y -= dom.getComputedBoxValue(node, 'margin', 'top'); + y -= offsets.getMargin('top'); } else { - y = offsetTop - scrollBounds.clientHeight + offsetHeight; + y = offsets.top - scrollBounds.clientHeight + offsets.height; // If nodeStyle exists, add the _marginBottom_ to the scroll value. - y += dom.getComputedBoxValue(node, 'margin', 'bottom'); + y += offsets.getMargin('bottom'); } break; case -1: // If control requested to be scrolled all the way to the viewport's bottom, or if the control // is larger than the viewport, scroll to the control's bottom edge. Otherwise, scroll just // far enough to get the control into view. - if (block === 'farthest' || block === 'end' || offsetHeight > scrollBounds.clientHeight) { - y = offsetTop - scrollBounds.clientHeight + offsetHeight; + if (block === 'farthest' || block === 'end' || offsets.height > scrollBounds.clientHeight) { + y = offsets.top - scrollBounds.clientHeight + offsets.height; } else { - y = offsetTop; + y = offsets.top; // If nodeStyle exists, subtract the _marginBottom_ from the scroll value. - y -= dom.getComputedBoxValue(node, 'margin', 'bottom'); + y -= offsets.getMargin('bottom'); } break; } - // If x or y changed, scroll to new position - if (x !== this.scrollLeft || y !== this.scrollTop) { - this.scrollTo(x, y, opts); + return {x: x, y: y, xDir: xDir, yDir: yDir}; + }, + + /** + * Helper function for `scrollToChild()` + * + * May be overridden as needed by more specific implementations. This + * API is to be considered experimental and subject to change -- use at + * your own risk. + * + * @protected + */ + scrollToTarget: kind.inherit(function (sup) { + if (sup === utils.nop) { + return function (child, targetCoordinates, opts) { + this.scrollTo(targetCoordinates.x, targetCoordinates.y, opts); + }; } + else { + return sup; + } + }), + + /** + * TODO: Document. Based on CSSOM View spec () + * @see {@linkplain http://dev.w3.org/csswg/cssom-view/} + * @see {@linkplain https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView} + * + * @public + */ + scrollToChild: function(child, opts) { + var scrollBounds = this.getScrollBounds(), + offsets = this.getChildOffsets(child, scrollBounds), + coordinates = this.getTargetCoordinates(scrollBounds, offsets, opts); + + this.scrollToTarget(child, coordinates, opts); }, /** From abebbdb629b6d9ebbb7ad8e5b97b9eb2e0fe9398 Mon Sep 17 00:00:00 2001 From: Gray Norton Date: Mon, 16 Nov 2015 09:46:53 -0800 Subject: [PATCH 170/195] ENYO-2816: Fix VDR child indexing logic (again) It has taken a few tries, but I think this finally gets us to the bullet-proof child-indexing solution we've been chasing. We simply rebuild the index from scratch each time we run the virtualization logic in doIt(). Enyo-DCO-1.1-Signed-Off-By: Gray Norton (gray.norton@lge.com) --- src/VirtualDataRepeater.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/VirtualDataRepeater.js b/src/VirtualDataRepeater.js index 654dedd56..1c631602c 100644 --- a/src/VirtualDataRepeater.js +++ b/src/VirtualDataRepeater.js @@ -49,7 +49,7 @@ module.exports = kind({ init: function () { this.orderedChildren = [], this.orderedChildren_alt = [], - this.childrenByIndex = {}, + this.childrenByIndex = [], this.orderedModels = [], this.orderedModels_alt = [], this.needed = [], @@ -198,8 +198,11 @@ module.exports = kind({ n = this.numItems, needed = this.needed, b = this.bullpen, - cbi = this.childrenByIndex, i, idx, m, ci, c, nNeeded, j, len2; + + // Clear our index, which we'll rebuild as we assign children + this.childrenByIndex.length = 0; + // Walk up from the first index through the // last and make sure that a child is assigned // for each @@ -265,11 +268,6 @@ module.exports = kind({ } // And now some cleanup... len2 = o2.length; - // First, if we have fewer children than we had before, - // we need to remove stale entries from our index - for (i = len2; i < len; i++) { - cbi[i] = null; - } // Reset our "needed" array, so it's ready for next time needed.length = 0; // Swap in our new ordered arrays for the old ones From 1cfc1a8c9b51d27f5e4aa18a4118ff5625fda06b Mon Sep 17 00:00:00 2001 From: Gray Norton Date: Mon, 16 Nov 2015 10:01:48 -0800 Subject: [PATCH 171/195] Revert "ENYO-2808 Clear metrics to allow for page generation" This reverts commit 6d477c1c5403b131f229d7888fe397f862e5225f. Reverting this change because it turns out not to be necessary to address any currently known issue, and we're at a point in both the product release cycle and the lifespan of enyo/DataList where minimizing risk of new regressions is our top priority. Leaving in the git log, though, in case we run into problems in this area and want to retrace our steps. Enyo-DCO-1.1-Signed-Off-By: Gray Norton (gray.norton@lge.com) --- src/VerticalDelegate.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/VerticalDelegate.js b/src/VerticalDelegate.js index dddee10bb..d50fd09ce 100644 --- a/src/VerticalDelegate.js +++ b/src/VerticalDelegate.js @@ -359,13 +359,6 @@ module.exports = { callback(); } } else { - // In pathological cases, the page bounds may not match up with the specified - // fixedChildSize or our estimation of the child size. This would cause the - // pagePosition calculation to not match the actual page position and the pages - // we wanted would not be generated. We throw away the current page metrics and - // only rely on computed values to force the correct pages to be generated, even - // if we get the actual position wrong. - list.metrics.pages = {}; // we do this to ensure we trigger the paging event when necessary this.resetToPosition(list, this.pagePosition(list, p)); // now retry the original logic until we have this right From 5e68af5784d958699eff62ea8150c9cd194cf81a Mon Sep 17 00:00:00 2001 From: Ryan Duffy Date: Mon, 16 Nov 2015 13:45:57 -0600 Subject: [PATCH 172/195] prevent update a11y attributes when unchanged Issue: ENYO-2844 Enyo-DCO-1.1-Signed-off-by: Ryan Duffy (ryan.duffy@lge.com) --- src/AccessibilitySupport/AccessibilitySupport.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/AccessibilitySupport/AccessibilitySupport.js b/src/AccessibilitySupport/AccessibilitySupport.js index 35f301ef4..54c74f92f 100644 --- a/src/AccessibilitySupport/AccessibilitySupport.js +++ b/src/AccessibilitySupport/AccessibilitySupport.js @@ -283,7 +283,11 @@ var AccessibilitySupport = { else if (value !== undefined && value !== null) { value = String(value); } - this.setAttribute(name, value); + // prevent invalidating attributes unnecessarily by checking current value first. avoids + // resetting values on alert-able properties (e.g. aria-valuenow). + if (this.getAttribute(name) !== value) { + this.setAttribute(name, value); + } }, /** From 6b2d06f0e408de7f378b9c08ec873b1625754bbf Mon Sep 17 00:00:00 2001 From: Jenkins Date: Tue, 17 Nov 2015 16:08:37 -0800 Subject: [PATCH 173/195] Update version string to 2.6.0-pre.20 --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 9e8024b5c..de1e7053d 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ 'use strict'; exports = module.exports = require('./src/options'); -exports.version = '2.6.0-pre.18.1'; +exports.version = '2.6.0-pre.20'; From d299d78331301e282fe16f481821ef330a5c5eed Mon Sep 17 00:00:00 2001 From: Ryan Duffy Date: Thu, 19 Nov 2015 15:21:22 -0600 Subject: [PATCH 174/195] prevent scrolling when focusing on a panel while transitioning in Issue: ENYO-2858 Enyo-DCO-1.1-Signed-off-by: Ryan Duffy (ryan.duffy@lge.com) --- src/LightPanels/LightPanels.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index aa4f3eb8b..f0ce6b1a7 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -99,6 +99,11 @@ module.exports = kind( */ defaultKind: LightPanel, + /** + * @private + */ + accessibilityPreventScroll: true, + /** * The index of the active panel. * From 054a58a02b60bab09f7ef552d6effae7b371c9c4 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Thu, 19 Nov 2015 15:35:46 -0800 Subject: [PATCH 175/195] Update version string to 2.6.0-pre.20.1 --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index de1e7053d..763a932fd 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ 'use strict'; exports = module.exports = require('./src/options'); -exports.version = '2.6.0-pre.20'; +exports.version = '2.6.0-pre.20.1'; From 180e98f5ec4c7911a6ed4bee86fe05836fe3da7a Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Fri, 20 Nov 2015 14:08:12 -0800 Subject: [PATCH 176/195] JSDoc cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/Image/Image.js | 9 ++++----- src/resolution.js | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Image/Image.js b/src/Image/Image.js index 85d1bf03a..1d70cfa94 100644 --- a/src/Image/Image.js +++ b/src/Image/Image.js @@ -51,7 +51,7 @@ var * specific image assets in a hash/object format to the `src` property, instead of the usual * string. The image sources will be used automatically when the screen resolution is less than * or equal to those screen types. For more informaton on our resolution support, and how to -* enable this feature, see our [resolution independence documentation]{@link module:enyo/resolution}. +* enable this feature, see the documentation for {@link module:enyo/resolution}. * * ``` * // Take advantage of the multi-rez mode @@ -106,11 +106,10 @@ module.exports = kind( /** * Maps to the `src` attribute of an [<img> tag]{@glossary img}. This also supports - * a multi-resolution hash object. See - * [the above description of enyo.Image]{@link module:enyo/Image~Image} for more details and examples - * or our [resolution independence docs]{@link module:enyo/resolution}. + * a multi-resolution hash object. For more details and examples, see the description of + * {@link module:enyo/Image~Image} above, or the documentation for {@link module:enyo/resolution}. * - * @type {String} + * @type {String|module:enyo/resolution#selectSrc~src} * @default '' * @public */ diff --git a/src/resolution.js b/src/resolution.js index 7d197acc7..883bb97bd 100644 --- a/src/resolution.js +++ b/src/resolution.js @@ -221,7 +221,7 @@ var ri = module.exports = { * a single image source or a key/value hash/object containing keys representing screen * types (`'hd'`, `'fhd'`, `'uhd'`, etc.) and values containing the asset source for * that target screen resolution. - * @returns {String} The chosen source, given the string or list provided + * @returns {String} The chosen source, given the string or hash provided * @public */ selectSrc: function (src) { From b8841bae33afebb55c307cc0005648d349d2fe72 Mon Sep 17 00:00:00 2001 From: Madala Cholan Satyanarayana Date: Fri, 27 Nov 2015 19:38:47 +0530 Subject: [PATCH 177/195] Optimizing easing calculations in a frame Enyo-DCO-1.1-Signed-off-by:Madala Satyanarayana --- src/AnimationSupport/Easings.js | 210 ++++++++++++++++++++++++++++++++ src/AnimationSupport/Tween.js | 110 +++-------------- 2 files changed, 230 insertions(+), 90 deletions(-) diff --git a/src/AnimationSupport/Easings.js b/src/AnimationSupport/Easings.js index 10143068d..582412f44 100644 --- a/src/AnimationSupport/Easings.js +++ b/src/AnimationSupport/Easings.js @@ -4,11 +4,221 @@ * @module enyo/AnimationSupport/Easings * @public */ +var matrixUtil = require('./Matrix'); var b = 0, c = 1; +var temp = null, + tempProp = null, + tempOldState = [], + tempNewState = []; var easings = { + /** + * @public + * apply the function to check whether the ease object has changed + * @params currentEase : the ease object which is currently available + */ + + easeChanged: function(currentEase) { + + if (temp === null) { // setting the values for the first time + temp = currentEase; + return false; + } else { + + if (JSON.stringify(temp) === JSON.stringify(currentEase)) { // compare the values + return false; + } else { + temp = currentEase; + return true; + } + + } + }, + /** + * @public + * apply the function to check whether the animating property of the object has changed + * @params currentProp : the animating property of the object which is currently available + */ + propChange: function(currentProp) { + + if (tempProp === null) { // setting the values for the first time + tempProp = currentProp; + return false; + } else { + + if (tempProp === currentProp) { // compare the values + return false; + } else { + tempProp = currentProp; + return true; + } + + } + }, + /** + * @public + * apply the function to check whether the oldState of the object has changed + * @params currentOldState : the oldState of the object which is currently available + */ + + oldStateChange: function(currentOldState) { + + if (tempOldState === null) { // setting the values for the first time + tempOldState = currentOldState; + return false; + } else { + var compareValue = easings.compareStates(tempOldState, currentOldState); + if (compareValue === true) { // compare the values + return false; + } else { + tempOldState = currentOldState; + return true; + } + + } + + }, + /** + * @public + * apply the function to check whether the newState of the object has changed + * @params currentOldState : the newState of the object which is currently available + */ + + newStateChange: function(currentNewState) { + + if (tempNewState === null) { // setting the values for the first time + tempNewState = currentNewState; + return false; + } else { + var compareValue = easings.compareStates(tempNewState, currentNewState); + if (compareValue === true) { // compare the values + return false; + } else { + tempNewState = currentNewState; + return true; + } + + } + + }, + /** + * @public + * apply the function to compare the states which are arrays + * @params currentOldState : x is the previous state and y is the current state + */ + + compareStates: function(x, y) { + var xLen = x.length; + var yLen = y.length; + if (xLen != yLen) { + return false; + } + for (var i = 0; i < xLen; i++) { + if (x[i] instanceof Array && y[i] instanceof Array) { + // recurse into the nested arrays + if (x[i].length != y[i].length) { + return false; + } + var recursiveValue = easings.compareStates(x[i], y[i]); + if (recursiveValue === false) { + return false; + } + } else { + if (x[i] != y[i]) { + return false; + } + } + } + return true; + }, + + calculateEase: function(easeObj, startPoint, endPoint) { + var order = (easeObj && Object.keys(easeObj).length) ? (Object.keys(easeObj).length + 1) : 0; + var controlPoints = [startPoint], + bValues = [], + m1 = [], + m2 = [], + m3 = [], + m4 = [], + l = 0; + + var t, a; + for (var key in easeObj) { + t = parseFloat(key) / 100; + a = parseFloat(easeObj[key]) / 100; + bValues = easings.getBezierValues(t, order); + bValues.shift(); + m1.push(a - bValues.pop()); + m2.push(bValues); + } + + m3 = matrixUtil.inverseN(m2, bValues.length); + + m4 = matrixUtil.multiplyN(m3, m1); + l = m4.length; + for (var i = 0; i < l; i++) { + controlPoints.push([m4[i], m4[i], m4[i]]); + } + + controlPoints.push(endPoint); + return controlPoints; + }, + /** + * @private + * @params n: order, k: current position + */ + getCoeff: function(n, k) { + n = parseInt(n, 10); + k = parseInt(k, 10); + // Credits + // https://math.stackexchange.com/questions/202554/how-do-i-compute-binomial-coefficients-efficiently#answer-927064 + if (isNaN(n) || isNaN(k)) + return void 0; + if ((n < 0) || (k < 0)) + return void 0; + if (k > n) + return void 0; + if (k === 0) + return 1; + if (k === n) + return 1; + if (k > n / 2) + return this.getCoeff(n, n - k); + + return n * this.getCoeff(n - 1, k - 1) / k; + }, + /** + * @public + * @params t: time, n: order + */ + getBezierValues: function(t, n) { + t = parseFloat(t, 10), + n = parseInt(n, 10); + + if (isNaN(t) || isNaN(n)) + return void 0; + if ((t < 0) || (n < 0)) + return void 0; + if (t > 1) + return void 0; + + var c, + values = [], + x = (1 - t), + y = t; + // + // Binomial theorem to expand (x+y)^n + // + for (var k = 0; k <= n; k++) { + c = this.getCoeff(n, k) * Math.pow(x, (n - k)) * Math.pow(y, k); + values.push(c); + } + + return values; + }, + timeCheck: function(t, d) { t = t * d; diff --git a/src/AnimationSupport/Tween.js b/src/AnimationSupport/Tween.js index f72f53584..3457e3374 100644 --- a/src/AnimationSupport/Tween.js +++ b/src/AnimationSupport/Tween.js @@ -2,7 +2,7 @@ require('enyo'); var frame = require('./Frame'), - matrixUtil = require('./Matrix'), + easings = require('./Easings'), Vector = require('./Vector'); var oldState, newState, node, matrix, cState = []; @@ -112,11 +112,26 @@ module.exports = { if (newState[k]) { if (charc.ease && (typeof charc.ease !== 'function')) { if ((k == 'rotate')) { + pts = this.beizerSPoints(charc.ease, frame.copy(oldState[k]), frame.copy(newState[k]), props[k]); cState = this.beizerSpline(t, pts, cState); + } else { - pts = this.calculateEase(charc.ease, frame.copy(oldState[k]), frame.copy(newState[k])); - cState = this.getBezier(t, pts, cState); + var checkEaseChange = easings.easeChanged(charc.ease); + var propChange = easings.propChange(k); + + if (!charc.controlPoints || (propChange === true || checkEaseChange === true)) { + // for the first time or either of Ease/Propery changed + charc.controlPoints = easings.calculateEase(charc.ease, frame.copy(oldState[k]), frame.copy(newState[k])); + } else if (propChange === false && checkEaseChange === false) { + // for the cases where property and ease remain same and the states are varying + var oldStateCheck = easings.oldStateChange(frame.copy(oldState[k])); + var newStateCheck = easings.newStateChange(frame.copy(newState[k])); + if (oldStateCheck === true || newStateCheck === true) { + charc.controlPoints = easings.calculateEase(charc.ease, frame.copy(oldState[k]), frame.copy(newState[k])); + } + } + cState = this.getBezier(t, charc.controlPoints, cState); } } else { c = k == 'rotate' ? this.slerp : this.lerp; @@ -249,37 +264,6 @@ module.exports = { return vR; }, - calculateEase: function(easeObj, startPoint, endPoint) { - var order = (easeObj && Object.keys(easeObj).length) ? (Object.keys(easeObj).length + 1) : 0; - var controlPoints = [startPoint], - bValues = [], - m1 = [], - m2 = [], - m3 = [], - m4 = [], - l = 0; - - var t, a; - for (var key in easeObj) { - t = parseFloat(key) / 100; - a = parseFloat(easeObj[key]) / 100; - bValues = this.getBezierValues(t, order); - bValues.shift(); - m1.push(a - bValues.pop()); - m2.push(bValues); - } - - m3 = matrixUtil.inverseN(m2, bValues.length); - - m4 = matrixUtil.multiplyN(m3, m1); - l = m4.length; - for (var i = 0; i < l; i++) { - controlPoints.push([m4[i], m4[i], m4[i]]); - } - - controlPoints.push(endPoint); - return controlPoints; - }, complete: function(charc) { charc.animating = false; @@ -332,7 +316,7 @@ module.exports = { lastIndex = (c - 1), startPoint = points[0], endPoint = points[lastIndex], - values = this.getBezierValues(t, lastIndex); + values = easings.getBezierValues(t, lastIndex); for (i = 0; i < l; i++) { vR[i] = 0; @@ -345,60 +329,6 @@ module.exports = { } } return vR; - }, - - /** - * @private - * @params n: order, k: current position - */ - getCoeff: function(n, k) { - n = parseInt(n, 10); - k = parseInt(k, 10); - // Credits - // https://math.stackexchange.com/questions/202554/how-do-i-compute-binomial-coefficients-efficiently#answer-927064 - if (isNaN(n) || isNaN(k)) - return void 0; - if ((n < 0) || (k < 0)) - return void 0; - if (k > n) - return void 0; - if (k === 0) - return 1; - if (k === n) - return 1; - if (k > n / 2) - return this.getCoeff(n, n - k); - - return n * this.getCoeff(n - 1, k - 1) / k; - }, - - /** - * @public - * @params t: time, n: order - */ - getBezierValues: function(t, n) { - t = parseFloat(t, 10), - n = parseInt(n, 10); - - if (isNaN(t) || isNaN(n)) - return void 0; - if ((t < 0) || (n < 0)) - return void 0; - if (t > 1) - return void 0; - - var c, - values = [], - x = (1 - t), - y = t; - // - // Binomial theorem to expand (x+y)^n - // - for (var k = 0; k <= n; k++) { - c = this.getCoeff(n, k) * Math.pow(x, (n - k)) * Math.pow(y, k); - values.push(c); - } - - return values; } + }; From c1abdd8660e32f4f08d973ae7ab70ea0b6354ef6 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Mon, 30 Nov 2015 15:37:25 -0800 Subject: [PATCH 178/195] Update version string to 2.6.0-rc.1 --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 763a932fd..ca5e1a50e 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ 'use strict'; exports = module.exports = require('./src/options'); -exports.version = '2.6.0-pre.20.1'; +exports.version = '2.6.0-rc.1'; From 432f5c51f84ed70d52afb965b55957be6fc73ccd Mon Sep 17 00:00:00 2001 From: Ryan Duffy Date: Thu, 3 Dec 2015 15:41:48 -0600 Subject: [PATCH 179/195] reset absoluteShowing when DataList is tore down Issue: PLAT-13387 Enyo-DCO-1.1-Signed-off-by: Ryan Duffy (ryan.duffy@lge.com) --- src/DataList/DataList.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/DataList/DataList.js b/src/DataList/DataList.js index a03dd7e3b..7bce1a45e 100644 --- a/src/DataList/DataList.js +++ b/src/DataList/DataList.js @@ -462,6 +462,19 @@ var DataList = module.exports = kind( sup.apply(this, arguments); }; }), + + /** + * @private + */ + beforeTeardown: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + // reset absoluteShowing on teardown because it can't be absolutely showing if it + // doesn't have a node! + this.set('absoluteShowing', false); + }; + }), + /** * Overloaded from base [kind]{@glossary kind} to ensure that the container options * correctly apply the [scroller]{@link module:enyo/Scroller~Scroller} options before instantiating it. From 91fcbb36d6f4a1d68d45cbaa3242ea1a7c974735 Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Fri, 4 Dec 2015 11:43:28 -0800 Subject: [PATCH 180/195] Updating inline code sample for modularized Enyo Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/DragAvatar.js | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/DragAvatar.js b/src/DragAvatar.js index 029aa1fe5..2cf9fe148 100644 --- a/src/DragAvatar.js +++ b/src/DragAvatar.js @@ -32,23 +32,27 @@ var Avatar = kind({ * position relative to the current pointer location. * * ```javascript -* enyo.kind({ -* name: 'App', -* handlers: { -* ondrag: 'drag', -* ondragfinish: 'dragFinish', -* }, -* components: [ -* {name: 'dragAvatar', kind: 'DragAvatar', -* components: [{tag: 'img', src: 'images/icon.png'}] -* } -* ], -* drag: function(inSender, inEvent) { -* this.$.dragAvatar.drag(inEvent); -* }, -* dragFinish: function(inSender, inEvent) { -* this.$.dragAvatar.hide(); -* } +* var +* kind = require('enyo/kind'), +* DragAvatar = require('enyo/DragAvatar'); +* +* module.exports = kind({ +* name: 'App', +* handlers: { +* ondrag: 'drag', +* ondragfinish: 'dragFinish', +* }, +* components: [ +* {name: 'dragAvatar', kind: DragAvatar, +* components: [{tag: 'img', src: 'images/icon.png'}] +* } +* ], +* drag: function(inSender, inEvent) { +* this.$.dragAvatar.drag(inEvent); +* }, +* dragFinish: function(inSender, inEvent) { +* this.$.dragAvatar.hide(); +* } * }); * ``` * From ea6495c843294c725b42461b99e27a0b42dd12e3 Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Fri, 4 Dec 2015 13:32:00 -0800 Subject: [PATCH 181/195] Updating inline code sample for modularized Enyo Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/MediaSource.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/MediaSource.js b/src/MediaSource.js index 1467f88b7..13a51da37 100644 --- a/src/MediaSource.js +++ b/src/MediaSource.js @@ -22,11 +22,16 @@ var /** * A media source for {@link module:enyo/Audio~Audio} or {@link module:enyo/Video~Video}. * -* ``` -* {kind: 'Video', components: [ -* {src: 'video.mp4', type: 'video/mp4'}, -* {src: 'video.ogg', type: 'video/ogg'}, -* {src: 'video.webm', type: 'video/webm'} +* ```javascript +* var +* kind = require('enyo/kind'), +* Video = require('enyo/Video'), +* MediaSource = require('enyo/MediaSource'); +* +* {kind: Video, components: [ +* {kind: MediaSource, src: 'video.mp4', type: 'video/mp4'}, +* {kind: MediaSource, src: 'video.ogg', type: 'video/ogg'}, +* {kind: MediaSource, src: 'video.webm', type: 'video/webm'} * ]} * ``` * From 25e2239e98358a65a1d3e8f2a1f78ef189e2ea8b Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Fri, 4 Dec 2015 14:40:45 -0800 Subject: [PATCH 182/195] Updating inline code samples for modularized Enyo Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/Repeater.js | 21 +++++++++++++-------- src/Select.js | 26 +++++++++++++++----------- src/Video.js | 10 +++++++--- 3 files changed, 35 insertions(+), 22 deletions(-) diff --git a/src/Repeater.js b/src/Repeater.js index 371e9e553..9c7116608 100644 --- a/src/Repeater.js +++ b/src/Repeater.js @@ -89,18 +89,23 @@ var OwnerProxy = kind( * and are wrapped in a control that keeps the state of the item index. * * ```javascript -* {kind: 'Repeater', count: 2, onSetupItem: 'setImageSource', components: [ -* {kind: 'Image'} +* var +* kind = require('enyo/kind'), +* Image = require('enyo/Image'), +* Repeater = require('enyo/Repeater'); +* +* {kind: Repeater, count: 2, onSetupItem: 'setImageSource', components: [ +* {kind: 'Image'} * ]} -* +* * setImageSource: function(inSender, inEvent) { -* var index = inEvent.index; -* var item = inEvent.item; -* item.$.image.setSrc(this.imageSources[index]); -* return true; +* var index = inEvent.index; +* var item = inEvent.item; +* item.$.image.setSrc(this.imageSources[index]); +* return true; * } * ``` -* +* * Be sure to return `true` from your `onSetupItem` handler to avoid having other * {@glossary event} handlers further up the tree try to modify your item control. * diff --git a/src/Select.js b/src/Select.js index 875dc426f..cdfd106b1 100644 --- a/src/Select.js +++ b/src/Select.js @@ -19,19 +19,23 @@ var * {@link module:enyo/Select~Select} implements an HTML [selection]{@glossary select} widget, using * {@link module:enyo/Option~Option} instances by default. * -* ``` -* {kind: 'Select', onchange: 'selectChanged', components: [ -* {content: 'Descending', value: 'd'}, -* {content: 'Ascending', value: 'a'} +* ```javascript +* var +* kind = require('enyo/kind'), +* Select = require('enyo/Select'); +* +* {kind: Select, onchange: 'selectChanged', components: [ +* {content: 'Descending', value: 'd'}, +* {content: 'Ascending', value: 'a'} * ]} -* +* * selectChanged: function (inSender, inEvent) { -* var s = inSender.getValue(); -* if (s == 'd') { -* this.sortListDescending(); -* } else { -* this.sortListAscending(); -* } +* var s = inSender.getValue(); +* if (s == 'd') { +* this.sortListDescending(); +* } else { +* this.sortListAscending(); +* } * } * ``` * diff --git a/src/Video.js b/src/Video.js index 428ea0f8c..6e9288156 100644 --- a/src/Video.js +++ b/src/Video.js @@ -111,10 +111,14 @@ var * * Initialize a video [component]{@link module:enyo/Component~Component} as follows: * +* ```javascript +* var +* kind = require('enyo/kind'), +* Video = require('enyo/Video'); +* +* {kind: Video, src: 'http://www.w3schools.com/html/movie.mp4'} * ``` -* {kind: 'Video', src: 'http://www.w3schools.com/html/movie.mp4'} -* ``` -* +* * To play a video, call `this.$.video.play()`. * * To get a reference to the actual HTML 5 Video element, call `this.$.video.hasNode()`. From a6ca0ef27f121716df43b11e7600b884b00a3b16 Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Fri, 4 Dec 2015 16:26:20 -0800 Subject: [PATCH 183/195] Updating inline code samples for modularized Enyo Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/Controller.js | 2 +- src/InputBinding.js | 2 +- src/ModelController.js | 4 +- src/MultipleDispatchComponent.js | 4 +- src/Selection.js | 67 +++++++++++++++++++------------- src/ViewController.js | 4 +- 6 files changed, 49 insertions(+), 34 deletions(-) diff --git a/src/Controller.js b/src/Controller.js index cb4fa3955..cf2c52f91 100644 --- a/src/Controller.js +++ b/src/Controller.js @@ -14,7 +14,7 @@ var /** * {@link module:enyo/Controller~Controller} is the base [kind]{@glossary kind} for all -* controllers in Enyo. An abstract kind, `enyo.Controller` is a +* controllers in Enyo. An abstract kind, `enyo/Controller` is a * [delegate]{@glossary delegate}/[component]{@link module:enyo/Component~Component} that * is designed to be a proxy for information. * diff --git a/src/InputBinding.js b/src/InputBinding.js index 3408ecf45..ac2d75810 100644 --- a/src/InputBinding.js +++ b/src/InputBinding.js @@ -14,7 +14,7 @@ var /** * An {@link module:enyo/Binding~Binding} designed to have its [source]{@link module:enyo/Binding~Binding#source} * or its [target]{@link module:enyo/Binding~Binding#target} be an {@link module:enyo/Input~Input}. If the -* `enyo.Input` has a [placeholder]{@link module:enyo/Input~Input#placeholder}, it will be +* `enyo/Input` has a [placeholder]{@link module:enyo/Input~Input#placeholder}, it will be * used when there is no value. This is a [two-way]{@link module:enyo/Binding~Binding#oneWay} binding. * * @class InputBinding diff --git a/src/ModelController.js b/src/ModelController.js index acd21a048..06a375eb5 100644 --- a/src/ModelController.js +++ b/src/ModelController.js @@ -30,12 +30,12 @@ var BaseModelController = kind({ /** * A controller designed to proxy an underlying {@link module:enyo/Model~Model}. Other * [kinds]{@glossary kind} may [bind]{@link module:enyo/BindingSupport~BindingSupport} to this -* controller as if it were an `enyo.Model`. Using the +* controller as if it were an `enyo/Model`. Using the * [model]{@link module:enyo/ModelController~ModelController#model} reserved property, the actual model * may be changed without the bindings' needing to know. It will also propagate * events [emitted]{@link module:enyo/EventEmitter~EventEmitter#emit} by the underlying model. * -* It is important to note that `"model"` is a reserved property name. Also +* It is important to note that `'model'` is a reserved property name. Also * note that bindings should **never** bind through the controller to the model * directly. * diff --git a/src/MultipleDispatchComponent.js b/src/MultipleDispatchComponent.js index 4732347f8..387b218cf 100644 --- a/src/MultipleDispatchComponent.js +++ b/src/MultipleDispatchComponent.js @@ -13,11 +13,11 @@ var MultipleDispatchSupport = require('./MultipleDispatchSupport'); /** -* {@link module:enyo/MultipleDispatchComponent~MultipleDispatchComponent} is a purely abstract [kind] +* {@link module:enyo/MultipleDispatchComponent~MultipleDispatchComponent} is a purely abstract * {@glossary kind} that simply provides a common ancestor for * {@link module:enyo/Component~Component} [objects]{@glossary Object} that need * the [MultipleDispatchSupport]{@link module:enyo/MultipleDispatchSupport~MultipleDispatchSupport} -* [mixin]{@glossary mixin}. +* {@glossary mixin}. * * @class MultipleDispatchComponent * @extends module:enyo/Component~Component diff --git a/src/Selection.js b/src/Selection.js index d2bb9aab9..0f3dfbf86 100644 --- a/src/Selection.js +++ b/src/Selection.js @@ -26,10 +26,15 @@ var * Fires when an item is selected. * * ```javascript -* {kind: "Selection", onSelect: "selectRow"... -* ... +* var +* kind = require('enyo/kind'), +* Selection = require('enyo/Selection'); +* +* {kind: Selection, onSelect: 'selectRow', ... } +* * selectRow: function(inSender, inEvent) { -* ... +* ... +* } * ``` * * @event module:enyo/Selection~Selection#onSelect @@ -45,10 +50,15 @@ var * Fires when an item is deselected. * * ```javascript -* {kind: "Selection", onSelect: "deselectRow"... -* ... -* deselectRow: function(inSender, inEvent) -* ... +* var +* kind = require('enyo/kind'), +* Selection = require('enyo/Selection'); +* +* {kind: Selection, onSelect: 'deselectRow', ... } +* +* deselectRow: function(inSender, inEvent) { +* ... +* } * ``` * * @event module:enyo/Selection~Selection#onDeselect @@ -76,25 +86,30 @@ var * selection state management for both single-select and multi-select lists. * * ```javascript -* // The following is an excerpt from enyo.FlyweightRepeater. -* enyo.kind({ -* name: "enyo.FlyweightRepeater", -* ... -* components: [ -* {kind: "Selection", onSelect: "selectDeselect", onDeselect: "selectDeselect"}, -* ... -* ], -* tap: function(inSender, inEvent) { -* ... -* // mark the tapped row as selected -* this.$.selection.select(inEvent.index); -* ... -* }, -* selectDeselect: function(inSender, inEvent) { -* // this is where a row selection highlight might be applied -* this.renderRow(inEvent.key); -* } -* ... +* // The following code is excerpted from layout/FlyweightRepeater. +* +* var +* kind = require('enyo/kind'), +* Selection = require('enyo/Selection'); +* +* module.exports = kind({ +* components: [ +* {kind: Selection, onSelect: 'selectDeselect', onDeselect: 'selectDeselect'}, +* ... +* ], +* tap: function(inSender, inEvent) { +* ... +* if (this.toggleSelected) { +* this.$.selection.toggle(event.index); +* } else { +* this.$.selection.select(event.index); +* } +* }, +* selectDeselect: function(inSender, inEvent) { +* // this is where a row selection highlight might be applied +* this.renderRow(inEvent.key); +* } +* ... * }); * ``` * diff --git a/src/ViewController.js b/src/ViewController.js index 7d0556954..d079b5fdb 100644 --- a/src/ViewController.js +++ b/src/ViewController.js @@ -26,9 +26,9 @@ var * used as the `controller` property of another view, although this usage will (by default) * result in the removal of its own view from the {@link module:enyo/Component~Component} bubbling hierarchy. * -* Note that `enyo.ViewController` may have components defined in its +* Note that `enyo/ViewController` may have components defined in its * `components` [array]{@glossary Array}, but these components should -* not be `enyo.Controls`. +* not be instances of `enyo/Control`. * * @class ViewController * @extends module:enyo/Controller~Controller From 570625c741c785e53a296236bc5da25b4e9f6a09 Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Mon, 7 Dec 2015 11:51:27 -0800 Subject: [PATCH 184/195] JSDoc cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/BackgroundTaskManager.js | 18 ++++++++--------- src/FluxStore.js | 39 +++++++++--------------------------- src/FormData.js | 4 ++-- src/XhrSource.js | 2 +- 4 files changed, 22 insertions(+), 41 deletions(-) diff --git a/src/BackgroundTaskManager.js b/src/BackgroundTaskManager.js index f5f15d7a2..58fa189c6 100644 --- a/src/BackgroundTaskManager.js +++ b/src/BackgroundTaskManager.js @@ -75,7 +75,7 @@ module.exports = kind.singleton( }, /** - * Add a customer to the queue. + * Adds a customer to the queue. * * @param {Object} customer - The item (customer) to add to the queue. * @param {String} nom - The name of the customer for later reference. @@ -91,7 +91,7 @@ module.exports = kind.singleton( }, /** - * Remove a specific customer. + * Removes a specific customer. * * @param {String} nom - The name of the customer to remove from the queue. * @public @@ -121,7 +121,7 @@ module.exports = kind.singleton( }, /** - * Clear the queue of customers. + * Clears the queue of customers. * * @public */ @@ -136,7 +136,7 @@ module.exports = kind.singleton( }, /** - * Iterate through customer queue and pause each customer. + * Iterates through customer queue and pauses each customer. * * @public */ @@ -149,7 +149,7 @@ module.exports = kind.singleton( }, /** - * Iterate through customer queue and resume each customer. + * Iterates through customer queue and resumes each customer. * * @public */ @@ -173,9 +173,9 @@ module.exports = kind.singleton( }, /** - * Determines whether any of our customers has a task. + * Determines whether any customers have a task. * - * @returns {Boolean} If `true`, we have at least one customer with a task; `false` otherwise. + * @returns {Boolean} `true` if at least one customer has a task; otherwise, `false`. * @private */ hasActiveTasks: function () { @@ -188,7 +188,7 @@ module.exports = kind.singleton( * Determines whether the priority of the last task added to a given customer is urgent * enough to move the customer to the front of the queue. * - * @param {Object} customer - The customer which has had a change in priority for one of its + * @param {Object} customer - The customer that has had a change in priority for one of its * tasks. * @param {Number} priority - The priority that will be checked for urgency. * @private @@ -218,7 +218,7 @@ module.exports = kind.singleton( }, /** - * Give the next customer a chance to execute a single task. + * Gives the next customer a chance to execute a single task. * * @private */ diff --git a/src/FluxStore.js b/src/FluxStore.js index 24fe9a80c..57ac44915 100644 --- a/src/FluxStore.js +++ b/src/FluxStore.js @@ -58,11 +58,8 @@ module.exports = kind( /** - * @name enyo.FluxStore.id - * - * How a store is identitified to the Flux Dispatcher - * This ID is used for subscribing to a store's - * state notification change + * How a store is identitified to the Flux Dispatcher. This ID is used for + * subscribing to a store's state notification change. * * @public * @type {Number} @@ -73,10 +70,7 @@ module.exports = kind( mixins: [EventEmitter, StateSupport], /** - * @name enyo.FluxStore.source - * - * The source that this FluxStore should use to fetch new - * data sets. + * The source that this FluxStore should use to fetch new data sets. * * @public * @type {String} @@ -87,11 +81,8 @@ module.exports = kind( published: { /** - * @name enyo.FluxStore.MergeRoot - * - * When a source sends data to the store, - * should the data root have the new data - * merged, otherwise it will replace. + * When a source sends data to the store, determines whether the data root + * has the new data merged. If `false`, data will be replaced instead. * * @public * @type {Boolean} @@ -116,9 +107,7 @@ module.exports = kind( }), /** - * @name enyo.FluxStore.add - * - * Adds data to the store, is called from the store's fetch + * Adds data to the store; called from the store's fetch. * * @param [data] - Object that has the data to be added to store. * @param {module:enyo/FluxStore~FluxStore~ActionOptions} [opts] - Optional configuration options. @@ -133,9 +122,7 @@ module.exports = kind( }, /** - * @name enyo.FluxStore.reset - * - * Clears the store's data + * Clears the store's data. * * @public */ @@ -144,8 +131,6 @@ module.exports = kind( }, /** - * @name enyo.FluxStore.fetch - * * Fetches the data from a [Source]{@link module:enyo/Source~Source} * * @param {module:enyo/FluxStore~FluxStore~ActionOptions} [opts] - Optional configuration options. @@ -164,11 +149,9 @@ module.exports = kind( }, /** - * @name enyo.FluxStore.success + * Success callback called when the [Source]{@link module:enyo/Source~Source} is successful. * - * Success callback is called when the [Source]{@link module:enyo/Source~Source} is successful - * - * @param {module:enyo/Source~Source} [source] - The source that iniated the fetch. + * @param {module:enyo/Source~Source} [source] - The source that initiated the fetch. * @param {module:enyo/Source~Source~Results} [res] - The result of the fetch. * @private */ @@ -184,9 +167,7 @@ module.exports = kind( }, /** - * @name enyo.FluxStore.error - * - * Error callback is called when the [Source]{@link module:enyo/Source~Source} has failed + * Error callback called when the [Source]{@link module:enyo/Source~Source} has failed. * * @param {module:enyo/Source~Source~Results} [res] - The result of the fetch. * @private diff --git a/src/FormData.js b/src/FormData.js index c024333bc..bdf55fbc7 100644 --- a/src/FormData.js +++ b/src/FormData.js @@ -9,8 +9,8 @@ var * internal [enyo/Blob]{@link module:enyo/Blob} [kind]{@glossary kind} is the * content provider for file-parts. * -* Note that in Internet Explorer < 10, both `enyo/FormData` and `enyo.Blob` are -* limited to [string]{@glossary String} content and `enyo.Blob` may only be +* Note that in Internet Explorer < 10, both `enyo/FormData` and `enyo/Blob` are +* limited to [string]{@glossary String} content and `enyo/Blob` may only be * instantiated using an [array]{@glossary Array} of [strings]{@glossary String}. * * This implementation is inspired by diff --git a/src/XhrSource.js b/src/XhrSource.js index 9c554a81e..283fdf4d7 100644 --- a/src/XhrSource.js +++ b/src/XhrSource.js @@ -59,7 +59,7 @@ var XhrSource = module.exports = kind( * These options will be merged, first with [subkinds]{@glossary subkind}, and then * with any options passed to the various API methods of {@link module:enyo/XhrSource~XhrSource}. While the * options passed to the methods may include any properties, only properties found in - * [enyo.AjaxProperties]{@link module:enyo/AjaxProperties#AjaxProperties} will be merged and passed to the + * [enyo/AjaxProperties]{@link module:enyo/AjaxProperties} will be merged and passed to the * [requestKind]{@link module:enyo/XhrSource~XhrSource#requestKind}. * * @see module:enyo/AjaxProperties From c5ad5f5c34d7a66f963fb9c00c3aec168b7dfcc4 Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Mon, 7 Dec 2015 16:00:02 -0800 Subject: [PATCH 185/195] JSDoc cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/ScrollMath.js | 2 +- src/gesture/gesture.js | 1 - src/i18n.js | 8 +++++--- src/job.js | 11 ++++++----- src/jobs.js | 2 +- src/kind.js | 42 +++++++++++++++++++++++------------------- 6 files changed, 36 insertions(+), 30 deletions(-) diff --git a/src/ScrollMath.js b/src/ScrollMath.js index 8656bbfd2..ca759576b 100644 --- a/src/ScrollMath.js +++ b/src/ScrollMath.js @@ -55,7 +55,7 @@ var * helper [kind]{@glossary kind} used by other [scroller]{@link module:enyo/Scroller~Scroller} * kinds, such as {@link module:enyo/TouchScrollStrategy~TouchScrollStrategy}. * -* `enyo.ScrollMath` is not typically created in application code. +* `enyo/ScrollMath` is not typically created in application code. * * @class ScrollMath * @protected diff --git a/src/gesture/gesture.js b/src/gesture/gesture.js index 86d4c5411..4eaf02179 100644 --- a/src/gesture/gesture.js +++ b/src/gesture/gesture.js @@ -26,7 +26,6 @@ var * documentation on [Event Handling]{@linkplain $dev-guide/key-concepts/event-handling.html} * in the Enyo Developer Guide. * -* @module enyo/gesture * @public */ var gesture = module.exports = { diff --git a/src/i18n.js b/src/i18n.js index 1cf8c5b6b..f13b09380 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -1,5 +1,5 @@ /** -* A set of extensible methods to enable internationalization of enyo applications +* A set of extensible methods to enable internationalization of Enyo applications. * * @module enyo/i18n */ @@ -15,7 +15,9 @@ var * preparation for localization. If a i18n library is not loaded, this function will return the * string as is. * -* `$L('Welcome')` +* ```javascript +* $L('Welcome'); +* ``` * * If a compatible i18n library is loaded, this function will be replaced by the i18n library's * version, which translates wrapped strings to strings from a developer-provided resource file @@ -38,7 +40,7 @@ exports.$L = new utils.Extensible(function (str) { * * Enyo registers an event listener for the `localechange` event and broadcasts the * `onlocalechange` signal when the locale has changed. Before broadcasting, Enyo calls -* `enyo.updateLocale()`. The default implementation of `enyo.updateLocale()` is a stub, but a +* `i18n.updateLocale()`. The default implementation of `i18n.updateLocale()` is a stub, but an * i18n library may override it to update its internal state before the `onlocalechange` signal * is broadcast. * diff --git a/src/job.js b/src/job.js index 2072743c8..6ccfda557 100644 --- a/src/job.js +++ b/src/job.js @@ -7,19 +7,20 @@ require('enyo'); var _jobs = {}; /** -* Runs a job after the specified amount of time has elapsed +* Runs a job after a specified amount of time has elapsed * since a job with the same name has run. * * Jobs can be used to throttle behaviors. If some event may occur one time or * multiple times, but we want a response to occur only once every `n` seconds, * we can use a job. * -* @example +* ``` * onscroll: function() { -* // updateThumb will be called, but only when 1 second has elapsed since the -* // last onscroll -* exports("updateThumb", this.bindSafely("updateThumb"), 1000); +* // updateThumb will be called, but only when 1 second has elapsed since the +* // last onscroll +* exports("updateThumb", this.bindSafely("updateThumb"), 1000); * } +* ``` * * @param {String} nom - The name of the job to throttle. * @param {(Function|String)} job - Either the name of a method or a [function]{@glossary Function} diff --git a/src/jobs.js b/src/jobs.js index e4db5e1f8..5582a6f0f 100644 --- a/src/jobs.js +++ b/src/jobs.js @@ -17,7 +17,7 @@ var CoreObject = require('./CoreObject'); * animations. To maintain backward compatibility, jobs are assigned a priority * of 5 by default; thus they are not blocked by animations. * -* Normally, application code will not use `enyo.jobs` directly, but will +* Normally, application code will not use `enyo/jobs` directly, but will * instead use the [job()]{@link module:enyo/Component~Component#job} method of * {@link module:enyo/Component~Component}. * diff --git a/src/kind.js b/src/kind.js index 98dc8f50e..a099198fd 100644 --- a/src/kind.js +++ b/src/kind.js @@ -7,18 +7,18 @@ var var defaultCtor = null; /** -* Creates a JavaScript [constructor]{@glossary constructor} function with +* Creates a JavaScript {@glossary constructor} function with * a prototype defined by `props`. **All constructors must have a unique name.** * -* `enyo.kind()` makes it easy to build a constructor-with-prototype (like a +* `kind()` makes it easy to build a constructor-with-prototype (like a * class) that has advanced features like prototype-chaining -* ([inheritance]{@glossary inheritance}). +* ({@glossary inheritance}). * * A plug-in system is included for extending the abilities of the -* [kind]{@glossary kind} generator, and constructors are allowed to +* {@glossary kind} generator, and constructors are allowed to * perform custom operations when subclassed. * -* If you make changes to `enyo.kind()`, be sure to add or update the appropriate +* If you make changes to `enyo/kind`, be sure to add or update the appropriate * [unit tests](@link https://github.com/enyojs/enyo/tree/master/tools/test/core/tests). * * For more information, see the documentation on @@ -26,7 +26,7 @@ var defaultCtor = null; * * @module enyo/kind * @param {Object} props - A [hash]{@glossary Object} of properties used to define and create -* the [kind]{@glossary kind} +* the {@glossary kind} * @public */ /*jshint -W120*/ @@ -101,24 +101,28 @@ var getDefaultCtor = exports.getDefaultCtor = function () { var concatenated = exports.concatenated = []; /** -* Creates a singleton of a given [kind]{@glossary kind} with a given +* Creates a singleton of a given {@glossary kind} with a given * definition. **The `name` property will be the instance name of the singleton * and must be unique.** * * ```javascript -* enyo.singleton({ -* kind: 'enyo.Control', -* name: 'app.MySingleton', -* published: { -* value: 'foo' -* }, -* makeSomething: function() { -* //... -* } -* }); +* var +* kind = require('enyo/kind'), +* Control = require('enyo/Control'); * -* app.MySingleton.makeSomething(); -* app.MySingleton.setValue('bar'); +* module.exports = singleton({ +* kind: Control, +* name: 'app.MySingleton', +* published: { +* value: 'foo' +* }, +* makeSomething: function() { +* //... +* } +* }); +* +* app.MySingleton.makeSomething(); +* app.MySingleton.setValue('bar'); *``` * * @public From a6978304840f0847bb831b6c622b2f1c58da06e0 Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Mon, 14 Dec 2015 11:45:00 -0800 Subject: [PATCH 186/195] JSDoc cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/i18n.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/i18n.js b/src/i18n.js index f13b09380..861ef5c9d 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -11,17 +11,17 @@ var Signals = require('./Signals'); /** -* Provides a stub function for i18n string translation. This allows strings to be wrapped in -* preparation for localization. If a i18n library is not loaded, this function will return the -* string as is. -* +* Provides a stub function for i18n string translation. This allows strings to +* be wrapped in preparation for localization. If an i18n library is not loaded, +* this function will return the string as is. +* * ```javascript -* $L('Welcome'); +* $L('Welcome'); * ``` -* -* If a compatible i18n library is loaded, this function will be replaced by the i18n library's -* version, which translates wrapped strings to strings from a developer-provided resource file -* corresponding to the current user locale. +* +* If a compatible i18n library is loaded, this function will be replaced by the +* i18n library's version, which translates wrapped strings to strings from a +* developer-provided resource file corresponding to the current user locale. * * @param {String} str - The {@glossary String} to translate. * @returns {String} The translated {@glossary String}. From 342ff30a92e2e9b3d52ac4e351d0a690d0e81d04 Mon Sep 17 00:00:00 2001 From: Jim Tang Date: Mon, 14 Dec 2015 14:48:47 -0800 Subject: [PATCH 187/195] Doc cleanup Enyo-DCO-1.1-Signed-Off-By: Jim Tang (jim.tang@lge.com) --- src/Control/Control.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Control/Control.js b/src/Control/Control.js index 79895a2d9..ae2d83aab 100644 --- a/src/Control/Control.js +++ b/src/Control/Control.js @@ -89,7 +89,7 @@ function getNodePurgatory () { * [Controls]{@linkplain $dev-guide/key-concepts/controls.html} in the * Enyo Developer Guide. * -* **If you make changes to `enyo.Control`, be sure to add or update the +* **If you make changes to `enyo/Control`, be sure to add or update the * appropriate unit tests.** * * @class Control From 3eb8b56b1beea255dc7e363f9c97aff8c9efa489 Mon Sep 17 00:00:00 2001 From: Madala Cholan Satyanarayana Date: Tue, 15 Dec 2015 19:17:35 +0530 Subject: [PATCH 188/195] New approach with seeking in animations Enyo-DCO-1.1-Signed-off-by:Madala Satyanarayana --- src/AnimationSupport/AnimationSupport.js | 98 ++++++------- src/AnimationSupport/Core.js | 34 ++--- src/AnimationSupport/Director.js | 92 +++++++++++++ src/AnimationSupport/EventDelegator.js | 24 ++-- src/AnimationSupport/Frame.js | 19 +-- src/AnimationSupport/FrameEditor.js | 63 +++++++++ src/AnimationSupport/Tween.js | 168 +++++++---------------- src/AnimationSupport/Vector.js | 142 +++++++++++-------- 8 files changed, 363 insertions(+), 277 deletions(-) create mode 100644 src/AnimationSupport/Director.js create mode 100644 src/AnimationSupport/FrameEditor.js diff --git a/src/AnimationSupport/AnimationSupport.js b/src/AnimationSupport/AnimationSupport.js index 590c51884..c1ee82446 100644 --- a/src/AnimationSupport/AnimationSupport.js +++ b/src/AnimationSupport/AnimationSupport.js @@ -5,6 +5,8 @@ var animation = require('./Core'), activator = require('./KeyFrame'), delegator = require('./EventDelegator'), + EventEmitter = require('../EventEmitter'), + FrameEditor = require('./FrameEditor'), frame = require('./Frame'), utils = require('../utils'); @@ -46,12 +48,30 @@ var AnimationSupport = { */ animDelta: [], + + vScrollX: 0, + + vScrollY: 0, + + vScrollZ: 0, + + prevDur: 0, + + totalDuration: 0, + + /** * Maximum threshold for animation * @private */ animMaxThreshold: [], + _animPose: [], + + _pose: [], + + mixins: [EventEmitter, FrameEditor], + /** * Check if the character is suitable for animation * @public @@ -79,7 +99,7 @@ var AnimationSupport = { * @public */ setDistance: function (dist) { - this._distance = dist; + this.distance = dist; }, /** @@ -87,40 +107,35 @@ var AnimationSupport = { * @public */ getDistance: function () { - return this._distance; + return this.distance; }, /** * Gets current state of animation for this character - * @parameter accelerate- Turns on/off hardware acceleration * @public */ initiate: function (current) { - var dom = this.hasNode(), - prop = this.getAnimation(), - init = frame.getCompoutedProperty(dom, prop, current); - - utils.mixin(this, init); - }, - - /** - * Gets animations applied to this chracter. - * @public - */ - getAnimation: function() { - return this._prop || (this._prop = this.animate); + var dom = this.hasNode(), dur, + pose = frame.getComputedProperty(dom, undefined, current); + pose.duration = 0; + this._animPose.push(pose); + this.currentState = pose.currentState; + frame.accelerate(dom, pose.matrix); + + if(this.animate !== true) { + dur = this.getDuration() || 0; + this.addAnimation(this.animate, dur); + } }, /** * Adds new animation on already existing animation for this character. * @public */ - addAnimation: function (newProp) { - if (this._prop === undefined || this._prop == true) { - this._prop = newProp; - } else { - utils.mixin(this._prop, newProp); - } + addAnimation: function (newProp, duration) { + this.prevDur = duration || this.prevDur; + this.totalDuration += this.prevDur; + this._animPose.push({animate: newProp, duration: this.totalDuration}); }, /** @@ -161,39 +176,12 @@ var AnimationSupport = { * @public */ start: function (active, delay) { - this._duration = parseInt(this.getDuration(), 10); this._startTime = utils.perfNow() + (delay || 0) ; this._lastTime = this._startTime + this._duration; this.animating = true; this.active = active; - this.initiate(this.currentState); }, - /** - * Halt existing animation of this character - * @public - */ - pause: function () { - this.animating = false; - this.set('animationState', 'paused'); - }, - - /** - * Halt all existing animations - * @public - */ - pauseAll: function () { - animation.pause(); - }, - - /** - * Resume the paused animation of this character - * @public - */ - resume: function () { - this.animating = true; - this.set('animationState', 'resumed'); - }, /** * @private */ @@ -201,7 +189,6 @@ var AnimationSupport = { return function () { sup.apply(this, arguments); this.initiate(); - frame.accelerate(this.hasNode(), this.matrix); if (this.handleAnimationEvents) { delegator.register(this); } @@ -216,7 +203,7 @@ var AnimationSupport = { animation.remove(this); sup.apply(this, arguments); }; - }), + }) }; module.exports = AnimationSupport; @@ -234,10 +221,11 @@ kind.concatHandler = function (ctor, props, instance) { if (props.animate || props.keyFrame || props.pattern || props.handleAnimationEvents) { var proto = ctor.prototype || ctor; extend(AnimationSupport, proto); - if (props.keyFrame && typeof props.keyFrame != 'function') { - activator.animate(proto, props); - } - if (props.animate && typeof props.animate != 'function') { + // if (props.keyFrame && typeof props.keyFrame != 'function') { + // activator.animate(proto, props); + // } + if ((props.animate && typeof props.animate != 'function' ) || + (props.keyFrame && typeof props.keyFrame != 'function')) { animation.trigger(proto); } if (props.handleAnimationEvents && typeof props.handleAnimationEvents != 'function') { diff --git a/src/AnimationSupport/Core.js b/src/AnimationSupport/Core.js index 45ef0d181..db2c4f855 100644 --- a/src/AnimationSupport/Core.js +++ b/src/AnimationSupport/Core.js @@ -4,9 +4,9 @@ var kind = require('../kind'), animation = require('../animation'), utils = require('../utils'), - tween = require('./Tween'); + director = require('./Director'); -var +var ts, wasTs, CoreObject = require('../CoreObject'); /** @@ -134,7 +134,7 @@ module.exports = kind.singleton({ register: function (charc) { this.deRegister(charc); this.evnts.push(charc); - //this.remove(charc); + this.remove(charc); charc.animating = true; if (!this.isTicking) { @@ -145,7 +145,7 @@ module.exports = kind.singleton({ deRegister: function (curr) { var idx = this.evnts.indexOf(curr); - if (idx >= 0) this.evnts.splice(idx, 1); + if (idx >= 0) this.evnts.splice(idx, 1); }, /** @@ -167,8 +167,7 @@ module.exports = kind.singleton({ */ loop: function () { var i, curr, - len = this.chracs.length, - ts; + len = this.chracs.length; if (len <= 0) { this.cancel(); @@ -176,21 +175,14 @@ module.exports = kind.singleton({ return; } + ts = utils.perfNow(); for (i = 0; i < len; i++) { curr = this.chracs[i]; if (curr && curr.ready()) { - ts = utils.perfNow(); - tween.update(curr, ts); - if (!curr._lastTime || ts >= curr._lastTime) { - tween.complete(curr); - curr.completed(curr); - curr.set('animationState', 'completed'); - if(!curr.active) { - this.remove(curr); - } - } + director.update(curr, ts - (wasTs || ts)); } } + wasTs = ts; this.start(); }, @@ -201,13 +193,13 @@ module.exports = kind.singleton({ var i, curr, evlen = this.evnts.length; for (i = 0; i < evlen; i++) { curr = this.evnts[i]; - if (typeof this.evnts[i].commitAnimation === 'function') { - this.evnts[i].commitAnimation(); - } + if (this.evnts[i].patterns && typeof this.evnts[i].patterns.length > 0) { + this.evnts[i].commitAnimation(); + } if (curr && curr.ready()) { - tween.updateDelta(curr); + director.updateDelta(curr); if (!curr.animating) { - tween.complete(curr); + director.complete(curr); curr.completed(curr); } } diff --git a/src/AnimationSupport/Director.js b/src/AnimationSupport/Director.js new file mode 100644 index 000000000..b097d1b70 --- /dev/null +++ b/src/AnimationSupport/Director.js @@ -0,0 +1,92 @@ +require('enyo'); + +var frame = require('./Frame'), + tween = require('./Tween'), + utils = require('../utils'); + +/** +* This module returns the Loop singleton +* Core module is responsible for handling all animations happening in Enyo. +* The responsibilities of this module is to; +* - Trigger vendor specific rAF. +* - Knowing all elements which have requested for animation. +* - Tween animation frames for each characters. +* +* @module enyo/Core +*/ +module.exports = { +/** + * Tweens public API which notifies to change current state of + * a character. This method is normally trigger by the Animation Core to + * update the animating characters state based on the current timestamp. + * + * As of now this method is provided as an interface for application + * to directly trigger an animation. However, this will be later made private + * and will be accessible only by the interfaces exposed by framework. + * @parameter chrac- Animating character + * ts- DOMHighResTimeStamp + * + * @public + */ + + update: function(actor, ts) { + var dur = actor.totalDuration, + tm = actor.rolePlay(ts); + + if (tm < 0) return; + if (tm <= dur) { + this.action(actor, tm); + } else { + this.cut(actor); + } + }, + + cut: function (actor) { + actor.animating = false; + actor._timeline = undefined; + actor.completed(actor); + actor.set('animationState', 'completed'); + if (!actor.active) { + // this.remove(actor); + } + }, + + action: function(actor, since) { + var pose, t, + index = this.poseByTime(actor._animPose, since), + props = actor._animPose[index], + prevDur = actor._animPose[(index - 1) < 0 ? 0 : (index - 1)].duration, + currentAnimSince = since - prevDur, + runningDur = props.duration - prevDur; + if (!props._startAnim) { + pose = frame.getComputedProperty(actor.hasNode(), props.animate, actor.currentState); + utils.mixin(props, pose); + } + + if (currentAnimSince < 0) return; + if (currentAnimSince <= runningDur) { + t = currentAnimSince / runningDur; + tween.step(actor, props, t, runningDur); + } else { + tween.step(actor, props, 1, runningDur); + } + }, + + poseByTime: function(arr, duration) { + var startIndex = 0, + stopIndex = arr.length - 1, + middle = Math.floor((stopIndex + startIndex) / 2); + + while (arr[middle].duration != duration && startIndex < stopIndex) { + if (duration < arr[middle].duration) { + stopIndex = middle; + } else if (duration > arr[middle].duration) { + startIndex = middle + 1; + } + + middle = Math.floor((stopIndex + startIndex) / 2); + } + + return (arr[middle].duration != duration) ? startIndex : middle; + } +}; \ No newline at end of file diff --git a/src/AnimationSupport/EventDelegator.js b/src/AnimationSupport/EventDelegator.js index fbec79f05..01298c901 100644 --- a/src/AnimationSupport/EventDelegator.js +++ b/src/AnimationSupport/EventDelegator.js @@ -98,9 +98,9 @@ var EventDelegator = { y = ev.targetTouches[0].pageY; if(x !== 0 || y !== 0) { - sender.animDelta[0] = sender.touchX - x; - sender.animDelta[1] = sender.touchY - y; - sender.animDelta[2] = 0; + sender.vScrollX = sender.touchX - x; + sender.vScrollY = sender.touchY - y; + sender.vScrollZ = 0; sender.touchX = x; sender.touchY = y; } @@ -136,9 +136,9 @@ var EventDelegator = { this.deltaY = scrollTop; - this.animDelta[0] = delta; - this.animDelta[1] = 0; - this.animDelta[2] = 0; + this.vScrollX = delta; + this.vScrollY = 0; + this.vScrollZ = 0; }, /** @@ -162,9 +162,9 @@ var EventDelegator = { this.dragLeft = dragLeft, this.dragTop = dragTop; - this.animDelta[0] = this.deltaX; - this.animDelta[1] = this.deltaY; - this.animDelta[2] = 0; + this.vScrollX = this.deltaX; + this.vScrollY = this.deltaY; + this.vScrollZ = 0; } }, @@ -172,9 +172,9 @@ var EventDelegator = { * @private */ mousewheelEvent: function (sender, ev) { - sender.animDelta[0] = ev.deltaY; - sender.animDelta[1] = ev.deltaX; - sender.animDelta[2] = 0; + sender.vScrollX = ev.deltaY; + sender.vScrollY = ev.deltaX; + sender.vScrollZ = 0; } }; diff --git a/src/AnimationSupport/Frame.js b/src/AnimationSupport/Frame.js index f0d9824b7..2393d0f77 100644 --- a/src/AnimationSupport/Frame.js +++ b/src/AnimationSupport/Frame.js @@ -305,25 +305,26 @@ var frame = module.exports = { * initial-Default properties to be applied. * @public */ - getCompoutedProperty: function (node, props, initial) { - if(!node || !props) return; + getComputedProperty: function (node, props, initial) { + if(!node) return; var eP = {}, sP = initial ? this.copy(initial) : {}, tP = {}, dP = {}, m, k, v, - s = Dom.getComputedStyle(node); + s = initial ? undefined : Dom.getComputedStyle(node); for (k in props) { v = sP[k]; if (!this.isTransform(k)) { - v = v || this.getStyleValue(s, k); + v = v || this.getStyleValue(s || Dom.getComputedStyle(node), k); eP[k] = this.parseValue(props[k]); sP[k] = this.parseValue(v); } else { v = this.parseValue(props[k]); - tP[k] = k == 'rotate' ? Vector.toQuant(v) : v; + //tP[k] = k == 'rotate' ? Vector.toQuant(v) : v; + tP[k] = v; } } @@ -334,7 +335,7 @@ var frame = module.exports = { dP.skew = initial.skew; dP.perspective = initial.perspective; } else { - m = this.getMatrix(s) || Matrix.identity(); + m = this.getMatrix(s || Dom.getComputedStyle(node)) || Matrix.identity(); this.decomposeMatrix(m, dP); } @@ -342,15 +343,15 @@ var frame = module.exports = { sP[k] = dP[k]; eP[k] = tP[k] || dP[k]; } - return {_startAnim: sP, _endAnim: eP, _transform: dP, currentState: dP, matrix: m}; + return {_startAnim: sP, _endAnim: eP, _transform: dP, currentState: dP, matrix: m, props: props}; }, getComputedDistance: function (prop, initalProp, finalProp) { var k, sV, eV, dst, tot = 0; for (k in prop) { - sV = initalProp[k]; + sV = k==='rotate' ? Vector.quantToVector(initalProp[k]) : initalProp[k]; eV = finalProp[k]; - dst = (k == 'rotate' ? Vector.quantDistance : Vector.distance)(eV, sV); + dst = Vector.distance(eV, sV); tot += dst; } return tot; diff --git a/src/AnimationSupport/FrameEditor.js b/src/AnimationSupport/FrameEditor.js new file mode 100644 index 000000000..20632f5fb --- /dev/null +++ b/src/AnimationSupport/FrameEditor.js @@ -0,0 +1,63 @@ +require('enyo'); + +module.exports = { +/** + * + * + * @public + */ + + timeline: 0, + _cachedValue: 0, + _frameSpeed: 1, + + + cache: function(){ + if(this._frameSpeed === 0){ + this._frameSpeed = this._cachedValue; + } + }, + play : function (){ + this._frameSpeed = 1; + }, + + resume: function() { + this.cache(); + this._frameSpeed *= 1; + }, + + pause: function () { + this._cachedValue = this._frameSpeed; + this._frameSpeed = 0; + }, + + reverse: function () { + this.cache(); + this._frameSpeed *= -1; + }, + + fast: function (mul) { + this.cache(); + this._frameSpeed *= mul; + }, + + slow: function (mul) { + this.cache(); + this._frameSpeed *= mul; + }, + + stop: function () { + this._cachedValue = 1; + this._frameSpeed = 0; + this.timeline = 0; + }, + + rolePlay: function (t) { + this.timeline += _rolePlay(t, this._frameSpeed); + return this.timeline; + } +}; + +function _rolePlay(t, mul) { + return mul * t; +} \ No newline at end of file diff --git a/src/AnimationSupport/Tween.js b/src/AnimationSupport/Tween.js index 3457e3374..56aed1b5e 100644 --- a/src/AnimationSupport/Tween.js +++ b/src/AnimationSupport/Tween.js @@ -2,8 +2,10 @@ require('enyo'); var frame = require('./Frame'), - easings = require('./Easings'), - Vector = require('./Vector'); + easings = require('./Easings'), + matrixUtil = require('./Matrix'), + Vector = require('./Vector'), + utils = require('../utils'); var oldState, newState, node, matrix, cState = []; /** @@ -15,147 +17,73 @@ var oldState, newState, node, matrix, cState = []; * @module enyo/AnimationSupport/Tween */ module.exports = { - /** - * Tweens public API which notifies to change current state of - * a character. This method is normally trigger by the Animation Core to - * update the animating characters state based on the current timestamp. - * - * As of now this method is provided as an interface for application - * to directly trigger an animation. However, this will be later made private - * and will be accessible only by the interfaces exposed by framework. - * @parameter chrac- Animating character - * ts- DOMHighResTimeStamp - * - * @public - */ - update: function(charc, ts) { - var t, - dur = charc._duration, - since = ts - charc._startTime; - - if (since < 0) return; - if (since <= dur) { - t = since / dur; - this.step(charc, t); - } else { - this.step(charc, 1); - } - }, - - /** - * Tweens public API which notifies to change current state of - * a character based on its interaction delta. This method is normally trigger by the Animation Core to - * update the animating characters state based on the current delta change. - * - * As of now this method is provided as an interface for application - * to directly trigger an animation. However, this will be later made private - * and will be accessible only by the interfaces exposed by framework. - * @parameter chrac- Animating character - * dt- An array of delta dimensions like [x,y,z] - * - * @public - */ - updateDelta: function(charc, dt) { - var d, - st, - dst, - inc, - frc = charc.friction || 1, - trD = charc.animThresholdDuration || 1000, - trh = charc.animMaxThreshold || false, - dir = charc.direction || 0, - tot = charc.getDistance() || frame.getComputedDistance( - charc.getAnimation(), - charc._startAnim, - charc._endAnim); - - dt = dt || charc.animDelta[dir]; - if (dt) { - dt = frc * dt; - dst = charc._animCurDistane || 0; - inc = dst + dt * (charc.reverse ? -1 : 1); - st = inc > 0 && inc <= tot; - - if (st) { - d = inc / tot; - if (trh && inc > trh && dt === 0) { - charc.setDuration(trD); - charc.start(true); - } - this.step(charc, d); - } else { - charc.animating = st; - charc.reverse = inc <= 0; - } - - charc._animCurDistane = st ? inc : 0; - charc.animDelta = []; - } - }, - + /** * @private */ - step: function(charc, t) { - var k, c, d, pts, props; + step: function(charc, pose, t, d) { + var k, c, pts, tState, oState, ease; node = charc.node; - newState = charc._endAnim; - d = charc._duration; - props = charc.getAnimation(); - oldState = charc._startAnim; + newState = pose._endAnim; + ease = pose.animate ? pose.animate.ease: this.ease; + oldState = pose._startAnim; charc.currentState = charc.currentState || {}; - - - for (k in props) { - cState = frame.copy(charc.currentState[k] || []); - if (newState[k]) { - if (charc.ease && (typeof charc.ease !== 'function')) { - if ((k == 'rotate')) { - - pts = this.beizerSPoints(charc.ease, frame.copy(oldState[k]), frame.copy(newState[k]), props[k]); - cState = this.beizerSpline(t, pts, cState); - - } else { - var checkEaseChange = easings.easeChanged(charc.ease); + if(pose.props){ + for (k in pose.props) { + cState = frame.copy(charc.currentState[k] || []); + if (newState[k]) { + if (ease && (typeof ease !== 'function')) { + var checkEaseChange = easings.easeChanged(ease); var propChange = easings.propChange(k); if (!charc.controlPoints || (propChange === true || checkEaseChange === true)) { // for the first time or either of Ease/Propery changed - charc.controlPoints = easings.calculateEase(charc.ease, frame.copy(oldState[k]), frame.copy(newState[k])); + charc.controlPoints = easings.calculateEase(ease, frame.copy(oldState[k]), frame.copy(newState[k])); } else if (propChange === false && checkEaseChange === false) { // for the cases where property and ease remain same and the states are varying var oldStateCheck = easings.oldStateChange(frame.copy(oldState[k])); var newStateCheck = easings.newStateChange(frame.copy(newState[k])); if (oldStateCheck === true || newStateCheck === true) { - charc.controlPoints = easings.calculateEase(charc.ease, frame.copy(oldState[k]), frame.copy(newState[k])); + charc.controlPoints = easings.calculateEase(ease, frame.copy(oldState[k]), frame.copy(newState[k])); } } cState = this.getBezier(t, charc.controlPoints, cState); + if (k == 'rotate') + cState = Vector.toQuant(cState); + } else { + if (k == 'rotate') { + tState = Vector.toQuant(newState[k]); + oState = Vector.toQuant(oldState[k]); + c = this.slerp; + } else { + tState = newState[k]; + oState = oldState[k]; + c = this.lerp; + } + cState = c(oState, tState, ease(t, d), cState); } - } else { - c = k == 'rotate' ? this.slerp : this.lerp; - cState = t ? c(oldState[k], newState[k], ((typeof charc.ease === 'function') ? charc.ease : this.ease)(t, d), cState) : newState[k]; } - } - if (!frame.isTransform(k)) { - frame.setProperty(node, k, cState); + if (!frame.isTransform(k)) { + frame.setProperty(node, k, cState); + } + charc.currentState[k] = cState; } - charc.currentState[k] = cState; } - - if (charc._transform) { - matrix = frame.recomposeMatrix( - charc.currentState.translate, - charc.currentState.rotate, - charc.currentState.scale, - charc.currentState.skew, - charc.currentState.perspective - ); - frame.accelerate(node, matrix); + else{ + utils.mixin(charc.currentState,oldState); } + matrix = frame.recomposeMatrix( + charc.currentState.translate, + charc.currentState.rotate, + charc.currentState.scale, + charc.currentState.skew, + charc.currentState.perspective + ); + frame.accelerate(node, matrix); + charc.animationStep && charc.animationStep(t); }, @@ -265,10 +193,6 @@ module.exports = { }, - complete: function(charc) { - charc.animating = false; - charc._prop = undefined; - }, lerp: function(vA, vB, t, vR) { if (!vR) vR = []; diff --git a/src/AnimationSupport/Vector.js b/src/AnimationSupport/Vector.js index a1094d950..7d46b85b6 100644 --- a/src/AnimationSupport/Vector.js +++ b/src/AnimationSupport/Vector.js @@ -11,7 +11,7 @@ module.exports = { * Divides vector with a scalar value. * @public */ - divide: function(v, s) { + divide: function (v, s) { return [v[0] / s, v[1] / s, v[2] / s]; }, @@ -19,31 +19,33 @@ module.exports = { * Add vector/quant with a vector/quant. * @public */ - add: function(q1, q2) { - q1[0] += q2[0]; - q1[1] += q2[1]; - q1[2] += q2[2]; - if (q1.length > 3 && q2.length > 3) q1[3] += q2[3]; - return q1; + add: function (q1, q2) { + var q3 = []; + q3[0] = q1[0] + q2[0]; + q3[1] = q1[1] + q2[1]; + q3[2] = q1[2] + q2[2]; + if (q1.length > 3 && q2.length > 3) q3[3] = q1[3] + q2[3]; + return q3; }, /** * Sub vector/quant with a vector/quant. * @public */ - subtract: function(q1, q2) { - q1[0] -= q2[0]; - q1[1] -= q2[1]; - q1[2] -= q2[2]; - if (q1.length > 3 && q2.length > 3) q1[3] -= q2[3]; - return q1; + subtract: function (q1, q2) { + var q3 = []; + q3[0] = q1[0] - q2[0]; + q3[1] = q1[1] - q2[1]; + q3[2] = q1[2] - q2[2]; + if (q1.length > 3 && q2.length > 3) q3[3] = q1[3] - q2[3]; + return q3; }, /** * Multiply vector/quant with a vector/quant. * @public */ - multiply: function(q, s) { + multiply: function (q, s) { q[0] *= s; q[1] *= s; q[2] *= s; @@ -55,7 +57,7 @@ module.exports = { * Limits the vector/quant between a maximum and minimum value. * @public */ - range: function(q, max, min) { + range: function (q, max, min) { for (var i = 0; i < q.length; i++) { q[i] = q[i] < min ? Math.max(q[i], min) : q[i] > max ? Math.min(q[i], max) : q[i]; } @@ -66,8 +68,8 @@ module.exports = { * Returns true when all the vector/quant values are equal to this scalar value. * @public */ - equalS: function(q1, s) { - return (q1.length > 0) && q1.every(function(e, i) { + equalS: function (q1, s) { + return (q1.length > 0) && q1.every(function (e, i) { return e === (s || 0); }); }, @@ -77,8 +79,8 @@ module.exports = { * Returns true when all the vector/quant values are greater or equal to this scalar value. * @public */ - greaterS: function(q1, s) { - return (q1.length > 0) && q1.every(function(e, i) { + greaterS: function (q1, s) { + return (q1.length > 0) && q1.every(function (e, i) { return e >= (s || 0); }); }, @@ -88,8 +90,8 @@ module.exports = { * Returns true when all the vector/quant values are lesser or equal to this scalar value. * @public */ - lesserS: function(q1, s) { - return (q1.length > 0) && q1.every(function(e, i) { + lesserS: function (q1, s) { + return (q1.length > 0) && q1.every(function (e, i) { return e < (s || 0); }); }, @@ -99,12 +101,9 @@ module.exports = { * Returns the absolute distance between two vectors. * @public */ - distance: function(q1, q2, d) { + distance: function (v1, v2, d) { d = Math.sqrt( - (q1[0] - q2[0]) * (q1[0] - q2[0]) + - (q1[1] - q2[1]) * (q1[1] - q2[1]) + - (q1[2] - q2[2]) * (q1[2] - q2[2]) - ); + (v1[0] - v2[0]) * (v1[0] - v2[0]) + (v1[1] - v2[1]) * (v1[1] - v2[1]) + (v1[2] - v2[2]) * (v1[2] - v2[2])); return (d < 0.01 && d > -0.01) ? 0 : d; }, @@ -113,13 +112,9 @@ module.exports = { * Returns the absolute distance between two quanterions. * @public */ - quantDistance: function(q1, q2, d) { + quantDistance: function (q1, q2, d) { d = Math.sqrt( - (q1[0] - q2[0]) * (q1[0] - q2[0]) + - (q1[1] - q2[1]) * (q1[1] - q2[1]) + - (q1[2] - q2[2]) * (q1[2] - q2[2]) + - (q1[3] - q2[3]) * (q1[3] - q2[3]) - ); + (q1[0] - q2[0]) * (q1[0] - q2[0]) + (q1[1] - q2[1]) * (q1[1] - q2[1]) + (q1[2] - q2[2]) * (q1[2] - q2[2]) + (q1[3] - q2[3]) * (q1[3] - q2[3])); return (d < 0.0001 && d > -0.0001) ? 0 : d; }, @@ -128,7 +123,7 @@ module.exports = { * Returns true if moving towards positive direction. * @public */ - direction: function(q1, q2) { + direction: function (q1, q2) { return (q1[0] - q2[0]) < 0 || (q1[1] - q2[1]) < 0 || (q1[2] - q2[2]) < 0; }, @@ -136,7 +131,7 @@ module.exports = { * Retunns an inverse of a quanterion. * @public */ - quantInverse: function(q) { + quantInverse: function (q) { // var d = (q[0] * q[0]) + (q[1] * q[1]) + (q[2] * q[2]) + (q[3] * q[3]); return [-q[0], -q[1], -q[2], q[3]]; }, @@ -145,7 +140,7 @@ module.exports = { * Length of 3D vectors * @public */ - sumS: function(q, s) { + sumS: function (q, s) { return q[0] * s + q[1] * s + q[2] * s + q[3] !== undefined ? q[3] * s : 0; }, @@ -153,7 +148,7 @@ module.exports = { * Length of 3D vectors * @public */ - len: function(q) { + len: function (q) { return Math.sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2]); }, @@ -161,7 +156,7 @@ module.exports = { * Dot product of 3D vectors * @public */ - dot: function(q1, q2) { + dot: function (q1, q2) { return (q1[0] * q2[0]) + (q1[1] * q2[1]) + (q1[2] * q2[2]) + (q1[3] !== undefined && q2[3] !== undefined ? (q1[3] * q2[3]) : 0); }, @@ -169,7 +164,7 @@ module.exports = { * Dot product of 3D vectors * @public */ - quantDot: function(q1, q2) { + quantDot: function (q1, q2) { return (q1[0] * q2[0]) + (q1[1] * q2[1]) + (q1[2] * q2[2]) + (q1[3] * q2[3]); }, @@ -177,7 +172,7 @@ module.exports = { * Quant Dot product of 3D vectors * @public */ - quantCross: function(q1, q2) { + quantCross: function (q1, q2) { return [ q1[3] * q2[0] + q1[0] * q2[3] + q1[1] * q2[2] - q1[2] * q2[1], q1[3] * q2[1] - q1[0] * q2[2] + q1[1] * q2[3] + q1[2] * q2[0], @@ -190,7 +185,7 @@ module.exports = { * Cross product of two vectors * @public */ - cross: function(q1, q2) { + cross: function (q1, q2) { return [ q1[1] * q2[2] - q1[2] * q2[1], q1[2] * q2[0] - q1[0] * q2[2], @@ -203,7 +198,7 @@ module.exports = { * To normalize a vector, divide the vector by its magnitude. * @public */ - normalize: function(q) { + normalize: function (q) { return this.divide(q, this.len(q)); }, @@ -212,7 +207,7 @@ module.exports = { * Required during parsing scaler values matrix. * @public */ - combine: function(a, b, ascl, bscl) { + combine: function (a, b, ascl, bscl) { return [ (ascl * a[0]) + (bscl * b[0]), (ascl * a[1]) + (bscl * b[1]), (ascl * a[2]) + (bscl * b[2]) ]; @@ -222,7 +217,7 @@ module.exports = { * Converts a quaternion vector to a rotation vector. * @public */ - toVector: function(rv) { + toVector: function (rv) { var r = 2 * Math.acos(rv[3]); var sA = Math.sqrt(1.0 - rv[3] * rv[3]); if (Math.abs(sA) < 0.0005) sA = 1; @@ -233,21 +228,52 @@ module.exports = { * Converts a rotation vector to a quaternion vector. * @public */ - toQuant: function(q) { - if (!q) q = []; + toQuant: function (v) { + if (!v) v = []; + var q = [], + p = parseFloat(v[1] || 0) * Math.PI / 360, + y = parseFloat(v[2] || 0) * Math.PI / 360, + r = parseFloat(v[0] || 0) * Math.PI / 360, + c1 = Math.cos(p), + c2 = Math.cos(y), + c3 = Math.cos(r), + s1 = Math.sin(p), + s2 = Math.sin(y), + s3 = Math.sin(r); - var x = q[0] || 0, - y = q[1] || 0, - z = q[2] || 0, - deg = q[3] || 0, - r = deg * (Math.PI / 360), - sR = Math.sin(r), - cR = Math.cos(r); - - q[0] = x * sR; - q[1] = y * sR; - q[2] = z * sR; - q[3] = cR; + q[3] = Math.round((c1 * c2 * c3 - s1 * s2 * s3) * 100000) / 100000; + q[0] = Math.round((s1 * s2 * c3 + c1 * c2 * s3) * 100000) / 100000; + q[1] = Math.round((s1 * c2 * c3 + c1 * s2 * s3) * 100000) / 100000; + q[2] = Math.round((c1 * s2 * c3 - s1 * c2 * s3) * 100000) / 100000; return q; + }, + + quantToVector: function (q) { + var vector = [], h, a, b, + x2 = q[0] * q[0], + y2 = q[1] * q[1], + z2 = q[2] * q[2], + test = q[0] * q[1] + q[2] * q[3]; + if (test > 0.499) { + vector[1] = 360 / Math.PI * Math.atan2(q[0], q[3]); + vector[2] = 90; + vector[0] = 0; + return; + } + if (test < -0.499) { + vector[1] = -360 / Math.PI * Math.atan2(q[0], q[3]); + vector[2] = -90; + vector[0] = 0; + return; + } + h = Math.atan2(2 * q[1] * q[3] - 2 * q[0] * q[2], 1 - 2 * y2 - 2 * z2); + a = Math.asin(2 * q[0] * q[1] + 2 * q[2] * q[3]); + b = Math.atan2(2 * q[0] * q[3] - 2 * q[1] * q[2], 1 - 2 * x2 - 2 * z2); + vector[1] = Math.round(h * 180 / Math.PI); + vector[2] = Math.round(a * 180 / Math.PI); + vector[0] = Math.round(b * 180 / Math.PI); + + return vector; } -}; + //TODO: Acheive the same fucntionality for other 11 choices XYX, XZX, XZY, YXY, YXZ, YZX, YZY, ZXY, ZXZ, ZYX, ZYZ +}; \ No newline at end of file From 8044538713fa0790ae7b3633b97676dd03372dae Mon Sep 17 00:00:00 2001 From: Ankur Mishra Date: Fri, 18 Dec 2015 17:01:58 +0530 Subject: [PATCH 189/195] Required changes for addAnimation at 0 duration (like keyframe) Enyo-DCO-1.1-Signed-off-by: Ankur Mishra --- src/AnimationSupport/AnimationSupport.js | 10 +++++++--- src/AnimationSupport/Core.js | 8 ++++---- src/AnimationSupport/Director.js | 8 +++++++- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/AnimationSupport/AnimationSupport.js b/src/AnimationSupport/AnimationSupport.js index c1ee82446..aeaf44a7d 100644 --- a/src/AnimationSupport/AnimationSupport.js +++ b/src/AnimationSupport/AnimationSupport.js @@ -133,9 +133,13 @@ var AnimationSupport = { * @public */ addAnimation: function (newProp, duration) { - this.prevDur = duration || this.prevDur; - this.totalDuration += this.prevDur; - this._animPose.push({animate: newProp, duration: this.totalDuration}); + if (this.prevDur === 0 && duration === 0) { + this._animPose[0] = {animate: newProp, duration: 0}; + } else { + this.prevDur = duration || this.prevDur; + this.totalDuration += this.prevDur; + this._animPose.push({animate: newProp, duration: this.totalDuration}); + } }, /** diff --git a/src/AnimationSupport/Core.js b/src/AnimationSupport/Core.js index db2c4f855..baaed018e 100644 --- a/src/AnimationSupport/Core.js +++ b/src/AnimationSupport/Core.js @@ -174,15 +174,15 @@ module.exports = kind.singleton({ this.running = false; return; } - - ts = utils.perfNow(); + for (i = 0; i < len; i++) { curr = this.chracs[i]; if (curr && curr.ready()) { - director.update(curr, ts - (wasTs || ts)); + ts = utils.perfNow(); + director.update(curr, ts - (curr.wasTs || ts)); + curr.wasTs = ts; } } - wasTs = ts; this.start(); }, diff --git a/src/AnimationSupport/Director.js b/src/AnimationSupport/Director.js index b097d1b70..94dd000dd 100644 --- a/src/AnimationSupport/Director.js +++ b/src/AnimationSupport/Director.js @@ -58,6 +58,7 @@ module.exports = { prevDur = actor._animPose[(index - 1) < 0 ? 0 : (index - 1)].duration, currentAnimSince = since - prevDur, runningDur = props.duration - prevDur; + if (!props._startAnim) { pose = frame.getComputedProperty(actor.hasNode(), props.animate, actor.currentState); utils.mixin(props, pose); @@ -65,7 +66,8 @@ module.exports = { if (currentAnimSince < 0) return; if (currentAnimSince <= runningDur) { - t = currentAnimSince / runningDur; + if (currentAnimSince === 0 || runningDur === 0) t = 1; + else t = currentAnimSince / runningDur; tween.step(actor, props, t, runningDur); } else { tween.step(actor, props, 1, runningDur); @@ -77,6 +79,10 @@ module.exports = { stopIndex = arr.length - 1, middle = Math.floor((stopIndex + startIndex) / 2); + if(duration === 0) { + return startIndex; + } + while (arr[middle].duration != duration && startIndex < stopIndex) { if (duration < arr[middle].duration) { stopIndex = middle; From f2e8d4f208be4d950dd1ca3642221fe7fc8e7d75 Mon Sep 17 00:00:00 2001 From: Ankur Mishra Date: Fri, 18 Dec 2015 17:27:23 +0530 Subject: [PATCH 190/195] FIx some bug in Tween Enyo-DCO-1.1-Signed-off-by: Ankur Mishra --- src/AnimationSupport/Tween.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AnimationSupport/Tween.js b/src/AnimationSupport/Tween.js index 56aed1b5e..8f29a2642 100644 --- a/src/AnimationSupport/Tween.js +++ b/src/AnimationSupport/Tween.js @@ -26,7 +26,7 @@ module.exports = { node = charc.node; newState = pose._endAnim; - ease = pose.animate ? pose.animate.ease: this.ease; + ease = pose.animate && pose.animate.ease ? pose.animate.ease: this.ease; oldState = pose._startAnim; charc.currentState = charc.currentState || {}; if(pose.props){ From 0a7101b6fef44dd8c8930ee20a9baa2fdc07eb53 Mon Sep 17 00:00:00 2001 From: Anish_Ramesan Date: Sat, 19 Dec 2015 18:26:30 +0530 Subject: [PATCH 191/195] Event delegation with virtual events Enyo-DCO-1.1-Signed-off-by: Anish Ramesan --- .../AnimationInterfaceSupport.js | 37 +++-- src/AnimationSupport/AnimationSupport.js | 54 ++++++- src/AnimationSupport/Core.js | 18 ++- src/AnimationSupport/Director.js | 40 ++++- src/AnimationSupport/EventDelegator.js | 141 +++++++++++------- src/AnimationSupport/Fadeable.js | 6 +- 6 files changed, 213 insertions(+), 83 deletions(-) diff --git a/src/AnimationSupport/AnimationInterfaceSupport.js b/src/AnimationSupport/AnimationInterfaceSupport.js index d2f4253e4..f45bcd933 100644 --- a/src/AnimationSupport/AnimationInterfaceSupport.js +++ b/src/AnimationSupport/AnimationInterfaceSupport.js @@ -265,7 +265,7 @@ var AnimationInterfaceSupport = { this.setAnimateOne = delta; this.setAnimateTwo = (-1 * (this.translateX)); this.setAnimateThree = (-1 * (this.translateY)); - if (patterns[0].name === "Slideable" || patterns[0].name === "Parallax") { + if (this.patterns[0].name === "Slideable" || this.patterns[0].name === "Parallax") { this.setAnimateTwo = this.setAnimateThree; } }, @@ -274,29 +274,29 @@ var AnimationInterfaceSupport = { * @public */ commonTasks: function(delta, deltax, deltay) { - var patternsLength = patterns.length; + var patternsLength = this.patterns.length; if (delta !== 0) { delta = delta / Math.abs(delta); } //Call specific interface for (var i = 0; i < patternsLength; i++) { - if (patterns[i].name === "Fadeable") { - patterns[i].fadeByDelta.call(this, delta); - } else if (patterns[i].name === "Flippable") { - patterns[i].doFlip.call(this, delta); - } else if (patterns[i].name === "Slideable") { + if (this.patterns[i].name === "Fadeable") { + this.patterns[i].fadeByDelta.call(this, delta); + } else if (this.patterns[i].name === "Flippable") { + this.patterns[i].doFlip.call(this, delta); + } else if (this.patterns[i].name === "Slideable") { if (this.parallax === true) { for (var j = 0; j < this.children.length; j++) { var current = this.children[j]; animator.trigger(current); - patterns[i].slide.call(current, (-1 * deltax / current.speed), (-1 * deltay / current.speed), 0); + this.patterns[i].slide.call(current, (-1 * deltax / current.speed), (-1 * deltay / current.speed), 0); current.start(true); } } else { - patterns[i].slide.call(this, (-1 * deltax), (-1 * deltay), 0); + this.patterns[i].slide.call(this, (-1 * deltax), (-1 * deltay), 0); } } - if (patterns[i].name !== "Slideable") { + if (this.patterns[i].name !== "Slideable") { this.setAnimateOne = 0; this.setAnimateTwo = 0; this.setAnimateThree = 0; @@ -318,15 +318,12 @@ var AnimationInterfaceSupport = { commitAnimation: function(x, y, z) { var i, len; - if (patterns && Object.prototype.toString.call(patterns) === "[object Array]") { - len = patterns.length; + if (this.patterns && Object.prototype.toString.call(this.patterns) === "[object Array]" && (len = this.patterns.length)) { for (i = 0; i < len; i++) { - if (typeof patterns[i].triggerEvent === 'function') { - //patterns[i].triggerEvent(); - - } + /*if (typeof this.patterns[i].triggerEvent === 'function') { + patterns[i].triggerEvent(); + }*/ this.commonTasks(this.setAnimateOne, this.setAnimateTwo, this.setAnimateThree); - } } }, @@ -359,10 +356,10 @@ kind.concatHandler = function(ctor, props, instance) { var proto = ctor.prototype || ctor; extend(AnimationInterfaceSupport, proto); - patterns = aPattern; - var len = patterns.length; + this.patterns = aPattern; + var len = this.patterns.length; for (var i = 0; i < len; i++) { - extend(patterns[i], proto); + extend(this.patterns[i], proto); } animator.register(proto); } diff --git a/src/AnimationSupport/AnimationSupport.js b/src/AnimationSupport/AnimationSupport.js index aeaf44a7d..2566b0b1d 100644 --- a/src/AnimationSupport/AnimationSupport.js +++ b/src/AnimationSupport/AnimationSupport.js @@ -42,6 +42,18 @@ var AnimationSupport = { */ animationState: "", + /** + * To check if the event delta value is changed + * @private + */ + deltaChanged: false, + + /** + * To hold the name of the animation event which occured on the character + * @private + */ + eventName: "", + /** * Holds delta value in the order [x, y, z, rad] * @private @@ -72,6 +84,9 @@ var AnimationSupport = { mixins: [EventEmitter, FrameEditor], + _eventCache: {}, + + /** * Check if the character is suitable for animation * @public @@ -150,6 +165,30 @@ var AnimationSupport = { this._prop = newProp; }, + + /** + * Sets the delta values of x, y and z for events + * @param {Object} obj - Object contains dX, dY and dZ as keys + * @public + */ + setAnimationDelta: function (ev) { + this._eventCache.dX = ev.dX + this._eventCache.dX || 0; + this._eventCache.dY = ev.dY + this._eventCache.dY || 0; + this._eventCache.dZ = ev.dZ + this._eventCache.dZ || 0; + this._eventCache[ev.vtype] = ev; + + this.deltaChanged = true; + this.eventCacheUpdated = true; + + }, + + /** + * Gets the delta values of x, y and z for events + * @public + */ + getAnimationDelta: function () { + return this._eventCache[this._virtualEvent]; + }, /** * Gets how long animation is active on this character * @public @@ -186,6 +225,15 @@ var AnimationSupport = { this.active = active; }, + /** + * Trigger the registered event to all the listeners + * @public + */ + triggerEvent: function () { + this.deltaChanged = false; + return delegator.emitEvent(this, this.getAnimationDelta()); + }, + /** * @private */ @@ -197,7 +245,7 @@ var AnimationSupport = { delegator.register(this); } }; - }), + }), /** * @private @@ -205,6 +253,10 @@ var AnimationSupport = { destroy: kind.inherit(function(sup) { return function() { animation.remove(this); + animation.deRegister(this); + if (this.handleAnimationEvents) { + delegator.deRegister(this); + } sup.apply(this, arguments); }; }) diff --git a/src/AnimationSupport/Core.js b/src/AnimationSupport/Core.js index baaed018e..723bd4fdf 100644 --- a/src/AnimationSupport/Core.js +++ b/src/AnimationSupport/Core.js @@ -104,7 +104,8 @@ module.exports = kind.singleton({ * @public */ remove: function (curr) { - this.chracs.splice(this.chracs.indexOf(curr), 1); + var i = this.chracs.indexOf(curr); + if (i >= 0) this.chracs.splice(i, 1); }, /** @@ -179,7 +180,7 @@ module.exports = kind.singleton({ curr = this.chracs[i]; if (curr && curr.ready()) { ts = utils.perfNow(); - director.update(curr, ts - (curr.wasTs || ts)); + director.take(curr, ts - (curr.wasTs || ts)); curr.wasTs = ts; } } @@ -190,19 +191,22 @@ module.exports = kind.singleton({ * @private */ eventLoop: function () { - var i, curr, evlen = this.evnts.length; + var i, curr, status, evlen = this.evnts.length; for (i = 0; i < evlen; i++) { curr = this.evnts[i]; if (this.evnts[i].patterns && typeof this.evnts[i].patterns.length > 0) { this.evnts[i].commitAnimation(); } + ts = utils.perfNow(); if (curr && curr.ready()) { - director.updateDelta(curr); - if (!curr.animating) { - director.complete(curr); - curr.completed(curr); + if (curr.deltaChanged) { + status = curr.triggerEvent(); + } + if (!status && curr.eventCacheUpdated) { + director.shot(curr, ts - (wasTs || ts)); } } + wasTs = ts; } this.dummy(); }, diff --git a/src/AnimationSupport/Director.js b/src/AnimationSupport/Director.js index 94dd000dd..0ce40c08f 100644 --- a/src/AnimationSupport/Director.js +++ b/src/AnimationSupport/Director.js @@ -29,7 +29,7 @@ module.exports = { * @public */ - update: function(actor, ts) { + take: function(actor, ts) { var dur = actor.totalDuration, tm = actor.rolePlay(ts); @@ -58,7 +58,6 @@ module.exports = { prevDur = actor._animPose[(index - 1) < 0 ? 0 : (index - 1)].duration, currentAnimSince = since - prevDur, runningDur = props.duration - prevDur; - if (!props._startAnim) { pose = frame.getComputedProperty(actor.hasNode(), props.animate, actor.currentState); utils.mixin(props, pose); @@ -94,5 +93,42 @@ module.exports = { } return (arr[middle].duration != duration) ? startIndex : middle; + }, + + shot: function(chrac, ts) { + var v1, s, a, v, + t = ts, + dt = chrac._eventCache, + dir = this.angle(chrac.direction), + v0 = dt.velocity || 0; + + v1 = dt[dir] / t; + if (v1 === 0) { + dt[dir] = 0; + dt.velocity = 0; + } else { + a = (v1 - v0) / t; + s = 0.5 * a * t * t; + v = (a < 0 ? -s : s); + dt[dir] = dt[dir] - v; + if (a > -0.001 && a < 0.001) { + dt[dir] = 0; + } + dt.velocity = v1; + this.take(chrac, dt[dir] > 0 ? v : -v); + } + }, + + angle: function (direction) { + switch(direction) { + case "X" : + return "dX"; + case "Y" : + return "dY"; + case "Z" : + return "dZ"; + default: + return "dX"; + } } }; \ No newline at end of file diff --git a/src/AnimationSupport/EventDelegator.js b/src/AnimationSupport/EventDelegator.js index 01298c901..b2691cf27 100644 --- a/src/AnimationSupport/EventDelegator.js +++ b/src/AnimationSupport/EventDelegator.js @@ -1,8 +1,20 @@ require('enyo'); var - dispatcher = require('../dispatcher'); - + dispatcher = require('../dispatcher'), + emitter = require('../EventEmitter'); + + +var eventsMap = { + vdrag: "drag", + vscroll: "scroll", + vmousewheel: "mousewheel", + vtouch: "touchmove", + drag: "vdrag", + scroll: "vscroll", + mousewheel: "vmousewheel", + touchmove: "vtouch" +}; /** * This module handles the animation events for the character. * If the character has opted to have animations handled by animation framework, @@ -41,7 +53,7 @@ var EventDelegator = { "touchmove", "touchend" ], - + /** * Attaches the evnet handlers to the character either its own events or * else default events with the framework. As of now only these events are @@ -52,9 +64,9 @@ var EventDelegator = { * @public */ register: function (charc) { - var events = charc.animationEvents || this.eventArray; - for (var i = 0, l = events.length; i < l; i++) { - this.addRemoveListener(charc, events[i]); + var events = charc.handleAnimationEvents || {}; + for (var key in events) { + this.addRemoveListener(charc, key, events[key]); } }, @@ -68,41 +80,71 @@ var EventDelegator = { * @public */ deRegister: function (charc) { - var events = charc.animationEvents || this.eventArray; - for (var i = 0, l = events.length; i < l; i++) { - this.addRemoveListener(charc, events[i], true); + var events = charc.handleAnimationEvents || {}; + for (var key in events) { + this.addRemoveListener(charc, key, events[key], true); } }, /** * @private */ - addRemoveListener: function(charc, name, remove) { - var d = remove ? dispatcher.stopListening : dispatcher.listen; - d(charc.hasNode(), name, charc.bindSafely(this[name + 'Event'], charc)); + addRemoveListener: function(charc, name, callback, remove) { + var d = remove ? dispatcher.stopListening : dispatcher.listen, + e = eventsMap[name]; + d(charc.hasNode(), e, charc.bindSafely(this[e + 'Event'], charc)); + + var fn = remove ? emitter.off : emitter.on; + fn.apply(emitter, [name, charc[callback], charc]); + }, + + /** + * @private + */ + emitEvent: function(charc, data) { + return emitter.vemit.call(emitter, data); }, /** * @private */ - touchstartEvent: function (sender, ev) { - sender.touchX = ev.targetTouches[0].pageX; - sender.touchY = ev.targetTouches[0].pageY; + touchstartEvent: function (sender, inEvent) { + sender.touchX = inEvent.targetTouches[0].pageX; + sender.touchY = inEvent.targetTouches[0].pageY; }, /** * @private */ - touchmoveEvent: function (sender, ev) { - var x = ev.targetTouches[0].pageX, - y = ev.targetTouches[0].pageY; + touchmoveEvent: function (sender, inEvent) { + var x = inEvent.targetTouches[0].pageX, + y = inEvent.targetTouches[0].pageY; if(x !== 0 || y !== 0) { - sender.vScrollX = sender.touchX - x; - sender.vScrollY = sender.touchY - y; - sender.vScrollZ = 0; - sender.touchX = x; - sender.touchY = y; + /*sender.animDelta[0] = sender.touchX - x; + sender.animDelta[1] = sender.touchY - y; + sender.animDelta[2] = 0;*/ + + // var o = { + // dX: (sender.touchX - x), + // dY: (sender.touchY - y), + // dZ: 0 + // }; + // sender.setAnimationDelta(o); + // sender.touchX = x; + // sender.touchY = y; + + // this.eventName = eventsMap[inEvent.type]; + + console.log(inEvent.targetTouches[0]); + + inEvent.dX = inEvent.deltaX; + inEvent.dY = inEvent.deltaY; + inEvent.dZ = 0; + inEvent.vtype = eventsMap[inEvent.type]; + + inSender.setAnimationDelta(inEvent); + inSender._virtualEvent = eventsMap[inEvent.type]; } }, @@ -118,27 +160,13 @@ var EventDelegator = { * @private */ scrollEvent: function (inSender, inEvent) { - var delta = inSender.deltaY, - scrollTop = inSender.target.scrollTop, - scrollLeft = inSender.target.scrollLeft; - - if (this.scrollValue === 0) { - this.scrollValue = inSender.target.scrollTop; - } - - delta = inSender.target.scrollTop - this.scrollValue; - - this.deltaX = scrollLeft - this.deltaX; - this.deltaY = scrollTop - this.deltaY; - this.scrollValue = scrollTop; + inEvent.dX = inEvent.deltaX; + inEvent.dY = inEvent.deltaY; + inEvent.dZ = 0; + inEvent.vtype = eventsMap[inEvent.type]; - this.deltaX = scrollLeft; - this.deltaY = scrollTop; - - - this.vScrollX = delta; - this.vScrollY = 0; - this.vScrollZ = 0; + inSender.setAnimationDelta(inEvent); + inSender._virtualEvent = eventsMap[inEvent.type]; }, /** @@ -162,19 +190,32 @@ var EventDelegator = { this.dragLeft = dragLeft, this.dragTop = dragTop; - this.vScrollX = this.deltaX; - this.vScrollY = this.deltaY; - this.vScrollZ = 0; + /*this.animDelta[0] = this.deltaX; + this.animDelta[1] = this.deltaY; + this.animDelta[2] = 0;*/ + + var o = { + dX: this.deltaX, + dY: this.deltaY, + dZ: 0 + }; + this.setAnimationDelta(o); + + this.eventName = eventsMap[inEvent.type]; } }, /** * @private */ - mousewheelEvent: function (sender, ev) { - sender.vScrollX = ev.deltaY; - sender.vScrollY = ev.deltaX; - sender.vScrollZ = 0; + mousewheelEvent: function (sender, inEvent) { + inEvent.dX = inEvent.deltaX; + inEvent.dY = inEvent.deltaY; + inEvent.dZ = 0; + inEvent.vtype = eventsMap[inEvent.type]; + + sender.setAnimationDelta(inEvent); + sender._virtualEvent = eventsMap[inEvent.type]; } }; diff --git a/src/AnimationSupport/Fadeable.js b/src/AnimationSupport/Fadeable.js index a7924bd08..425589767 100644 --- a/src/AnimationSupport/Fadeable.js +++ b/src/AnimationSupport/Fadeable.js @@ -80,7 +80,7 @@ module.exports = { * @public * Bubble the fadeable event */ - triggerEvent: function(e) { - //this.doFadeStart(); - } + /*triggerEvent: function(e) { + this.doFadeStart(); + }*/ }; From 7e3bbd01c5dabe2b2e7a620b1ad36531aaf1ffd3 Mon Sep 17 00:00:00 2001 From: Arzoo-Rai Date: Wed, 30 Dec 2015 18:41:46 +0530 Subject: [PATCH 192/195] Changes based on path property present in character object Enyo-DCO-1.1-Signed-off-by:Arzoo Rai --- src/AnimationSupport/Easings.js | 1 + src/AnimationSupport/Tween.js | 27 +++++++++++++++++---------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/AnimationSupport/Easings.js b/src/AnimationSupport/Easings.js index 582412f44..cfcbf6d58 100644 --- a/src/AnimationSupport/Easings.js +++ b/src/AnimationSupport/Easings.js @@ -206,6 +206,7 @@ var easings = { var c, values = [], + x = (1 - t), y = t; // diff --git a/src/AnimationSupport/Tween.js b/src/AnimationSupport/Tween.js index 8f29a2642..2daea8274 100644 --- a/src/AnimationSupport/Tween.js +++ b/src/AnimationSupport/Tween.js @@ -2,7 +2,7 @@ require('enyo'); var frame = require('./Frame'), - easings = require('./Easings'), + easings = require('./Easings'), matrixUtil = require('./Matrix'), Vector = require('./Vector'), utils = require('../utils'); @@ -22,7 +22,7 @@ module.exports = { * @private */ step: function(charc, pose, t, d) { - var k, c, pts, tState, oState, ease; + var k, c, pts, tState, oState, ease, points; node = charc.node; newState = pose._endAnim; @@ -36,7 +36,6 @@ module.exports = { if (ease && (typeof ease !== 'function')) { var checkEaseChange = easings.easeChanged(ease); var propChange = easings.propChange(k); - if (!charc.controlPoints || (propChange === true || checkEaseChange === true)) { // for the first time or either of Ease/Propery changed charc.controlPoints = easings.calculateEase(ease, frame.copy(oldState[k]), frame.copy(newState[k])); @@ -74,7 +73,10 @@ module.exports = { else{ utils.mixin(charc.currentState,oldState); } - + if(charc.path){ + points = this.getBezier(t, charc.path, charc.currentState.translate, true); + charc.currentState.translate = points; + } matrix = frame.recomposeMatrix( charc.currentState.translate, charc.currentState.rotate, @@ -84,7 +86,7 @@ module.exports = { ); frame.accelerate(node, matrix); - charc.animationStep && charc.animationStep(t); + charc.animationStep && charc.animationStep(t,matrix); }, @@ -230,7 +232,7 @@ module.exports = { * @public * @params t: time, points: knot and control points, vR: resulting point */ - getBezier: function(t, points, vR) { + getBezier: function(t, points, vR, isPath) { if (!vR) vR = []; @@ -241,18 +243,23 @@ module.exports = { startPoint = points[0], endPoint = points[lastIndex], values = easings.getBezierValues(t, lastIndex); - for (i = 0; i < l; i++) { vR[i] = 0; for (j = 0; j < c; j++) { - if ((j > 0) && (j < (c - 1))) { - vR[i] = vR[i] + ((startPoint[i] + (points[j][i] * (endPoint[i] - startPoint[i]))) * values[j]); - } else { + if(isPath){ vR[i] = vR[i] + (points[j][i] * values[j]); } + else { + if((j > 0) && (j < (c - 1))){ + vR[i] = vR[i] + ((startPoint[i] + (points[j][i] * (endPoint[i] - startPoint[i]))) * values[j]); + } else { + vR[i] = vR[i] + (points[j][i] * values[j]); + } + } } } return vR; } + }; From 4fece85bc73707544b51b63cb1b281ab745bf53a Mon Sep 17 00:00:00 2001 From: Ankur Mishra Date: Thu, 31 Dec 2015 16:02:28 +0530 Subject: [PATCH 193/195] Fixed some issue regarding final frame missing in multiple addAnimation Enyo-DCO-1.1-Signed-off-by: Ankur Mishra --- src/AnimationSupport/Director.js | 10 +++++----- src/AnimationSupport/Tween.js | 21 +++++++++++---------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/AnimationSupport/Director.js b/src/AnimationSupport/Director.js index 0ce40c08f..8407048f3 100644 --- a/src/AnimationSupport/Director.js +++ b/src/AnimationSupport/Director.js @@ -34,9 +34,10 @@ module.exports = { tm = actor.rolePlay(ts); if (tm < 0) return; - if (tm <= dur) { + if (tm < dur) { this.action(actor, tm); } else { + this.action(actor, tm); this.cut(actor); } }, @@ -64,10 +65,9 @@ module.exports = { } if (currentAnimSince < 0) return; - if (currentAnimSince <= runningDur) { - if (currentAnimSince === 0 || runningDur === 0) t = 1; - else t = currentAnimSince / runningDur; - tween.step(actor, props, t, runningDur); + if (currentAnimSince <= runningDur && runningDur !== 0) { + t = currentAnimSince / runningDur; + tween.step(actor, props, ( t > 0.98) ? t = 1 : t, runningDur); } else { tween.step(actor, props, 1, runningDur); } diff --git a/src/AnimationSupport/Tween.js b/src/AnimationSupport/Tween.js index 2daea8274..cf419dce3 100644 --- a/src/AnimationSupport/Tween.js +++ b/src/AnimationSupport/Tween.js @@ -29,13 +29,15 @@ module.exports = { ease = pose.animate && pose.animate.ease ? pose.animate.ease: this.ease; oldState = pose._startAnim; charc.currentState = charc.currentState || {}; - if(pose.props){ + + if (pose.props) { for (k in pose.props) { cState = frame.copy(charc.currentState[k] || []); if (newState[k]) { if (ease && (typeof ease !== 'function')) { var checkEaseChange = easings.easeChanged(ease); var propChange = easings.propChange(k); + if (!charc.controlPoints || (propChange === true || checkEaseChange === true)) { // for the first time or either of Ease/Propery changed charc.controlPoints = easings.calculateEase(ease, frame.copy(oldState[k]), frame.copy(newState[k])); @@ -53,7 +55,7 @@ module.exports = { } else { if (k == 'rotate') { tState = Vector.toQuant(newState[k]); - oState = Vector.toQuant(oldState[k]); + oState = oldState[k]; c = this.slerp; } else { tState = newState[k]; @@ -69,11 +71,10 @@ module.exports = { } charc.currentState[k] = cState; } - } - else{ + } else { utils.mixin(charc.currentState,oldState); } - if(charc.path){ + if (charc.path) { points = this.getBezier(t, charc.path, charc.currentState.translate, true); charc.currentState.translate = points; } @@ -89,7 +90,6 @@ module.exports = { charc.animationStep && charc.animationStep(t,matrix); }, - /** * @private */ @@ -194,8 +194,6 @@ module.exports = { return vR; }, - - lerp: function(vA, vB, t, vR) { if (!vR) vR = []; var i, l = vA.length; @@ -213,18 +211,21 @@ module.exports = { theta, dot = Vector.quantDot(qA, qB), l = qA.length; - + dot = Math.min(Math.max(dot, -1.0), 1.0); + if (dot == 1.0) { qR = frame.copy(qA); return qR; } + theta = Math.acos(dot); for (var i = 0; i < l; i++) { a = (Math.sin((1 - t) * theta) / Math.sin(theta)) * qA[i]; b = (Math.sin(t * theta) / Math.sin(theta)) * qB[i]; qR[i] = a + b; } + return qR; }, @@ -243,6 +244,7 @@ module.exports = { startPoint = points[0], endPoint = points[lastIndex], values = easings.getBezierValues(t, lastIndex); + for (i = 0; i < l; i++) { vR[i] = 0; for (j = 0; j < c; j++) { @@ -261,5 +263,4 @@ module.exports = { return vR; } - }; From d1d0844c41ed2cab88eddba0c3069c959d315438 Mon Sep 17 00:00:00 2001 From: Ankur Mishra Date: Thu, 31 Dec 2015 16:27:38 +0530 Subject: [PATCH 194/195] Fixed some issues regarding multiple and repeat animations Enyo-DCO-1.1-Signed-off-by: Ankur Mishra --- src/AnimationSupport/AnimationSupport.js | 1 + src/AnimationSupport/Director.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/AnimationSupport/AnimationSupport.js b/src/AnimationSupport/AnimationSupport.js index 2566b0b1d..f7894217b 100644 --- a/src/AnimationSupport/AnimationSupport.js +++ b/src/AnimationSupport/AnimationSupport.js @@ -133,6 +133,7 @@ var AnimationSupport = { var dom = this.hasNode(), dur, pose = frame.getComputedProperty(dom, undefined, current); pose.duration = 0; + this._animPose = []; this._animPose.push(pose); this.currentState = pose.currentState; frame.accelerate(dom, pose.matrix); diff --git a/src/AnimationSupport/Director.js b/src/AnimationSupport/Director.js index 8407048f3..12ec4ba3a 100644 --- a/src/AnimationSupport/Director.js +++ b/src/AnimationSupport/Director.js @@ -44,7 +44,7 @@ module.exports = { cut: function (actor) { actor.animating = false; - actor._timeline = undefined; + actor.timeline = 0; actor.completed(actor); actor.set('animationState', 'completed'); if (!actor.active) { From 9fc574e69788519b8115479c3de8e50176e4c5cb Mon Sep 17 00:00:00 2001 From: Arzoo-Rai Date: Tue, 19 Jan 2016 16:59:55 +0530 Subject: [PATCH 195/195] changes in framework to achieve dynamic path animation Enyo-DCO-1.1-Signed-off-by:Arzoo Rai --- src/AnimationSupport/AnimationSupport.js | 15 +++++++++++++-- src/AnimationSupport/Director.js | 4 +++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/AnimationSupport/AnimationSupport.js b/src/AnimationSupport/AnimationSupport.js index f7894217b..5f129c65e 100644 --- a/src/AnimationSupport/AnimationSupport.js +++ b/src/AnimationSupport/AnimationSupport.js @@ -80,6 +80,8 @@ var AnimationSupport = { _animPose: [], + _animPath: [], + _pose: [], mixins: [EventEmitter, FrameEditor], @@ -158,6 +160,14 @@ var AnimationSupport = { } }, + /** + * Adds new path on already existing path for this character. + * @public + */ + addPath: function(path,dur){ + this.addAnimation({},dur); + this._animPath.push({path:path,duration:this.totalDuration}); + }, /** * Sets new animation for this character. * @public @@ -165,8 +175,6 @@ var AnimationSupport = { setAnimation: function (newProp) { this._prop = newProp; }, - - /** * Sets the delta values of x, y and z for events * @param {Object} obj - Object contains dX, dY and dZ as keys @@ -284,6 +292,9 @@ kind.concatHandler = function (ctor, props, instance) { if ((props.animate && typeof props.animate != 'function' ) || (props.keyFrame && typeof props.keyFrame != 'function')) { animation.trigger(proto); + if(props.path){ + AnimationSupport.addPath(props.path,props.duration); + } } if (props.handleAnimationEvents && typeof props.handleAnimationEvents != 'function') { animation.register(proto); diff --git a/src/AnimationSupport/Director.js b/src/AnimationSupport/Director.js index 12ec4ba3a..ffebfe8b4 100644 --- a/src/AnimationSupport/Director.js +++ b/src/AnimationSupport/Director.js @@ -56,14 +56,16 @@ module.exports = { var pose, t, index = this.poseByTime(actor._animPose, since), props = actor._animPose[index], + ind = this.poseByTime(actor._animPath, since), + path = actor._animPath[ind].path, prevDur = actor._animPose[(index - 1) < 0 ? 0 : (index - 1)].duration, currentAnimSince = since - prevDur, runningDur = props.duration - prevDur; + actor.path = path; if (!props._startAnim) { pose = frame.getComputedProperty(actor.hasNode(), props.animate, actor.currentState); utils.mixin(props, pose); } - if (currentAnimSince < 0) return; if (currentAnimSince <= runningDur && runningDur !== 0) { t = currentAnimSince / runningDur;