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..a7eb13116 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,7 @@ docs/out out lcov-report lcov.info -npm-* \ No newline at end of file +npm-* +dist +.enyocache +test/dist \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index b4d1573b7..065e17d82 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: node_js +sudo: false node_js: - - "0.10" + - "0.12" cache: directories: - $HOME/.npm \ No newline at end of file diff --git a/css/mixins.less b/css/mixins.less new file mode 100644 index 000000000..a84e780da --- /dev/null +++ b/css/mixins.less @@ -0,0 +1,38 @@ +// 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; + } +} + +// 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(); } +} + +.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/gulpfile.js b/gulpfile.js index 0313895db..1912b83ed 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,21 +1,67 @@ '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 - .src('./lib/**.js') + .src('./src/**/*.js') .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/index.js b/index.js index 9b988016e..ca5e1a50e 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 = module.exports = require('./src/options'); +exports.version = '2.6.0-rc.1'; diff --git a/lib/AccessibilitySupport/AccessibilitySupport.js b/lib/AccessibilitySupport/AccessibilitySupport.js deleted file mode 100644 index ee60b756a..000000000 --- a/lib/AccessibilitySupport/AccessibilitySupport.js +++ /dev/null @@ -1,193 +0,0 @@ -var - dispatcher = require('../dispatcher'), - kind = require('../kind'); - - -/** -* 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; - }); - } -} - -/** -* @name AccessibilityMixin -* @mixin -*/ -module.exports = { - - /** - * @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: '', - - /** - * 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, - - /** - * @private - */ - observers: [ - {method: 'updateAccessibilityAttributes', path: [ - 'content', - 'accessibilityHint', - 'accessibilityLabel', - 'accessibilityAlert', - 'accessibilityLive', - 'accessibilityDisabled' - ]} - ], - - /** - * @method - * @private - */ - create: kind.inherit(function (sup) { - return function (props) { - sup.apply(this, arguments); - this.initAccessibility(); - }; - }), - - /** - * 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); - * }; - * }); - * ``` - * - * @protected - */ - 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'); - }, - - /** - * @private - */ - rendered: kind.inherit(function (sup) { - return function () { - sup.apply(this, arguments); - if (this.accessibilityPreventScroll) { - preventScroll(this.hasNode()); - } - }; - }) -}; diff --git a/lib/Button/ButtonAccessibilitySupport.js b/lib/Button/ButtonAccessibilitySupport.js deleted file mode 100644 index 5274ccfea..000000000 --- a/lib/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/lib/Checkbox/CheckboxAccessibilitySupport.js b/lib/Checkbox/CheckboxAccessibilitySupport.js deleted file mode 100644 index bf830665e..000000000 --- a/lib/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/lib/Group/GroupAccessibilitySupport.js b/lib/Group/GroupAccessibilitySupport.js deleted file mode 100644 index 56512b514..000000000 --- a/lib/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/lib/Input/InputAccessibilitySupport.js b/lib/Input/InputAccessibilitySupport.js deleted file mode 100644 index d2ad7aaa7..000000000 --- a/lib/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/lib/LightPanels/LightPanels.css b/lib/LightPanels/LightPanels.css deleted file mode 100644 index 73043e4ad..000000000 --- a/lib/LightPanels/LightPanels.css +++ /dev/null @@ -1,37 +0,0 @@ -.enyo-light-panels { - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - overflow: hidden; -} - -.enyo-light-panels > * { - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - box-sizing: border-box; -} - -.enyo-light-panels.horizontal.forwards > * { - transform: translateX(100%); - -webkit-transform: translateX(100%); -} - -.enyo-light-panels.horizontal.backwards > * { - transform: translateX(-100%); - -webkit-transform: translateX(-100%); -} - -.enyo-light-panels.vertical.forwards > * { - transform: translateY(100%); - -webkit-transform: translateY(100%); -} - -.enyo-light-panels.vertical.backwards > * { - transform: translateY(-100%); - -webkit-transform: translateY(-100%); -} \ No newline at end of file diff --git a/lib/LightPanels/LightPanels.js b/lib/LightPanels/LightPanels.js deleted file mode 100644 index 3c4cd9295..000000000 --- a/lib/LightPanels/LightPanels.js +++ /dev/null @@ -1,713 +0,0 @@ -require('enyo'); - -/** -* Contains the declaration for the {@link module:enyo/LightPanels~LightPanels} kind. -* @module enyo/LightPanels -*/ - -var - kind = require('../kind'), - dom = require('../dom'), - utils = require('../utils'), - asyncMethod = utils.asyncMethod; - -var - Control = require('../Control'), - ViewPreloadSupport = require('../ViewPreloadSupport'), - TaskManagerSupport = require('../TaskManagerSupport'); - -/** -* The configurable options used by {@link module:enyo/LightPanels~LightPanels} when pushing panels. -* -* @typedef {Object} enyo.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 -* immediately after each panel is created. -* @property {Number} [targetIndex] - The index of the panel to display, otherwise the last panel -* created will be displayed. -*/ - -/** -* A light-weight panels implementation that has basic support for CSS transitions between child -* components. -* -* @class LightPanels -* @extends module:enyo/Control~Control -* @ui -* @public -*/ -module.exports = kind( - /** @lends module:enyo/LightPanels~LightPanels.prototype */ { - - /** - * @private - */ - name: 'enyo.LightPanels', - - /** - * @private - */ - kind: Control, - - /** - * @private - */ - mixins: [ViewPreloadSupport, TaskManagerSupport], - - /** - * @private - */ - classes: 'enyo-light-panels', - - /** - * @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' - }, - - /** - * @private - */ - handlers: { - ontransitionend: 'transitionFinished' - }, - - /** - * @method - * @private - */ - 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(); - }; - }), - - - - /* - =============== - Change handlers - =============== - */ - - /** - * @private - */ - orientationChanged: function () { - this._axis = this.orientation == 'horizontal' ? 'X' : 'Y'; - }, - - /** - * @private - */ - directionChanged: function () { - this._direction = this.direction == 'forwards' ? 1 : this.direction == 'backwards' ? -1 : 0; - }, - - /** - * @private - */ - indexChanged: function (was) { - this.setupTransitions(was); - }, - - /** - * @private - */ - cacheViewsChanged: function () { - this.popOnForward = this.cacheViews; - }, - - - - /* - ======================= - Public accessor methods - ======================= - */ - - /** - * Retrieves the currently displayed panel. - * - * @return {Object} The currently displayed panel. - * @public - */ - getActivePanel: function () { - return this._currentPanel; - }, - - /** - * Retrieves the panels currently part of this control. - * - * @return {Array} The set of panels. - * @public - */ - getPanels: function () { - /*jshint -W093 */ - return (this._panels = this._panels || (this.controlParent || this).children); - }, - - - - /* - ===================== - Public action methods - ===================== - */ - - /** - * Animates to the specified panel index. - * - * @param {Number} index - The index of the panel we wish to animate a transition to. - * @public - */ - animateTo: function (index) { - var from = this.index; - this.index = index; - this.setupTransitions(from, true); - }, - - /** - * Transitions to the previous panel--i.e., the panel whose index value is one - * less than that of the current active panel. - * - * @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); - } - }, - - /** - * Transitions to the next panel--i.e., the panel whose index value is one - * greater than that of the current active panel. - * - * @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); - } - }, - - /** - * Creates a panel on top of the stack and increments index to select that component. - * - * @param {Object} info - The declarative {@glossary kind} definition. - * @param {Object} moreInfo - Additional properties to be applied (defaults). - * @param {module:enyo/LightPanels~PushPanelOptions} opts - Additional options to be used during - * panel pushing. - * @return {Object} The instance of the panel that was created on top of the stack. - * @public - */ - pushPanel: function (info, moreInfo, opts) { - if (opts && opts.purge) { - this.purge(); - } - - var lastIndex = this.getPanels().length - 1, - nextPanel = this.createPanel(info, moreInfo), - newIndex = lastIndex + 1; - if (this.cacheViews) { - this.pruneQueue([info]); - } - - nextPanel.render(); - - if (opts && opts.forcePostTransition && nextPanel.postTransition) { - nextPanel.postTransition(); - } - - if (!this.animate || (opts && opts.direct)) this.set('index', newIndex); - else this.animateTo(newIndex); - - // TODO: When pushing panels after we have gone back (but have not popped), we need to - // adjust the position of the panels after the previous index before our push. - return nextPanel; - }, - - /** - * Creates multiple panels on top of the stack and updates index to select the last one - * created. Supports an optional `opts` object as the third parameter. - * - * @param {Object[]} info - The declarative {@glossary kind} definitions. - * @param {Object} moreInfo - Additional properties to be applied (defaults). - * @param {module:enyo/LightPanels~PushPanelOptions} opts - Additional options to be used when - * pushing multiple panels. - * @return {null|Object[]} Array of the panels that were created on top of the stack, or - * `null` if panels could not be created. - * @public - */ - pushPanels: function (info, moreInfo, opts) { - if (opts && opts.purge) { - this.purge(); - } - - var lastIndex = this.getPanels().length, - newPanels = [], - newPanel, targetIdx, compareIdx, idx; - - for (idx = 0; idx < info.length; idx++) { - newPanel = this.createPanel(info[idx], moreInfo); - newPanels.push(newPanel); - if ((opts && opts.targetIndex != null && lastIndex + idx == opts.targetIndex) || idx == info.length - 1) { - newPanel.render(); - } else { - compareIdx = opts && opts.targetIndex != null ? opts.targetIndex : lastIndex + info.length; - this.shiftPanel(newPanel, compareIdx - this.index); - } - if (opts && opts.forcePostTransition && newPanel.postTransition) { - newPanel.postTransition(); - } - } - if (this.cacheViews) { - this.pruneQueue(info); - } - - targetIdx = (opts && opts.targetIndex != null) ? opts.targetIndex : lastIndex + newPanels.length - 1; - - if (!this.animate || (opts && opts.direct)) this.set('index', targetIdx); - else this.animateTo(targetIdx); - - return newPanels; - }, - - /** - * Destroys 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. - * @public - */ - popPanels: function (index, direction) { - var panels = this.getPanels(); - - if (direction < 0) { - while (panels.length > index + 1 && index >= 0) { - this.popPanel(panels.length - 1); - } - } else { - for (var panelIndex = index - 1; panelIndex >= 0; panelIndex--) { - this.popPanel(panelIndex, true); - } - } - }, - - /** - * Destroys 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 - */ - popPanel: function (index, preserve) { - var panels = this.getPanels(), - panel = panels[index]; - - if (panel) { - if (this.cacheViews) { - this.cacheView(panel, preserve); - } else { - panel.destroy(); - } - } - }, - - - - /* - ============================================================ - Public methods implementing the ViewPreloadSupport interface - ============================================================ - */ - - /** - * Determines the id of the given view. - * - * @param {Object} view - The view whose id we will determine. - * @return {String} The id of the given view. - * @public - */ - getViewId: function (view) { - 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._axis, 100 * this._direction + '%'); - }, - - - - /* - ====================================== - Public methods for queued task support - ====================================== - */ - - /** - * Enqueues a view that will eventually be pre-cached at an opportunistic time. - * - * @param {String} viewProps - The properties of the view to be enqueued. - * @param {Number} [priority] - The priority of the job. - * @public - */ - enqueuePanel: function (viewProps, priority) { - var viewId = this.getViewId(viewProps); - if (!this.isViewPreloaded(viewId)) { - this.addTask(function () { - // TODO: once the data layer is hooked into the run loop, we should no longer need - // to forcibly trigger the post transition work. - this.preCacheView(viewProps, {}, function (view) { - if (view.postTransition) { - view.postTransition(); - } - }); - }, priority || this.defaultPriority, viewId); - } - }, - - /** - * Enqueues a set of views that will eventually be pre-cached at an opportunistic time. - * - * @param {Array} viewPropsArray - A set of views to be enqueued. - * @param {Number} [priority] - The priority of the job. - * @public - */ - enqueuePanels: function (viewPropsArray, priority) { - for (var idx = 0; idx < viewPropsArray.length; idx++) { - this.enqueuePanel(viewPropsArray[idx], priority); - } - }, - - - - /* - ================= - Protected methods - ================= - */ - - /** - * Determines whether or not we should animate the panel transition. - * - * @return {Boolean} If `true`, the panels should animate. - * @protected - */ - shouldAnimate: function () { - return this.generated && this.getPanels().length > 1 && this.animate; - }, - - - - /* - ============== - Event handlers - ============== - */ - - /** - * @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)) { - 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._currentPanel) { - this._currentPanel.removeClass('transitioning'); - this._currentPanel.activated && this._currentPanel.activated(); - } - - if (this._previousPanel) { - this._previousPanel.removeClass('transitioning'); - this._previousPanel.deactivated && this._previousPanel.deactivated(); - } - } - }, - - - - /* - ======================= - Private support methods - ======================= - */ - - /** - * Retrieves a cached panel or, if not found, creates a new panel - * - * @param {Object} info - The declarative {@glossary kind} definition. - * @param {Object} moreInfo - Additional properties to be applied (defaults). - * @return {Object} - Found or created control - * @private - */ - createPanel: function (info, moreInfo) { - var panel, - panelId = this.getViewId(info); - - if (this.cacheViews && panelId) { - panel = this.restoreView(panelId); - } - - panel = panel || this.createComponent(info, moreInfo); - return panel; - }, - - /** - * 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 - * transitioning between the current and next panel. - * @private - */ - setupTransitions: function (previousIndex, animate) { - var panels = this.getPanels(), - nextPanel = panels[this.index], - trans, wTrans; - - this._indexDirection = this.index - previousIndex; - - if (nextPanel) { - if (!nextPanel.generated) { - nextPanel.render(); - } - - // 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 (this._currentPanel) { - this._currentPanel.applyStyle('-webkit-transition', wTrans); - this._currentPanel.applyStyle('transition', trans); - this._currentPanel.addClass('transitioning'); - } - - setTimeout(this.bindSafely(function () { - this.applyTransitions(nextPanel); - }), 16); - } else { - this.transitionDirect(nextPanel); - } - } - }, - - /** - * 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. - * @private - */ - applyTransitions: function (nextPanel, direct) { - // apply the transition for the next panel - dom.transformValue(nextPanel, 'translate' + this._axis, '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}); - } - }, - - /** - * 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. - * @private - */ - shiftPanel: function (panel, indexDirection) { - var value = (indexDirection > 0 ? -100 : 100) * this._direction + '%'; - dom.transformValue(panel, 'translate' + this._axis, value); - }, - - /** - * Destroys all panels. These will be queued for destruction after the next panel has loaded. - * - * @private - */ - purge: function () { - var panels = this.getPanels(); - this._garbagePanels = panels.slice(); - panels.length = 0; - this.index = -1; - }, - - /** - * Clean-up any panels queued for destruction. - * - * @private - */ - finalizePurge: function () { - var panels = this._garbagePanels, - panel; - while (panels.length) { - panel = panels.pop(); - if (this.cacheViews) { - this.cacheView(panel); - } - else { - panel.destroy(); - } - } - }, - - /** - * 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. - * - * @param {String} viewProps - The properties of the view to be enqueued. - * @private - */ - pruneQueue: function (viewProps) { - for (var idx = 0; idx < viewProps.length; idx++) { - this.removeTask(this.getViewId(viewProps[idx])); - } - } - -}); diff --git a/lib/Popup/PopupAccessibilitySupport.js b/lib/Popup/PopupAccessibilitySupport.js deleted file mode 100644 index 6c1ce3279..000000000 --- a/lib/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/lib/RichText/RichTextAccessibilitySupport.js b/lib/RichText/RichTextAccessibilitySupport.js deleted file mode 100644 index f42daae6a..000000000 --- a/lib/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/lib/Table/TableAccessibilitySupport.js b/lib/Table/TableAccessibilitySupport.js deleted file mode 100644 index 0e0f636a5..000000000 --- a/lib/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/lib/TableCell/TableCellAccessibilitySupport.js b/lib/TableCell/TableCellAccessibilitySupport.js deleted file mode 100644 index 6ebfeeddf..000000000 --- a/lib/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/lib/TableRow/TableRowAccessibilitySupport.js b/lib/TableRow/TableRowAccessibilitySupport.js deleted file mode 100644 index 0a1503fff..000000000 --- a/lib/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/lib/TextArea/TextAreaAccessibilitySupport.js b/lib/TextArea/TextAreaAccessibilitySupport.js deleted file mode 100644 index fbcbb5c42..000000000 --- a/lib/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 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.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/fullscreen/package.json b/lib/fullscreen/package.json deleted file mode 100644 index edc423f99..000000000 --- a/lib/fullscreen/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "main": "fullscreen.js", - "styles": [ - "fullscreen.css" - ] -} diff --git a/package.json b/package.json index 810267ce5..a899b0142 100644 --- a/package.json +++ b/package.json @@ -1,14 +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" - ], "keywords": [ "framework", "toolkit", @@ -36,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": "^0.5.1", + "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/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/src/AccessibilitySupport/AccessibilitySupport.js b/src/AccessibilitySupport/AccessibilitySupport.js new file mode 100644 index 000000000..2bdf893dc --- /dev/null +++ b/src/AccessibilitySupport/AccessibilitySupport.js @@ -0,0 +1,328 @@ +/** +* 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 of contained controls into view when +* those controls are explicitly focused. +* +* @private +*/ +function preventScroll (node, rtl) { + if (node) { + dispatcher.listen(node, 'scroll', function () { + node.scrollTop = 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; + }); + } +} + +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, the screen reader will read the label when the + * control is focused. + * + * @type {String} + * @default '' + * @public + */ + accessibilityLabel: '', + + /** + * `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 '' + * @public + */ + accessibilityHint: '', + + /** + * The `role` of the control. May be superseded by a truthy `accessibilityAlert` value. + * + * @type {String} + * @default '' + * @public + */ + accessibilityRole: '', + + /** + * `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 + * @public + */ + accessibilityAlert: false, + + /** + * `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 + * @public + */ + accessibilityLive: 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 + * @public + */ + accessibilityDisabled: false, + + /** + * 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 + * @public + */ + accessibilityPreventScroll: false, + + /** + * 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 + * @public + */ + + /** + * @method + * @private + */ + create: kind.inherit(function (sup) { + return function (props) { + sup.apply(this, arguments); + initAriaObservers(this); + }; + }), + + /** + * If `accessibilityDisabled` is `false`, sets the specified 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); + } + // 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); + } + }, + + /** + * @private + */ + rendered: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + if (this.accessibilityPreventScroll) { + preventScroll(this.hasNode(), this.rtl); + } + }; + }) +}; + +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/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/src/AnimationSupport/AnimationInterfaceSupport.js b/src/AnimationSupport/AnimationInterfaceSupport.js new file mode 100644 index 000000000..f45bcd933 --- /dev/null +++ b/src/AnimationSupport/AnimationInterfaceSupport.js @@ -0,0 +1,366 @@ +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", + "mousemove" + ], + + /** + * @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; + case "mousemove": + this.touchDragMove(inSender, inEvent, (inEvent, inSender.pageX) / 1.5, (inSender.pageY) / 1.5); + 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 (this.patterns[0].name === "Slideable" || this.patterns[0].name === "Parallax") { + this.setAnimateTwo = this.setAnimateThree; + } + }, + + /** + * @public + */ + commonTasks: function(delta, deltax, deltay) { + var patternsLength = this.patterns.length; + if (delta !== 0) { + delta = delta / Math.abs(delta); + } + //Call specific interface + for (var i = 0; i < patternsLength; i++) { + 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); + this.patterns[i].slide.call(current, (-1 * deltax / current.speed), (-1 * deltay / current.speed), 0); + current.start(true); + } + } else { + this.patterns[i].slide.call(this, (-1 * deltax), (-1 * deltay), 0); + } + } + if (this.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 (this.patterns && Object.prototype.toString.call(this.patterns) === "[object Array]" && (len = this.patterns.length)) { + for (i = 0; i < len; i++) { + /*if (typeof this.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); + + this.patterns = aPattern; + var len = this.patterns.length; + for (var i = 0; i < len; i++) { + extend(this.patterns[i], proto); + } + animator.register(proto); + } +}; diff --git a/src/AnimationSupport/AnimationSupport.js b/src/AnimationSupport/AnimationSupport.js new file mode 100644 index 000000000..5f129c65e --- /dev/null +++ b/src/AnimationSupport/AnimationSupport.js @@ -0,0 +1,306 @@ +require('enyo'); + +var + kind = require('../kind'), + animation = require('./Core'), + activator = require('./KeyFrame'), + delegator = require('./EventDelegator'), + EventEmitter = require('../EventEmitter'), + FrameEditor = require('./FrameEditor'), + frame = require('./Frame'), + utils = require('../utils'); + +var extend = kind.statics.extend; + +kind.concatenated.push('animation'); + +var AnimationSupport = { + + /** + * @private + */ + //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) + * 'resumed' - Character animation has resumed(within rAF) + * 'completed' - Character animation has finished(within rAF) + * @private + */ + 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 + */ + animDelta: [], + + + vScrollX: 0, + + vScrollY: 0, + + vScrollZ: 0, + + prevDur: 0, + + totalDuration: 0, + + + /** + * Maximum threshold for animation + * @private + */ + animMaxThreshold: [], + + _animPose: [], + + _animPath: [], + + _pose: [], + + mixins: [EventEmitter, FrameEditor], + + _eventCache: {}, + + + /** + * 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 ? 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; + }, + + /** + * Gets current state of animation for this character + * @public + */ + initiate: function (current) { + 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); + + 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, duration) { + 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}); + } + }, + + /** + * 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 + */ + 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 + * @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 + */ + getDuration: function() { + return 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._startTime = utils.perfNow() + (delay || 0) ; + this._lastTime = this._startTime + this._duration; + this.animating = true; + this.active = active; + }, + + /** + * Trigger the registered event to all the listeners + * @public + */ + triggerEvent: function () { + this.deltaChanged = false; + return delegator.emitEvent(this, this.getAnimationDelta()); + }, + + /** + * @private + */ + rendered: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + this.initiate(); + if (this.handleAnimationEvents) { + delegator.register(this); + } + }; + }), + + /** + * @private + */ + destroy: kind.inherit(function(sup) { + return function() { + animation.remove(this); + animation.deRegister(this); + if (this.handleAnimationEvents) { + delegator.deRegister(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 || 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' ) || + (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); + } + 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 new file mode 100644 index 000000000..723bd4fdf --- /dev/null +++ b/src/AnimationSupport/Core.js @@ -0,0 +1,221 @@ +require('enyo'); + +var + kind = require('../kind'), + animation = require('../animation'), + utils = require('../utils'), + director = require('./Director'); + +var ts, wasTs, + 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) { + var i = this.chracs.indexOf(curr); + if (i >= 0) this.chracs.splice(i, 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 + * + * @parameter charc- Animation character + * + * @public + */ + register: function (charc) { + this.deRegister(charc); + this.evnts.push(charc); + this.remove(charc); + charc.animating = true; + + 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); + }, + + /** + * @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; + + if (len <= 0) { + this.cancel(); + this.running = false; + return; + } + + for (i = 0; i < len; i++) { + curr = this.chracs[i]; + if (curr && curr.ready()) { + ts = utils.perfNow(); + director.take(curr, ts - (curr.wasTs || ts)); + curr.wasTs = ts; + } + } + this.start(); + }, + + /** + * @private + */ + eventLoop: function () { + 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()) { + if (curr.deltaChanged) { + status = curr.triggerEvent(); + } + if (!status && curr.eventCacheUpdated) { + director.shot(curr, ts - (wasTs || ts)); + } + } + wasTs = ts; + } + this.dummy(); + }, + + /** + * TODO: Merge this implementation with actual start + * @private + */ + dummy: function () { + animation.requestAnimationFrame(this.eventLoop.bind(this)); + } +}); \ No newline at end of file diff --git a/src/AnimationSupport/Director.js b/src/AnimationSupport/Director.js new file mode 100644 index 000000000..ffebfe8b4 --- /dev/null +++ b/src/AnimationSupport/Director.js @@ -0,0 +1,136 @@ +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 + */ + + take: function(actor, ts) { + var dur = actor.totalDuration, + tm = actor.rolePlay(ts); + + if (tm < 0) return; + if (tm < dur) { + this.action(actor, tm); + } else { + this.action(actor, tm); + this.cut(actor); + } + }, + + cut: function (actor) { + actor.animating = false; + actor.timeline = 0; + 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], + 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; + tween.step(actor, props, ( t > 0.98) ? t = 1 : 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); + + if(duration === 0) { + return startIndex; + } + + 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; + }, + + 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/Easings.js b/src/AnimationSupport/Easings.js new file mode 100644 index 000000000..cfcbf6d58 --- /dev/null +++ b/src/AnimationSupport/Easings.js @@ -0,0 +1,522 @@ +/** + * Interface to achieve Easings in various animations + * + * @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; + 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/EventDelegator.js b/src/AnimationSupport/EventDelegator.js new file mode 100644 index 000000000..b2691cf27 --- /dev/null +++ b/src/AnimationSupport/EventDelegator.js @@ -0,0 +1,222 @@ +require('enyo'); + +var + 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, +* 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: [ + "drag", + "scroll", + "dragstart", + "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.handleAnimationEvents || {}; + for (var key in events) { + this.addRemoveListener(charc, key, events[key]); + } + }, + + /** + * 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.handleAnimationEvents || {}; + for (var key in events) { + this.addRemoveListener(charc, key, events[key], true); + } + }, + + /** + * @private + */ + 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, inEvent) { + sender.touchX = inEvent.targetTouches[0].pageX; + sender.touchY = inEvent.targetTouches[0].pageY; + }, + + /** + * @private + */ + touchmoveEvent: function (sender, inEvent) { + var x = inEvent.targetTouches[0].pageX, + y = inEvent.targetTouches[0].pageY; + + if(x !== 0 || y !== 0) { + /*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]; + } + }, + + /** + * @private + */ + touchendEvent: function (sender, ev) { + sender.touchX = 0; + sender.touchY = 0; + }, + + /** + * @private + */ + scrollEvent: function (inSender, inEvent) { + inEvent.dX = inEvent.deltaX; + inEvent.dY = inEvent.deltaY; + inEvent.dZ = 0; + inEvent.vtype = eventsMap[inEvent.type]; + + inSender.setAnimationDelta(inEvent); + inSender._virtualEvent = eventsMap[inEvent.type]; + }, + + /** + * @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;*/ + + var o = { + dX: this.deltaX, + dY: this.deltaY, + dZ: 0 + }; + this.setAnimationDelta(o); + + this.eventName = eventsMap[inEvent.type]; + } + }, + + /** + * @private + */ + 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]; + } +}; + +module.exports = EventDelegator; \ No newline at end of file diff --git a/src/AnimationSupport/Fadeable.js b/src/AnimationSupport/Fadeable.js new file mode 100644 index 000000000..425589767 --- /dev/null +++ b/src/AnimationSupport/Fadeable.js @@ -0,0 +1,86 @@ +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) { + this.doFadeStart(); + }*/ +}; diff --git a/src/AnimationSupport/Flippable.js b/src/AnimationSupport/Flippable.js new file mode 100644 index 000000000..aaa5c727c --- /dev/null +++ b/src/AnimationSupport/Flippable.js @@ -0,0 +1,71 @@ +var + kind = require('../kind'), + animation = require('./Core'); + +/** + * Interface to achieve flip animation + * + * @module enyo/AnimationSupport/Flippable + * @public + */ +module.exports = { + + /** + * @private + */ + name: 'Flippable', + + /** + * 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 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 + * 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/src/AnimationSupport/Frame.js b/src/AnimationSupport/Frame.js new file mode 100644 index 000000000..2393d0f77 --- /dev/null +++ b/src/AnimationSupport/Frame.js @@ -0,0 +1,359 @@ +require('enyo'); + +var + Dom = require('../dom'), + Vector = require('./Vector'), + Matrix = require('./Matrix'); + +var + 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}; + +/** +* 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) || 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; + }, + + + /** + * 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 + */ + getComputedProperty: function (node, props, initial) { + if(!node) return; + + var eP = {}, + sP = initial ? this.copy(initial) : {}, + tP = {}, + dP = {}, + m, k, v, + s = initial ? undefined : Dom.getComputedStyle(node); + + for (k in props) { + v = sP[k]; + if (!this.isTransform(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] = 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 || Dom.getComputedStyle(node)) || 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, matrix: m, props: props}; + }, + + getComputedDistance: function (prop, initalProp, finalProp) { + var k, sV, eV, dst, tot = 0; + for (k in prop) { + sV = k==='rotate' ? Vector.quantToVector(initalProp[k]) : initalProp[k]; + eV = finalProp[k]; + dst = Vector.distance(eV, sV); + tot += dst; + } + return tot; + } +}; \ No newline at end of file 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/HierarchicalMixin.js b/src/AnimationSupport/HierarchicalMixin.js new file mode 100644 index 000000000..e618f58c4 --- /dev/null +++ b/src/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/src/AnimationSupport/KeyFrame.js b/src/AnimationSupport/KeyFrame.js new file mode 100644 index 000000000..61b904498 --- /dev/null +++ b/src/AnimationSupport/KeyFrame.js @@ -0,0 +1,133 @@ +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, 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); + }, + + /** + * 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; + this.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"; + charc.addAnimation(charc.keyProps[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 + charc.keyframeCallback && charc.keyframeCallback(this); + } +}; diff --git a/src/AnimationSupport/Matrix.js b/src/AnimationSupport/Matrix.js new file mode 100644 index 000000000..fb59144ed --- /dev/null +++ b/src/AnimationSupport/Matrix.js @@ -0,0 +1,225 @@ +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; + } +}; \ No newline at end of file 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 new file mode 100644 index 000000000..77284ba94 --- /dev/null +++ b/src/AnimationSupport/Slideable.js @@ -0,0 +1,98 @@ +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; + 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/src/AnimationSupport/Tween.js b/src/AnimationSupport/Tween.js new file mode 100644 index 000000000..cf419dce3 --- /dev/null +++ b/src/AnimationSupport/Tween.js @@ -0,0 +1,266 @@ +require('enyo'); + +var + frame = require('./Frame'), + easings = require('./Easings'), + matrixUtil = require('./Matrix'), + Vector = require('./Vector'), + utils = require('../utils'); + +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 = { + + /** + * @private + */ + step: function(charc, pose, t, d) { + var k, c, pts, tState, oState, ease, points; + + node = charc.node; + newState = pose._endAnim; + ease = pose.animate && pose.animate.ease ? pose.animate.ease: this.ease; + oldState = pose._startAnim; + charc.currentState = charc.currentState || {}; + + 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])); + } 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(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 = oldState[k]; + c = this.slerp; + } else { + tState = newState[k]; + oState = oldState[k]; + c = this.lerp; + } + cState = c(oState, tState, ease(t, d), cState); + } + } + + if (!frame.isTransform(k)) { + frame.setProperty(node, k, cState); + } + charc.currentState[k] = cState; + } + } 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, + charc.currentState.scale, + charc.currentState.skew, + charc.currentState.perspective + ); + frame.accelerate(node, matrix); + + charc.animationStep && charc.animationStep(t,matrix); + }, + + /** + * @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; + }, + + 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; + }, + + /** + * @public + * @params t: time, points: knot and control points, vR: resulting point + */ + getBezier: function(t, points, vR, isPath) { + + if (!vR) vR = []; + + var i, j, + c = points.length, + l = points[0].length, + lastIndex = (c - 1), + 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(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; + } + +}; diff --git a/src/AnimationSupport/Vector.js b/src/AnimationSupport/Vector.js new file mode 100644 index 000000000..7d46b85b6 --- /dev/null +++ b/src/AnimationSupport/Vector.js @@ -0,0 +1,279 @@ +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 (v, s) { + return [v[0] / s, v[1] / s, v[2] / s]; + }, + + /** + * Add vector/quant with a vector/quant. + * @public + */ + 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) { + 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) { + 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 (v1, v2, d) { + d = Math.sqrt( + (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; + }, + + /** + * 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 (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); + + 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 diff --git a/src/AnimationSupport/package.json b/src/AnimationSupport/package.json new file mode 100644 index 000000000..8454c0c27 --- /dev/null +++ b/src/AnimationSupport/package.json @@ -0,0 +1,3 @@ +{ + "main": "AnimationSupport.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 88% rename from lib/BackgroundTaskManager.js rename to src/BackgroundTaskManager.js index f5f15d7a2..58fa189c6 100644 --- a/lib/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/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 72% rename from lib/Button/Button.js rename to src/Button/Button.js index f985a1552..f6ccbcee9 100644 --- a/lib/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/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 93% rename from lib/Checkbox/Checkbox.js rename to src/Checkbox/Checkbox.js index a46b9da9b..d58885aa8 100644 --- a/lib/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/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 97% rename from lib/Collection.js rename to src/Collection.js index e151019a2..21241f53a 100644 --- a/lib/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. @@ -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 @@ -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}. @@ -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/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 78% rename from lib/ContentAreaSupport.js rename to src/ContentAreaSupport.js index e159dc5cf..8ffefed56 100644 --- a/lib/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/lib/Control.js b/src/Control/Control.js similarity index 95% rename from lib/Control.js rename to src/Control/Control.js index 83876eda3..ae2d83aab 100644 --- a/lib/Control.js +++ b/src/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; @@ -87,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 @@ -239,7 +241,7 @@ var Control = module.exports = kind( * @public */ renderOnShow: false, - + /** * @todo Find out how to document "handlers". * @public @@ -408,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 */ @@ -453,7 +455,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; @@ -527,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)); @@ -645,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 + ';')); @@ -677,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; }, @@ -787,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 @@ -797,7 +802,7 @@ var Control = module.exports = kind( this.set('canGenerate', true); this.render(); } - + this.sendShowingChangedEvent(was); } @@ -824,7 +829,7 @@ var Control = module.exports = kind( // the state of renderOnShow this.canGenerate = (this.canGenerate && !this.renderOnShow); }, - + /** * @private */ @@ -878,9 +883,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 */ @@ -895,7 +918,7 @@ var Control = module.exports = kind( * @public */ isFullscreen: function () { - return (this.hasNode() && this.node === fullscreen.getFullscreenElement()); + return (this.hasNode() && this.node === Control.Fullscreen.getFullscreenElement()); }, /** @@ -910,7 +933,7 @@ var Control = module.exports = kind( requestFullscreen: function () { if (!this.hasNode()) return false; - if (fullscreen.requestFullscreen(this)) { + if (Control.Fullscreen.requestFullscreen(this)) { return true; } @@ -927,7 +950,7 @@ var Control = module.exports = kind( */ cancelFullscreen: function() { if (this.isFullscreen()) { - fullscreen.cancelFullscreen(); + Control.Fullscreen.cancelFullscreen(); return true; } @@ -1108,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` @@ -1174,8 +1207,9 @@ 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) { + Dom.removeNode(node); } }, @@ -1663,7 +1697,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 : ''; @@ -1685,8 +1719,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 @@ -1699,3 +1733,9 @@ Control.FloatingLayer = FloatingLayer(Control); * @public */ Control.floatingLayer = new Control.FloatingLayer({id: 'floatingLayer'}); + +/** +* @static +* @public +*/ +Control.Fullscreen = fullscreen(Control); diff --git a/lib/floatingLayer.js b/src/Control/floatingLayer.js similarity index 70% rename from lib/floatingLayer.js rename to src/Control/floatingLayer.js index 56dab2434..b58708e07 100644 --- a/lib/floatingLayer.js +++ b/src/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/src/Control/fullscreen.js b/src/Control/fullscreen.js new file mode 100644 index 000000000..82e128bd5 --- /dev/null +++ b/src/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/src/Control/fullscreen.less b/src/Control/fullscreen.less new file mode 100644 index 000000000..f87cb3810 --- /dev/null +++ b/src/Control/fullscreen.less @@ -0,0 +1,29 @@ +/* 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; +} + +// 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 diff --git a/src/Control/package.json b/src/Control/package.json new file mode 100644 index 000000000..159482766 --- /dev/null +++ b/src/Control/package.json @@ -0,0 +1,6 @@ +{ + "main": "Control.js", + "styles": [ + "fullscreen.less" + ] +} diff --git a/lib/Controller.js b/src/Controller.js similarity index 88% rename from lib/Controller.js rename to src/Controller.js index ab19643dd..cf2c52f91 100644 --- a/lib/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. * @@ -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/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 95% rename from lib/DataGridList/DataGridList.js rename to src/DataGridList/DataGridList.js index 0fd24593e..ba2c9e5ad 100644 --- a/lib/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/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 95% rename from lib/DataList/DataList.js rename to src/DataList/DataList.js index 9d3bcf110..7bce1a45e 100644 --- a/lib/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 @@ -224,18 +225,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); }); } } @@ -455,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. 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 92% rename from lib/DataRepeater.js rename to src/DataRepeater.js index d11551d83..49de3d17c 100644 --- a/lib/DataRepeater.js +++ b/src/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 = []; + } }, /** @@ -562,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}. * @@ -586,7 +617,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 +640,14 @@ 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; + if(c) c.syncBindings({force: true, all: true}); + } } this.notifyObservers('selected'); }, @@ -753,7 +791,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/lib/DataTable.js b/src/DataTable.js similarity index 90% rename from lib/DataTable.js rename to src/DataTable.js index c3e553f34..1f6ecfe49 100644 --- a/lib/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/lib/DragAvatar.js b/src/DragAvatar.js similarity index 87% rename from lib/DragAvatar.js rename to src/DragAvatar.js index 029aa1fe5..2cf9fe148 100644 --- a/lib/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(); +* } * }); * ``` * diff --git a/lib/Drawer.js b/src/Drawer.js similarity index 95% rename from lib/Drawer.js rename to src/Drawer.js index 8200b59bb..2ab3d4e25 100644 --- a/lib/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 */ 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 78% rename from lib/FluxStore.js rename to src/FluxStore.js index 24fe9a80c..57ac44915 100644 --- a/lib/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/lib/FormData.js b/src/FormData.js similarity index 96% rename from lib/FormData.js rename to src/FormData.js index c024333bc..bdf55fbc7 100644 --- a/lib/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/lib/Group/Group.js b/src/Group/Group.js similarity index 90% rename from lib/Group/Group.js rename to src/Group/Group.js index c0ad9ddba..eabd555e3 100644 --- a/lib/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/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 97% rename from lib/HTMLStringDelegate.js rename to src/HTMLStringDelegate.js index 3f79cdc7d..835009a44 100644 --- a/lib/HTMLStringDelegate.js +++ b/src/HTMLStringDelegate.js @@ -244,7 +244,13 @@ module.exports = { * @private */ teardownRender: function (control, cache) { - if (control.generated) this.teardownChildren(control, 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); } diff --git a/src/History.js b/src/History.js new file mode 100644 index 000000000..eaaeb9890 --- /dev/null +++ b/src/History.js @@ -0,0 +1,417 @@ +/** +* 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 +*/ + +/** +* 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'), + 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, + + // 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; + +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, + + /** + * 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 + */ + components: [ + {kind: Signals, onkeyup: 'handleKeyUp'} + ], + + /** + * @private + */ + enabledChanged: function () { + // reset private members + if (!this.enabled) this.clear(); + }, + + /** + * Resets the value to false if the platform does not support the History API + * + * @private + */ + updateHistoryChanged: function () { + this.updateHistory = this.updateHistory && _supports; + }, + + // Public methods + + /** + * Adds a new history entry + * + * @param {module:enyo/History~HistoryEntry} entry Object describing the history entry + * + * @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 + * + * @public + */ + 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]; + }, + + /** + * 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 ql = _queue.length, + hl = _history.length; + + _popQueueCount = 0; + _pushQueued = false; + _abortQueueProcessing = _processing; + _processing = false; + if (ql) _queue.splice(0, ql); + if (hl) this.drop(hl); + }, + + /** + * Returns `true` when enyo/History is currently handling a popstate event and invoking the + * callbacks for any popped entries. + * + * @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 + + /** + * 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 next, i, entries; + + this.silencePushEntries(); + + while (_queue.length && !_abortQueueProcessing) { + 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 (i = entries.length - 1; i >= 0; --i) { + // and call each handler if it exists + this.processPopEntry(entries[i]); + } + } + // otherwise we just drop the entries and do nothing + } + } + if (_abortQueueProcessing) { + _abortQueueProcessing = false; + } else { + _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 { + next.silenced = false; + } + } 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; + 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) { + _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 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 { + // 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; + 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); + } + }, + + /** + * 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 = entry.context && entry.context.id || 'anonymous', + location = entry.location || ''; + _history.push(entry); + if (this.updateHistory && !silenced) { + global.history.pushState({id: id}, '', location); + } + }, + + /** + * 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.context.getShowing()) { + this.pop(); + } + return true; + } + +}); + +dispatcher.listen(global, 'popstate', EnyoHistory.handlePop.bind(EnyoHistory)); \ No newline at end of file diff --git a/lib/HorizontalDelegate.js b/src/HorizontalDelegate.js similarity index 98% rename from lib/HorizontalDelegate.js rename to src/HorizontalDelegate.js index 361386b1c..2d62bdb27 100644 --- a/lib/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/lib/Image/Image.css b/src/Image/Image.css similarity index 83% rename from lib/Image/Image.css rename to src/Image/Image.css index a795b7638..e8e51c5b7 100644 --- a/lib/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; diff --git a/lib/Image/Image.js b/src/Image/Image.js similarity index 82% rename from lib/Image/Image.js rename to src/Image/Image.js index fdff569fb..1d70cfa94 100644 --- a/lib/Image/Image.js +++ b/src/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 the documentation for {@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 @@ -101,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 */ @@ -151,6 +155,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 @@ -190,7 +199,6 @@ module.exports = kind( * @private */ handlers: { - onload: 'handleLoad', onerror: 'handleError' }, @@ -247,8 +255,8 @@ module.exports = kind( if (this.sizing) { this.addClass(this.sizing); } + this.updateSource(); if (this.generated) { - this.updateSource(); this.render(); } }, @@ -262,19 +270,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 */ @@ -299,11 +294,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); - } - // 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); + } else { + // when update source + if (!prop || prop == 'placeholder') { + this.applyStyle('background-image', plUrl); + } this.setAttribute('src', src); } }, @@ -321,7 +316,7 @@ module.exports = kind( }), /** - * @lends enyo.Image + * @lends module:enyo/Image~Image * @private */ statics: { @@ -341,5 +336,15 @@ module.exports = kind( 'E7IiAvPjxsaW5lIHgxPSIwIiB5MT0iMCIgeDI9IjEwMCUiIHkyPSIxMDAlIiBzdHlsZT0ic3Ryb2tlOiAjNDQ0' + 'OyBzdHJva2Utd2lkdGg6IDE7IiAvPjxsaW5lIHgxPSIxMDAlIiB5MT0iMCIgeDI9IjAiIHkyPSIxMDAlIiBzdH' + 'lsZT0ic3Ryb2tlOiAjNDQ0OyBzdHJva2Utd2lkdGg6IDE7IiAvPjwvc3ZnPg==' - } + }, + + // Accessibility + + /** + * @default img + * @type {String} + * @see enyo/AccessibilitySupport~AccessibilitySupport#accessibilityRole + * @public + */ + accessibilityRole: 'img' }); 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 96% rename from lib/Input/Input.js rename to src/Input/Input.js index f06d27259..a450ca098 100644 --- a/lib/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/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 95% rename from lib/InputBinding.js rename to src/InputBinding.js index 3408ecf45..ac2d75810 100644 --- a/lib/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/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 98% rename from lib/Jsonp.js rename to src/Jsonp.js index 321b270f7..83d0f1840 100644 --- a/lib/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/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 84% rename from lib/Layout.js rename to src/Layout.js index 214846b22..a3e7e78cf 100644 --- a/lib/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 () { diff --git a/src/LightPanels/LightPanel.js b/src/LightPanels/LightPanel.js new file mode 100644 index 000000000..fe334da90 --- /dev/null +++ b/src/LightPanels/LightPanel.js @@ -0,0 +1,73 @@ +/** +* Contains the declaration for the {@link module:enyo/LightPanels~LightPanel} kind. +* @module enyo/LightPanels +*/ + +require('enyo'); + +var + kind = require('../kind'); + +var + Control = require('../Control'); + +/** +* @enum {Number} +* @memberof module:enyo/LightPanels~LightPanel +* @public +*/ +var States = { + ACTIVE: 1, + ACTIVATING: 2, + DEACTIVATING: 3, + INACTIVE: 4 +}; + +/** +* A lightweight 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/LightPanels~LightPanel.prototype */ { + + /** + * @private + */ + name: 'enyo.LightPanel', + + /** + * @private + */ + kind: Control, + + /** + * The current [state]{@link module:enyo/LightPanels~LightPanel#States}. + * + * @type {module:enyo/LightPanels~LightPanel#States} + * @default null + * @public + */ + state: States.INACTIVE, + + /** + * This overridable method is called before a transition. + * + * @public + */ + preTransition: function () {}, + + /** + * This overridable method is called after a transition. + * + * @public + */ + postTransition: function () {} + +}); + +module.exports.States = States; \ No newline at end of file diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js new file mode 100644 index 000000000..f0ce6b1a7 --- /dev/null +++ b/src/LightPanels/LightPanels.js @@ -0,0 +1,854 @@ +require('enyo'); + +/** +* Contains the declaration for the {@link module:enyo/LightPanels~LightPanels} kind. +* @module enyo/LightPanels +*/ + +var + kind = require('../kind'), + dom = require('../dom'), + animation = require('../animation'), + utils = require('../utils'); + +var + Control = require('../Control'), + ViewPreloadSupport = require('../ViewPreloadSupport'), + TaskManagerSupport = require('../TaskManagerSupport'); + +var + LightPanel = require('./LightPanel'), + States = LightPanel.States; + +var + trans, wTrans; + +/** +* @enum {Number} +* @memberof module:enyo/LightPanels~LightPanels +* @public +*/ +var Direction = { + FORWARDS: 1, + BACKWARDS: -1 +}; + +/** +* @enum {Number} +* @memberof module:enyo/LightPanels~LightPanels +* @public +*/ +var Orientation = { + HORIZONTAL: 'X', + VERTICAL: 'Y' +}; + +/** +* The configurable options used by {@link module:enyo/LightPanels~LightPanels} when pushing panels. +* +* @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 +* 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. +*/ + +/** +* 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 +*/ +module.exports = kind( + /** @lends module:enyo/LightPanels~LightPanels.prototype */ { + + /** + * @private + */ + name: 'enyo.LightPanels', + + /** + * @private + */ + kind: Control, + + /** + * @private + */ + mixins: [ViewPreloadSupport, TaskManagerSupport], + + /** + * @private + */ + classes: 'enyo-light-panels', + + /** + * @private + */ + defaultKind: LightPanel, + + /** + * @private + */ + accessibilityPreventScroll: true, + + /** + * The index of the active panel. + * + * @type {Number} + * @default 0 + * @public + */ + index: 0, + + /** + * 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, + + /** + * 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 {@linkplain 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:enyo/LightPanels~LightPanels#Orientation}. + * + * @type {String} + * @default {@link module:enyo/LightPanels~LightPanels#Orientation.HORIZONTAL} + * @public + */ + orientation: Orientation.HORIZONTAL, + + /** + * The direction of the panel movement. Possible values from + * {@link module:enyo/LightPanels~LightPanels#Direction}. + * + * @type {String} + * @default {@link module:enyo/LightPanels~LightPanels#Direction.FORWARDS} + * @public + */ + 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 + */ + tools: [ + {kind: Control, name: 'client', classes: 'panels-container', ontransitionend: 'transitionFinished', onwebkitTransitionEnd: 'transitionFinished'} + ], + + /** + * @private + */ + create: function () { + Control.prototype.create.apply(this, arguments); + this.updateTransforms(); + this.orientationChanged(); + this.directionChanged(); + this.animateChanged(); + 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) { + Control.prototype.addChild.apply(this, arguments); + if (control.parent === this.$.client) control.addClass('offscreen'); + }, + + + + /* + =============== + Change handlers + =============== + */ + + /** + * @private + */ + directionChanged: function (was) { + 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()); + } + }, + + /** + * @private + */ + 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 + */ + durationChanged: function () { + this.updateTransforms(); + }, + + /** + * @private + */ + timingFunctionChanged: function () { + this.updateTransforms(); + }, + + /** + * @private + */ + animateChanged: function () { + dom.transform(this.$.client, {translateZ: this.animate ? 0 : null}); + }, + + /** + * @private + */ + 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 || (opts && opts.force)) { + this.setupTransitions(was); + } + }, + + + + /* + ======================= + Public accessor methods + ======================= + */ + + /** + * Retrieves the currently displayed panel. + * + * @return {Object} The currently displayed panel. + * @public + */ + getActivePanel: function () { + return this._currentPanel; + }, + + /** + * Retrieves the panels currently part of this control. + * + * @return {Array} The set of panels. + * @public + */ + getPanels: function () { + /*jshint -W093 */ + return (this._panels = this._panels || (this.controlParent || this).children); + }, + + + + /* + ===================== + Public action methods + ===================== + */ + + /** + * Animates to the specified panel index. + * + * @param {Number} index - The index of the panel we wish to animate a transition to. + * @public + */ + animateTo: function (index) { + var from = this.index; + this.index = index; + this.notifyObservers('index'); + this.setupTransitions(from, true); + }, + + /** + * Transitions to the previous panel--i.e., the panel whose index value is one + * less than that of the current active panel. + * + * @public + */ + previous: function () { + 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); + } + } + }, + + /** + * Transitions to the next panel--i.e., the panel whose index value is one + * greater than that of the current active panel. + * + * @public + */ + next: function () { + 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); + } + } + }, + + /** + * Creates a panel on top of the stack and increments index to select that component. + * + * @param {Object} info - The declarative {@glossary kind} definition. + * @param {Object} moreInfo - Additional properties to be applied (defaults). + * @param {module:enyo/LightPanels~PushPanelOptions} opts - Additional options to be used during + * panel pushing. + * @return {Object} The instance of the panel that was created on top of the stack. + * @public + */ + pushPanel: function (info, moreInfo, opts) { + if (this.transitioning) return; + + if (opts && opts.purge) { + this.purge(); + } + + var lastIndex = this.getPanels().length - 1, + nextPanel = this.createPanel(info, moreInfo), + newIndex = lastIndex + 1; + if (this.cacheViews) { + this.pruneQueue([info]); + } + + nextPanel.render(); + + if (opts && opts.forcePostTransition && nextPanel.postTransition) { + nextPanel.postTransition(); + } + + 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 + // adjust the position of the panels after the previous index before our push. + return nextPanel; + }, + + /** + * Creates multiple panels on top of the stack and updates index to select the last one + * created. Supports an optional `opts` object as the third parameter. + * + * @param {Object[]} info - The declarative {@glossary kind} definitions. + * @param {Object} moreInfo - Additional properties to be applied (defaults). + * @param {module:enyo/LightPanels~PushPanelOptions} opts - Additional options to be used when + * pushing multiple panels. + * @return {null|Object[]} Array of the panels that were created on top of the stack, or + * `null` if panels could not be created. + * @public + */ + pushPanels: function (info, moreInfo, opts) { + if (this.transitioning) return true; + + if (opts && opts.purge) { + this.purge(); + } + + var lastIndex = this.getPanels().length, + newPanels = [], + newPanel, targetIdx, compareIdx, idx; + + for (idx = 0; idx < info.length; idx++) { + newPanel = this.createPanel(info[idx], moreInfo); + newPanels.push(newPanel); + if ((opts && opts.targetIndex != null && lastIndex + idx == opts.targetIndex) || idx == info.length - 1) { + newPanel.render(); + } else { + compareIdx = opts && opts.targetIndex != null ? opts.targetIndex : lastIndex + info.length; + this.shiftContainer(compareIdx - this.index); + } + if (opts && opts.forcePostTransition && newPanel.postTransition) { + newPanel.postTransition(); + } + } + if (this.cacheViews) { + this.pruneQueue(info); + } + + targetIdx = (opts && opts.targetIndex != null) ? opts.targetIndex : lastIndex + newPanels.length - 1; + + if (!this.animate || (opts && opts.direct)) this.set('index', targetIdx, {force: opts && opts.force}); + else this.animateTo(targetIdx); + + return newPanels; + }, + + /** + * 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 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. + * @public + */ + removePanels: function (index, direction) { + var panels = this.getPanels(), + i; + + if (direction < 0) { + for (i = panels.length - 1; i > index; i--) { + this.removePanel(panels[i]); + } + } else { + for (i = 0; i < index; i++) { + this.removePanel(panels[i], true); + } + } + }, + + /** + * Removes the specified panel. + * + * @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 + */ + removePanel: function (panel, preserve) { + if (panel) { + if (this.cacheViews) { + this.cacheView(panel, preserve); + } else { + panel.destroy(); + } + } + }, + + /** + * 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 = end - 1; idx >= start; idx--) { + this.removePanel(panels[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}); + }, + + + + /* + ============================================================ + Public methods implementing the ViewPreloadSupport interface + ============================================================ + */ + + /** + * Determines the id of the given view. + * + * @param {Object} view - The view whose id we will determine. + * @return {String} The id of the given view. + * @public + */ + getViewId: function (view) { + return view.panelId; + }, + + + + /* + ====================================== + Public methods for queued task support + ====================================== + */ + + /** + * Enqueues a view that will eventually be pre-cached at an opportunistic time. + * + * @param {String} viewProps - The properties of the view to be enqueued. + * @param {Number} [priority] - The priority of the job. + * @public + */ + enqueuePanel: function (viewProps, priority) { + var viewId = this.getViewId(viewProps); + if (!this.isViewPreloaded(viewId)) { + this.addTask(function () { + // TODO: once the data layer is hooked into the run loop, we should no longer need + // to forcibly trigger the post transition work. + this.preCacheView(viewProps, {}, function (view) { + if (view.postTransition) { + view.postTransition(); + } + }); + }, priority || this.defaultPriority, viewId); + } + }, + + /** + * Enqueues a set of views that will eventually be pre-cached at an opportunistic time. + * + * @param {Array} viewPropsArray - A set of views to be enqueued. + * @param {Number} [priority] - The priority of the job. + * @public + */ + enqueuePanels: function (viewPropsArray, priority) { + for (var idx = 0; idx < viewPropsArray.length; idx++) { + this.enqueuePanel(viewPropsArray[idx], priority); + } + }, + + + + /* + ================= + Protected methods + ================= + */ + + /** + * Determines whether or not we should animate the panel transition. + * + * @return {Boolean} If `true`, the panels should animate. + * @protected + */ + shouldAnimate: function () { + return this.generated && this.getPanels().length > 1 && this.animate; + }, + + + + /* + ======================= + Private support methods + ======================= + */ + + /** + * Updates the transform style strings we will apply to the panel container. + * + * @private + */ + updateTransforms: function () { + trans = 'transform ' + this.duration + 'ms ' + this.timingFunction; + wTrans = '-webkit-' + trans; + }, + + /** + * 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. + utils.asyncMethod(this, function () { + panel.postTransition(); + }); + } + } + }, + + /** + * 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 + */ + transitionFinished: function (sender, ev, direct) { + var prevPanel, currPanel; + + 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.cacheViews && this.index > 0)) { + this.removePanels(this.index, this._indexDirection); + } + + if (prevPanel) { + prevPanel.removeClass('shifted'); + prevPanel.addClass('offscreen'); + } + + this.cleanUpPanel(prevPanel); + this.cleanUpPanel(currPanel); + + this.removeClass('transitioning'); + this.transitioning = false; + } + }, + + /** + * Retrieves a cached panel or, if not found, creates a new panel + * + * @param {Object} info - The declarative {@glossary kind} definition. + * @param {Object} moreInfo - Additional properties to be applied (defaults). + * @return {Object} - Found or created control + * @private + */ + createPanel: function (info, moreInfo) { + var panel, + panelId = this.getViewId(info); + + if (this.cacheViews && panelId) { + panel = this.restoreView(panelId); + } + + panel = panel || this.createComponent(info, moreInfo); + return panel; + }, + + /** + * 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 + * transitioning between the current and next panel. + * @private + */ + setupTransitions: function (previousIndex, animate) { + var panels = this.getPanels(), + nextPanel = panels[this.index], + currPanel = this._currentPanel, + shiftCurrent, fnInitiateTransition; + + 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; + + // prepare the panel that will be deactivated + if (currPanel) { + currPanel.set('state', States.DEACTIVATING); + if (currPanel.preTransition) currPanel.preTransition(); + } + + // prepare the panel that will be activated + nextPanel.set('state', States.ACTIVATING); + if (!nextPanel.generated) nextPanel.render(); + if (nextPanel.preTransition) nextPanel.preTransition(); + + 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; + + this.addClass('transitioning'); + nextPanel.removeClass('offscreen'); + nextPanel.addRemoveClass('shifted', shiftCurrent); + if (currPanel) currPanel.addRemoveClass('shifted', !shiftCurrent); + + // timestamp will be truthy if this is triggered from a rAF + if (timestamp) animation.requestAnimationFrame(this.bindSafely('applyTransitions', nextPanel, animate)); + else this.applyTransitions(nextPanel, animate); + }); + + if (!this.generated || !animate) fnInitiateTransition(); + else animation.requestAnimationFrame(fnInitiateTransition); + } + }, + + /** + * Applies the transitions for moving between the current and next panel. + * + * @param {Object} nextPanel - The panel we are transitioning to. + * @param {Boolean} animate - If `true`, signifies that this is an animated transition. + * @private + */ + applyTransitions: function (nextPanel, animate) { + // move the panel container to its intended post-transition position + 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() || !animate) this.transitionFinished(null, null, true); + }, + + /** + * Shifts the given panel into its post-transition 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 + */ + shiftContainer: function (indexDirection, animate) { + var container = this.$.client, + 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 + '%'); + }, + + /** + * Destroys all panels. + * + * @private + */ + purge: function () { + var panels = this.getPanels(), + panel; + + while (panels.length) { + panel = panels.pop(); + if (this.cacheViews) { + this.cacheView(panel); + } + else { + panel.destroy(); + } + } + + this.index = -1; + }, + + /** + * Prunes the queue of to-be-cached panels in the event that any panels in the queue have + * already been instanced. + * + * @param {String} viewProps - The properties of the view to be enqueued. + * @private + */ + pruneQueue: function (viewProps) { + for (var idx = 0; idx < viewProps.length; idx++) { + this.removeTask(this.getViewId(viewProps[idx])); + } + } +}); + +module.exports.Panel = LightPanel; +module.exports.Direction = Direction; +module.exports.Orientation = Orientation; diff --git a/src/LightPanels/LightPanels.less b/src/LightPanels/LightPanels.less new file mode 100644 index 000000000..f86242e8a --- /dev/null +++ b/src/LightPanels/LightPanels.less @@ -0,0 +1,104 @@ +.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%); + -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%); + } + } + } + + > .panels-container { + width: 200%; + height: 100%; + + > * { + width: 50%; + } + } + } + + &.vertical { + &.forwards { + > .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%); + } + } + } + + > .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 { + visibility: hidden; + } + + .enyo-locale-right-to-left & > * { + direction: rtl; + } + } +} diff --git a/lib/LightPanels/package.json b/src/LightPanels/package.json similarity index 68% rename from lib/LightPanels/package.json rename to src/LightPanels/package.json index 1fd5137b3..2484c901d 100644 --- a/lib/LightPanels/package.json +++ b/src/LightPanels/package.json @@ -1,6 +1,6 @@ { "main": "LightPanels.js", "styles": [ - "LightPanels.css" + "LightPanels.less" ] } 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 98% rename from lib/LocalStorageSource.js rename to src/LocalStorageSource.js index 826e0cf71..0038b9edd 100644 --- a/lib/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/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 85% rename from lib/MediaSource.js rename to src/MediaSource.js index 1467f88b7..13a51da37 100644 --- a/lib/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'} * ]} * ``` * 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 95% rename from lib/Model.js rename to src/Model.js index da16979ac..63fa57aa8 100644 --- a/lib/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 @@ -240,13 +240,13 @@ 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 - * @see module:enyo/StateSupport~StateSupport + * @see {@link module:enyo/StateSupport~StateSupport} * @type {module:enyo/States~States} * @readonly * @public @@ -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 @@ -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 @@ -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}. * @@ -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/lib/ModelController.js b/src/ModelController.js similarity index 97% rename from lib/ModelController.js rename to src/ModelController.js index acd21a048..06a375eb5 100644 --- a/lib/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/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 93% rename from lib/MultipleDispatchComponent.js rename to src/MultipleDispatchComponent.js index 4732347f8..387b218cf 100644 --- a/lib/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/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 85% rename from lib/NewDataList.js rename to src/NewDataList.js index 44e47ce67..eb44b0767 100644 --- a/lib/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); }, /** @@ -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) { @@ -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 */ @@ -323,57 +343,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) { + init: 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; - } + 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/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 90% rename from lib/NewThumb/NewThumb.js rename to src/NewThumb/NewThumb.js index 4726b1982..7e1f0c74b 100644 --- a/lib/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/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 88% rename from lib/ObserverSupport.js rename to src/ObserverSupport.js index 66bfbcb02..1df5d353c 100644 --- a/lib/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/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 97% rename from lib/Popup/Popup.js rename to src/Popup/Popup.js index 4e03fbefd..89c7a7e72 100644 --- a/lib/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 */ @@ -132,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. * @@ -325,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); }; @@ -747,7 +732,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/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 99% rename from lib/PriorityQueue.js rename to src/PriorityQueue.js index ada196e3e..752ba4818 100644 --- a/lib/PriorityQueue.js +++ b/src/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 */ 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 83% rename from lib/ProxyObject.js rename to src/ProxyObject.js index a6be6e4a6..525b01413 100644 --- a/lib/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#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#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/lib/Relation.js b/src/Relation.js similarity index 98% rename from lib/Relation.js rename to src/Relation.js index c5894148c..6cccc89c9 100644 --- a/lib/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/lib/RelationalModel/RelationalCollection.js b/src/RelationalModel/RelationalCollection.js similarity index 72% rename from lib/RelationalModel/RelationalCollection.js rename to src/RelationalModel/RelationalCollection.js index a7bc4df63..4989f64b4 100644 --- a/lib/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/lib/RelationalModel/RelationalModel.js b/src/RelationalModel/RelationalModel.js similarity index 94% rename from lib/RelationalModel/RelationalModel.js rename to src/RelationalModel/RelationalModel.js index a15ea2d1b..90cd16d08 100644 --- a/lib/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 */ @@ -42,18 +42,23 @@ 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 - * enyo.kind({ + * var + * kind = require('enyo/kind'), + * RelationalModel = require('enyo/RelationalModel'); + * + * module.exports = kind({ * name: 'Person', - * kind: enyo.RelationalModel, + * kind: RelationalModel, * relations: [ * { * key: 'nicknames', 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 93% rename from lib/RelationalModel/manyToMany.js rename to src/RelationalModel/manyToMany.js index fe8c543db..221f9f7fa 100644 --- a/lib/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 module:enyo/RelationalModel~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/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 96% rename from lib/RelationalModel/toMany.js rename to src/RelationalModel/toMany.js index 209683a31..e2cdd1add 100644 --- a/lib/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 @@ -19,11 +19,11 @@ var * models. This is an internally-used class. * * @class toMany -* @extends Relation +* @extends module:enyo/Relation~Relation * @protected */ var toMany = module.exports = kind( - /** @lends module:enyo/toMany~toMany.prototype */ { + /** @lends module:enyo/RelationalModel~toMany.prototype */ { /** * @private diff --git a/lib/RelationalModel/toOne.js b/src/RelationalModel/toOne.js similarity index 97% rename from lib/RelationalModel/toOne.js rename to src/RelationalModel/toOne.js index c1c73a009..1dd3f0847 100644 --- a/lib/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'), @@ -10,11 +15,11 @@ var * model. This is an internally-used class. * * @class toOne -* @extends Relation +* @extends module:enyo/Relation~Relation * @protected */ var toOne = module.exports = kind( - /** @lends module:enyo/toOne~toOne.prototype */ { + /** @lends module:enyo/RelationalModel~toOne.prototype */ { /** * @private diff --git a/lib/Repeater.js b/src/Repeater.js similarity index 92% rename from lib/Repeater.js rename to src/Repeater.js index 4d43f8e4b..9c7116608 100644 --- a/lib/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. * @@ -233,5 +238,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/lib/RepeaterChildSupport.js b/src/RepeaterChildSupport.js similarity index 97% rename from lib/RepeaterChildSupport.js rename to src/RepeaterChildSupport.js index 8fa0a7e9f..46d665ba4 100644 --- a/lib/RepeaterChildSupport.js +++ b/src/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 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 97% rename from lib/RichText/RichText.js rename to src/RichText/RichText.js index 0588fbaae..dae00b0de 100644 --- a/lib/RichText/RichText.js +++ b/src/RichText/RichText.js @@ -9,12 +9,12 @@ require('enyo'); var kind = require('../kind'), - options = require('../options'), utils = require('../utils'), platform = require('../platform'); + var - Input = require('../Input'), - RichTextAccessibilitySupport = require('./RichTextAccessibilitySupport'); + Control = require('../Control'), + Input = require('../Input'); /** * The type of change to apply. Possible values are `'move'` and `'extend'`. @@ -66,11 +66,6 @@ var RichText = module.exports = kind( */ kind: Input, - /** - * @private - */ - mixins: options.accessibility ? [RichTextAccessibilitySupport] : null, - /** * @private */ @@ -343,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/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 90% rename from lib/Router.js rename to src/Router.js index b427671c9..3b4bd0a9f 100644 --- a/lib/Router.js +++ b/src/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); } }); 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 97% rename from lib/Scrim/Scrim.js rename to src/Scrim/Scrim.js index 2bd49a28d..0768fb32b 100644 --- a/lib/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/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 99% rename from lib/ScrollMath.js rename to src/ScrollMath.js index 8656bbfd2..ca759576b 100644 --- a/lib/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/lib/ScrollStrategy.js b/src/ScrollStrategy.js similarity index 98% rename from lib/ScrollStrategy.js rename to src/ScrollStrategy.js index 487893db6..34e5b50c1 100644 --- a/lib/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/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 98% rename from lib/ScrollThumb/ScrollThumb.js rename to src/ScrollThumb/ScrollThumb.js index 0aefe13a9..27908d28d 100644 --- a/lib/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/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 82% rename from lib/Scrollable/Scrollable.js rename to src/Scrollable/Scrollable.js index dc7555165..c2eb8055e 100644 --- a/lib/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'); @@ -83,7 +84,7 @@ module.exports = { * * @type {Number} * @default null - * @memberof enyo.Scroller.prototype + * @memberof module:enyo/Scroller~Scroller.prototype * @public */ maxHeight: null, @@ -103,8 +104,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 +117,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' @@ -222,6 +223,7 @@ module.exports = { defProps = this.defaultProps; this.defaultProps = {}; + this.accessibilityPreventScroll = true; this.createComponents(smc, {isChrome: true, owner: this}); if (this.scrollControls) { @@ -308,8 +310,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 */ @@ -320,37 +322,64 @@ 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 + * 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; + } + + offsets.top = offsets.top - viewportBounds.top; + offsets.left = (this.rtl ? offsets.right : offsets.left) - (this.rtl ? viewportBounds.right : viewportBounds.left); - if (n) { - this.scrollToNode(n, opts); + offsets.getMargin = function (side) { + return dom.getComputedBoxValue(node, 'margin', side); + }; + + return offsets; + }; } - }, + else { + return sup; + } + }), /** - * 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 + * 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, @@ -366,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. @@ -400,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'; } } @@ -418,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; } @@ -448,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); }, /** @@ -984,7 +1025,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) { @@ -1004,7 +1045,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/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 96% rename from lib/Scroller/Scroller.js rename to src/Scroller/Scroller.js index 430569b3c..42f99c8f4 100644 --- a/lib/Scroller/Scroller.js +++ b/src/Scroller/Scroller.js @@ -9,7 +9,6 @@ require('enyo'); var kind = require('../kind'), - utils = require('../utils'), platform = require('../platform'); var @@ -21,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). @@ -42,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. */ @@ -51,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. */ /** @@ -62,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 */ @@ -96,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 @@ -167,7 +166,7 @@ var Scroller = module.exports = kind( * * @type {Number} * @default null - * @memberof enyo.Scroller.prototype + * @memberof enyo/Scroller~Scroller.prototype * @public */ maxHeight: null, @@ -239,7 +238,7 @@ var Scroller = module.exports = kind( * @name touchScrolling * @type {Boolean} * @default undefined - * @memberof enyo.Scroller.prototype + * @memberof enyo/Scroller~Scroller.prototype * @public */ @@ -274,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 */ @@ -563,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 */ 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 91% rename from lib/Select.js rename to src/Select.js index 875dc426f..cdfd106b1 100644 --- a/lib/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/lib/Selection.js b/src/Selection.js similarity index 88% rename from lib/Selection.js rename to src/Selection.js index d2bb9aab9..0f3dfbf86 100644 --- a/lib/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/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 97% rename from lib/SpriteAnimation/SpriteAnimation.js rename to src/SpriteAnimation/SpriteAnimation.js index e3ea6b446..82fea364d 100644 --- a/lib/SpriteAnimation/SpriteAnimation.js +++ b/src/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'} ], /** @@ -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/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 84% rename from lib/States.js rename to src/States.js index 672b1b0fa..3e72e5d18 100644 --- a/lib/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 diff --git a/lib/Store.js b/src/Store.js similarity index 99% rename from lib/Store.js rename to src/Store.js index a0c8dc226..a0cd310a4 100644 --- a/lib/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 */ 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 92% rename from lib/StylesheetSupport.js rename to src/StylesheetSupport.js index 982a3339a..7370fe900 100644 --- a/lib/StylesheetSupport.js +++ b/src/StylesheetSupport.js @@ -1,3 +1,8 @@ +/** +* Exports the {@link module:enyo/StylesheetSupport} mixin. +* @module enyo/StylesheetSupport +*/ + require('enyo'); var @@ -7,12 +12,12 @@ 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). * -* @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/lib/SystemMonitor.js b/src/SystemMonitor.js similarity index 83% rename from lib/SystemMonitor.js rename to src/SystemMonitor.js index 7324621dd..7e5564f09 100644 --- a/lib/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; diff --git a/lib/Table/Table.js b/src/Table/Table.js similarity index 76% rename from lib/Table/Table.js rename to src/Table/Table.js index c1d5c8abd..5770be3ff 100644 --- a/lib/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/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 66% rename from lib/TableCell/TableCell.js rename to src/TableCell/TableCell.js index 85153ea60..fce02cf73 100644 --- a/lib/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/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 67% rename from lib/TableRow/TableRow.js rename to src/TableRow/TableRow.js index e73912df3..42f72d6b5 100644 --- a/lib/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/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 87% rename from lib/TaskManagerSupport.js rename to src/TaskManagerSupport.js index ef1e2e55f..f7c8516db 100644 --- a/lib/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 */ diff --git a/lib/TextArea/TextArea.js b/src/TextArea/TextArea.js similarity index 82% rename from lib/TextArea/TextArea.js rename to src/TextArea/TextArea.js index ebe96e260..22400ee3d 100644 --- a/lib/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/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 98% rename from lib/TouchScrollStrategy.js rename to src/TouchScrollStrategy.js index cf20c8bd3..42a2d5eb2 100644 --- a/lib/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. @@ -33,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 @@ -805,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/lib/TransitionScrollStrategy.js b/src/TransitionScrollStrategy.js similarity index 99% rename from lib/TransitionScrollStrategy.js rename to src/TransitionScrollStrategy.js index d2aa76be4..0c05cc2a8 100644 --- a/lib/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/lib/TranslateScrollStrategy.js b/src/TranslateScrollStrategy.js similarity index 93% rename from lib/TranslateScrollStrategy.js rename to src/TranslateScrollStrategy.js index e8fb449c8..787898f6d 100644 --- a/lib/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 @@ -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(); + } }, /** diff --git a/lib/UiComponent.js b/src/UiComponent.js similarity index 96% rename from lib/UiComponent.js rename to src/UiComponent.js index 1a393b47c..011ee7138 100644 --- a/lib/UiComponent.js +++ b/src/UiComponent.js @@ -8,7 +8,9 @@ require('enyo'); var kind = require('./kind'), utils = require('./utils'), - master = require('./master'); + master = require('./master'), + AnimationSupport = require('./AnimationSupport/AnimationSupport'), + AnimationInterfaceSupport = require('./AnimationSupport/AnimationInterfaceSupport'); var Component = require('./Component'); @@ -18,7 +20,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 @@ -128,6 +130,12 @@ var UiComponent = module.exports = kind( 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 @@ -144,13 +152,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 +632,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 +678,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); } }, @@ -701,5 +696,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]; + } + } } }); diff --git a/lib/VerticalDelegate.js b/src/VerticalDelegate.js old mode 100755 new mode 100644 similarity index 98% rename from lib/VerticalDelegate.js rename to src/VerticalDelegate.js index d10cdf36c..d50fd09ce --- a/lib/VerticalDelegate.js +++ b/src/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 + } } }, @@ -333,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 @@ -348,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/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 93% rename from lib/Video.js rename to src/Video.js index 4ebbea0ca..6e9288156 100644 --- a/lib/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()`. @@ -263,7 +267,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 +275,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'] } }, @@ -499,7 +503,8 @@ module.exports = kind( * @public */ fastForward: function () { - var node = this.hasNode(); + var node = this.hasNode(), + isNeedPlay = false; if (!node) { return; @@ -507,32 +512,30 @@ 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': this.selectPlaybackRateArray('slowForward'); this._speedIndex = 0; if (this.isPaused()) { - node.play(); + isNeedPlay = true; } 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': @@ -548,6 +551,7 @@ module.exports = kind( this.setPlaybackRate(this.selectPlaybackRate(this._speedIndex)); + isNeedPlay && node.play(); }, /** @@ -556,8 +560,8 @@ module.exports = kind( * @public */ rewind: function () { - var node = this.hasNode(); - + var node = this.hasNode(), + isNeedPlay = false; if (!node) { return; } @@ -570,14 +574,23 @@ 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; if (this.isPaused() && this.node.duration > this.node.currentTime) { - node.play(); + isNeedPlay = true; } this._prevCommand = 'slowRewind'; break; @@ -594,6 +607,8 @@ module.exports = kind( this.setPlaybackRate(this.selectPlaybackRate(this._speedIndex)); + + isNeedPlay && node.play(); }, /** @@ -603,16 +618,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})); }, @@ -623,13 +645,21 @@ 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); + + if(oldPlaybackRateNumber < 0) { + this.node.play(); // Play before skip so video won't restart + } + node.currentTime += parseInt(this.jumpSec, 10); this._prevCommand = 'jumpForward'; @@ -719,8 +749,7 @@ module.exports = kind( */ setPlaybackRate: function (rate) { var node = this.hasNode(), - pbNumber - ; + pbNumber; if (!node) { return; diff --git a/lib/ViewController.js b/src/ViewController.js similarity index 98% rename from lib/ViewController.js rename to src/ViewController.js index 7d0556954..d079b5fdb 100644 --- a/lib/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 diff --git a/lib/ViewPreloadSupport.js b/src/ViewPreloadSupport.js similarity index 88% rename from lib/ViewPreloadSupport.js rename to src/ViewPreloadSupport.js index 34f55c3db..4e0d0059f 100644 --- a/lib/ViewPreloadSupport.js +++ b/src/ViewPreloadSupport.js @@ -3,7 +3,9 @@ * @module enyo/ViewPreloadSupport */ var - kind = require('./kind'); + dom = require('./dom'), + kind = require('./kind'), + utils = require('./utils'); var Control = require('./Control'); @@ -22,19 +24,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,10 +40,19 @@ 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 + var cp = this.controlParent; + this.controlParent = null; this.createChrome([ {name: 'viewCache', kind: Control, canGenerate: false} ]); + this.controlParent = cp; + this.removeChild(this.$.viewCache); this._cachedViews = {}; } @@ -107,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.node) { - view.node.remove(); + if (node) { view.teardownRender(true); + dom.removeNode(node); } if (!preserve) { diff --git a/lib/VirtualDataRepeater.js b/src/VirtualDataRepeater.js similarity index 51% rename from lib/VirtualDataRepeater.js rename to src/VirtualDataRepeater.js index de4c61353..1c631602c 100644 --- a/lib/VirtualDataRepeater.js +++ b/src/VirtualDataRepeater.js @@ -25,15 +25,31 @@ module.exports = kind({ // reorderNodes: false, reset: function () { - this.init(); - this.destroyClientControls(); - this.setExtent(); + // If we are showing, go ahead and reset... + if (this.getAbsoluteShowing()) { + this.init(); + this.destroyClientControls(); + this.setExtent(); + if (this._needsReset) { + // Now that we've done our deferred reset, we + // can become visible again + this.applyStyle('visibility', 'visible'); + 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; + } }, init: function () { this.orderedChildren = [], this.orderedChildren_alt = [], - this.childrenByIndex = {}, + this.childrenByIndex = [], this.orderedModels = [], this.orderedModels_alt = [], this.needed = [], @@ -64,26 +80,79 @@ module.exports = kind({ this.notify('numItems', pn, this.numItems); } }, - + refresh: function (immediate) { + // If we haven't initialized, we need to reset instead if (!this.hasInitialized) return this.reset(); - this.stabilizeExtent(); + // 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; + } - var refresh = this.bindSafely(function() { - this.doIt(); + // 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; + } + } + // 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) { + if (immediate === true) { refresh(); - } else { + } + else { + // 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); } }, + /** + * @private + */ + 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(); + } + else if (this._needsRefresh) { + this.refresh(); + } + return sup.apply(this, arguments); + }; + }), + childForIndex: function(idx) { return this.childrenByIndex[idx]; }, @@ -97,6 +166,7 @@ module.exports = kind({ var pMod = child.model, pIdx = child.index, sc = child.selectedClass || 'selected', + cbi = this.childrenByIndex, s; if (pMod !== model) { @@ -108,12 +178,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(); @@ -125,17 +193,27 @@ 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; + 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 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); @@ -144,6 +222,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); @@ -153,38 +233,53 @@ 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; + // 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(); } }, @@ -192,7 +287,7 @@ module.exports = kind({ fwd: function() { this.set('first', this.first + 1); }, - + bak: function() { this.set('first', this.first - 1); }, diff --git a/lib/WebService.js b/src/WebService.js similarity index 98% rename from lib/WebService.js rename to src/WebService.js index 5813eb8a6..5a3677c21 100644 --- a/lib/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 diff --git a/lib/XhrSource.js b/src/XhrSource.js similarity index 98% rename from lib/XhrSource.js rename to src/XhrSource.js index 9c554a81e..283fdf4d7 100644 --- a/lib/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 diff --git a/lib/animation.js b/src/animation.js similarity index 93% rename from lib/animation.js rename to src/animation.js index 39a41f663..2aa09be80 100644 --- a/lib/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/lib/cookie.js b/src/cookie.js similarity index 93% rename from lib/cookie.js rename to src/cookie.js index e99cf4575..d9e50f1f8 100644 --- a/lib/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/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 95% rename from lib/dom.js rename to src/dom.js index 63e5ffc4e..baf213a66 100644 --- a/lib/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 @@ -407,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`. * @@ -428,7 +442,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; }, @@ -440,7 +454,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); } @@ -454,7 +468,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); } @@ -522,13 +536,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 +787,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/lib/gesture/drag.js b/src/gesture/drag.js similarity index 99% rename from lib/gesture/drag.js rename to src/gesture/drag.js index 1b1c549a7..8de40002d 100644 --- a/lib/gesture/drag.js +++ b/src/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/gesture.js b/src/gesture/gesture.js similarity index 99% rename from lib/gesture/gesture.js rename to src/gesture/gesture.js index 86d4c5411..4eaf02179 100644 --- a/lib/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/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 98% rename from lib/gesture/touchGestures.js rename to src/gesture/touchGestures.js index 91be53901..eed1a4cb0 100644 --- a/lib/gesture/touchGestures.js +++ b/src/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 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 77% rename from lib/i18n.js rename to src/i18n.js index 1cf8c5b6b..861ef5c9d 100644 --- a/lib/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 */ @@ -11,15 +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. -* -* `$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. +* 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'); +* ``` +* +* 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}. @@ -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/lib/job.js b/src/job.js similarity index 89% rename from lib/job.js rename to src/job.js index 2072743c8..6ccfda557 100644 --- a/lib/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/lib/jobs.js b/src/jobs.js similarity index 98% rename from lib/jobs.js rename to src/jobs.js index e4db5e1f8..5582a6f0f 100644 --- a/lib/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/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 91% rename from lib/kind.js rename to src/kind.js index fd56c198c..a099198fd 100644 --- a/lib/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 @@ -308,6 +312,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); @@ -387,6 +394,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); } }; diff --git a/lib/logger.js b/src/logger.js similarity index 91% rename from lib/logger.js rename to src/logger.js index db3f524dc..83440115f 100644 --- a/lib/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/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 94% rename from lib/pageVisibility.js rename to src/pageVisibility.js index c13189276..c493e7ff9 100644 --- a/lib/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/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 63% rename from lib/resolution.js rename to src/resolution.js index 2c82e4a87..883bb97bd 100644 --- a/lib/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 hash provided * @public */ selectSrc: function (src) { 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 99% rename from lib/utils.js rename to src/utils.js index dd3f80df3..c4204fc9d 100644 --- a/lib/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 */ @@ -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 diff --git a/lib/xhr.js b/src/xhr.js similarity index 100% rename from lib/xhr.js rename to src/xhr.js 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..9fd31c629 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 () { @@ -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 () { 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(); + }; + }); + }); +}); 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/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 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/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); + }); + }); + }); +}); 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 () { 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 000000000..bfc6f7f66 Binary files /dev/null and b/tests/Tween/reports/Tween_module_getcoeff_test_report_after.pdf differ 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 000000000..decd80475 Binary files /dev/null and b/tests/Tween/reports/Tween_module_getcoeff_test_report_before.pdf differ diff --git a/tests/build/enyo.css b/tests/build/enyo.css new file mode 100644 index 000000000..d011dfac9 --- /dev/null +++ b/tests/build/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/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 `
` 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
%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