diff --git a/.travis.yml b/.travis.yml index 065e17d82..855feb1f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ -language: node_js sudo: false +language: node_js node_js: - - "0.12" + - "node" cache: directories: - $HOME/.npm \ No newline at end of file diff --git a/docs/external.jsdoc b/docs/external.jsdoc index 32bfdb9cb..38d0f28f8 100644 --- a/docs/external.jsdoc +++ b/docs/external.jsdoc @@ -153,11 +153,27 @@ * @glossary computed_property */ +/** +* Methods and properties marked as private should not be used by developers. They may be changed or +* removed without notice and their particular implementations should not be relied upon. Kinds and +* modules marked as private are inteded for use only by the framework. +* +* @glossary private +*/ + +/** +* Methods and properties marked as protected should only be used when subclassing a kind. They +* represent the internal workings of a kind and should not be used by outside kinds. Protected kinds +* and modules are not intended to be instantiated directly by developers. +* +* @glossary protected +*/ + ///// Internally Documented Links (Glossary) ///// /** * Binary (bitwise) operations. -* +* * @glossary bitwise * @see http://www.experts-exchange.com/Programming/Misc/A_1842-Binary-Bit-Flags-Tutorial-and-Usage-Tips.html * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators @@ -166,7 +182,7 @@ /** * Strict Equality. This comparison logic ensures not only that the value of the things being * compared is the same, but also their types. -* +* * @glossary === * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Using_the_Equality_Operators */ @@ -176,25 +192,26 @@ * that don't support a native version of said method, in an effort to bring the * browser up to the standards of modern browsers. This allows code that expects * a modern browser to work properly in an older one. -* +* * @glossary polyfill * @see http://en.wikipedia.org/wiki/Polyfill */ /** * The Document Object Model (DOM) is a programming interface for HTML and XML documents. +* * @glossary DOM * @see https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model */ +///// Externally Documented Links ///// + /** * Inheritance * @glossary inheritance * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain */ -///// Externally Documented Links ///// - /** * Universally Unique Identifier * @glossary UUID diff --git a/index.js b/index.js index ca5e1a50e..b4dc77b26 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ 'use strict'; exports = module.exports = require('./src/options'); -exports.version = '2.6.0-rc.1'; +exports.version = '2.7.0'; diff --git a/package.json b/package.json index a899b0142..b34c69f1c 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "main": "index.js", "moduleDir": "src", "filename": "enyo.js", - "version": "2.6.0-pre", + "version": "2.7.0", "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/", @@ -26,12 +26,7 @@ "web": "http://enyojs.com/" } ], - "licenses": [ - { - "type": "Apache-2.0", - "url": "http://www.apache.org/licenses/LICENSE-2.0" - } - ], + "license": "Apache-2.0", "scripts": { "test": "./node_modules/.bin/gulp" }, @@ -40,17 +35,17 @@ "url": "http://github.com/enyojs/enyo" }, "devDependencies": { - "chai": "^3.2.0", - "enyo-dev": "^0.5.1", + "chai": "^3.5.0", + "enyo-dev": "^0.5.2", "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", + "gulp-jshint": "^2.0.0", + "gulp-mocha-phantomjs": "^0.10.1", + "jshint": "^2.9.1", + "jshint-stylish": "^2.1.0", + "mocha": "^2.4.4", + "sinon": "^1.17.3", "sinon-chai": "^2.8.0", - "through2": "^0.6.5" + "through2": "^2.0.0" } -} +} \ No newline at end of file diff --git a/src/Ajax.js b/src/Ajax.js index c3e282f41..c2c193d30 100644 --- a/src/Ajax.js +++ b/src/Ajax.js @@ -184,7 +184,7 @@ var Ajax = module.exports = kind( } utils.mixin(xhr_headers, this.headers); // don't pass in headers structure if there are no headers defined as this messes - // up CORS code for IE8-9 + // up CORS code for IE9 if (utils.keys(xhr_headers).length === 0) { xhr_headers = undefined; } @@ -278,7 +278,7 @@ var Ajax = module.exports = kind( } } else { var text = ''; - // work around IE8-9 bug where accessing responseText will thrown error + // work around IE9 bug where accessing responseText will thrown error // for binary requests. if (typeof inXhr.responseText === 'string') { text = inXhr.responseText; @@ -337,25 +337,27 @@ var Ajax = module.exports = kind( * @private */ updateProgress: function (event) { - // IE8 doesn't properly support progress events and doesn't pass an object to the - // handlers so we'll check that before continuing. - if (event) { - // filter out 'input' as it causes exceptions on some Firefox versions - // due to unimplemented internal APIs - var ev = {}; - for (var k in event) { - if (k !== 'input') { - ev[k] = event[k]; - } + // filter out 'input' as it causes exceptions on some Firefox versions + // due to unimplemented internal APIs + var ev = {}; + for (var k in event) { + if (k !== 'input') { + ev[k] = event[k]; } - this.sendProgress(event.loaded, 0, event.total, ev); } + this.sendProgress(event.loaded, 0, event.total, ev); }, /** * @private */ - statics: { + statics: /** @lends module:enyo/Ajax~Ajax */ { + /** + * Takes an object and converts it to an encoded URI string. NOTE: It does not traverse into + * objects or arrays, so nested objects will be rendered as the string 'object Object', which + * is not terribly useful. + * @public + */ objectToQuery: function (/*Object*/ map) { var enc = encodeURIComponent; var pairs = []; diff --git a/src/AjaxSource.js b/src/AjaxSource.js index 5b2bc00fd..8bac1d4f8 100644 --- a/src/AjaxSource.js +++ b/src/AjaxSource.js @@ -11,6 +11,7 @@ var var XhrSource = require('./XhrSource'), Ajax = require('./Ajax'), + States = require('./States'), AjaxProperties = require('./AjaxProperties'); /** @@ -73,7 +74,7 @@ module.exports = kind( * @public */ commit: function (model, opts) { - opts.method = model.isNew? 'POST': 'PUT'; + opts.method = (model.status & States.NEW) ? 'POST': 'PUT'; opts.url = this.buildUrl(model, opts); opts.postBody = opts.postBody || model.toJSON(); this.go(opts); diff --git a/src/AnimationSupport/AnimationSupport.js b/src/AnimationSupport/AnimationSupport.js new file mode 100644 index 000000000..b923fd991 --- /dev/null +++ b/src/AnimationSupport/AnimationSupport.js @@ -0,0 +1,308 @@ +require('enyo'); + +var + kind = require('../kind'), + scene = require('../scene'), + utils = require('../utils'); + +var extend = kind.statics.extend; + +kind.concatenated.push('scene'); + +var AnimationSupport = { + + id: utils.uid("@"), + + handleLayers: false, + + /** + * An exposed property to know if know the animating state of this scene. + * 'true' - the scene is asked for animation(doesn't mean animation is happening) + * 'false' - the scene is not active(has completed or its actors are not visible) + * @type {Boolean} + * @memberOf module:enyo/AnimationSupport/Actor + * @public + */ + animating: false, + + /** + * An exposed property to know if the scene is ready with actors performing action. + * 'true' - the scene actors are ready for action + * 'false' - some or all actors are not ready + * @type {Boolean} + * @memberOf module:enyo/AnimationSupport/Actor + * @public + */ + active: this && this.generated, + + + /** + * @private + */ + timeline: 0, + /** + * @private + */ + _cachedValue: 0, + /** + * @private + */ + speed: 0, + /** + * @private + */ + seekInterval: 0, + /** + * @private + */ + repeat: false, + /** + * @private + */ + cache: function(actor) { + actor = actor || this; + if(actor.speed === 0){ + actor.speed = actor._cachedValue; + } + this.animating = true; + }, + + /** + * Starts the animation of the actor given in argument. + * If actor is not provided, animation of all the components linked to the {@link module:enyo/AnimationSupport/Scene} will be started. + * @param [Component]{@link module:enyo/Component~Component} actor The component to be animated + * @public + */ + play: function (actor) { + actor = actor || this; + actor.speed = 1; + if (isNaN(actor.timeline) || !actor.timeline) { + actor.timeline = 0; + } + this.trigger(); + actor._cachedValue = actor.speed; + this.animating = true; + }, + + /** + * Replays the animation of the actor given in argument. + * If actor is not provided, animation of all the components linked to the {@link module:enyo/AnimationSupport/Scene} will be started. + * @param [Component]{@link module:enyo/Component~Component} actor The component to be animated + * @public + */ + replay: function (actor) { + this.stop(); + this.play(); + }, + /** + * Resumes the paused animation of the actor given in argument. + * If actor is not provided, animation of all the components linked to the {@link module:enyo/AnimationSupport/Scene} will be resumed. + * @param [Component]{@link module:enyo/Component~Component} actor The component to be animated + * @public + */ + resume: function(actor) { + this.cache(actor); + actor = actor || this; + actor.speed *= 1; + }, + + /** + * Pauses the animation of the actor given in argument. + * If actor is not provided, animation of all the components linked to the {@link module:enyo/AnimationSupport/Scene} will be paused. + * @param [Component]{@link module:enyo/Component~Component} actor The component to be animated + * @public + */ + pause: function (actor) { + actor = actor || this; + actor._cachedValue = actor.speed; + actor.speed = 0; + }, + + /** + * Reverses the animation of the actor given in argument. + * If actor is not provided, animation of all the components linked to the {@link module:enyo/AnimationSupport/Scene} will be reversed. + * @param [Component]{@link module:enyo/Component~Component} actor The component to be animated + * @public + */ + reverse: function (actor) { + this.cache(actor); + actor = actor || this; + actor._cachedValue = actor.speed; + actor.speed *= -1; + }, + + /** + * fast description goes here + * @param {Number} mul description goes here + * @param [Component]{@link module:enyo/Component~Component} actor description goes here + * @public + */ + fast: function (mul, actor) { + this.cache(actor); + actor = actor || this; + actor.speed *= mul; + }, + + /** + * slow description goes here + * @param {Number} mul description goes here + * @param [Component]{@link module:enyo/Component~Component} actor description goes here + * @public + */ + slow: function (mul, actor) { + this.cache(actor); + actor = actor || this; + actor.speed *= mul; + }, + + /** + * Changes the speed of the animation.
+ * Speed of the animation changed based on the factor.
+ * To slow down the speed use values between 0 and 1. For Example 0.5 to reduce the speed by 50%.
+ * To increase the speed use values above 1. For Example 2 to increase the speed by 200%.
+ * Animation will be paused if factor is 0. To pause the animation use {@link enyo/AnimationSupport/Editor.pause pause} API.
+ * Speed will not be affected incase of negative multiplication factor. + * @param {Number} factor Multiplication factor which changes the speed + * @param [Component {@link module:enyo/Component~Component}] actor The component whose animating speed should be changed + * @public + */ + // speed: function(mul, actor) { + // if (mul < 0) return; + // this.cache(actor); + // actor = actor || this; + // actor.speed *= mul; + // }, + + /** + * Stops the animation of the actor given in argument. + * If actor is not provided, animation of all the components linked to the {@link module:enyo/AnimationSupport/Scene} will be stopped. + * @param [Component]{@link module:enyo/Component~Component} actor The component to be animated + * @public + */ + stop: function (actor) { + actor = actor || this; + actor._cachedValue = 1; + actor.speed = 0; + actor.timeline = 0; + // this.animating = false; + // this.cancel(); + }, + + /** + * Seeks the animation of the actor to the position provided in seek + * The value of seek should be between 0 to duration of the animation. + * @param {Number} seek Value in seek where the animation has to be seeked + * @param [Component]{@link module:enyo/Component~Component} actor The component to be animated + * @public + */ + seek: function(seek, actor) { + actor = actor || this; + actor.timeline = seek; + }, + + /** + * Seeks actor with animation to the position provided in seek + * The value of seek should be between 0 to duration of the animation. + * @param {Number} seek Value in seek where the animation has to be seeked + * @param [Component]{@link module:enyo/Component~Component} actor The component to be animated + * @public + */ + seekAnimate: function(seek, actor) { + actor = actor || this; + if (seek >= 0 ) { + if (!this.animating) + this.play(actor); + actor.speed = 1; + }else{ + actor.speed = -1; + } + actor.seekInterval = actor.timeline + seek; + if (actor.seekInterval < 0) { + actor.speed = 0; + actor.seekInterval = 0; + } + }, + + //TODO: Move these events to Event Delegator + /** + * Event to identify when the scene has done animating. + * @memberOf module:enyo/AnimationSupport/Actor + * @public + */ + completed: function() {}, + + /** + * Event to identify when the scene has done a step(rAF updatation of time) in the animation. + * @memberOf module:enyo/AnimationSupport/Actor + * @public + */ + step: function() {} +}; + +module.exports = AnimationSupport; + +/** + * Adds animation from a scene to other scene + * @memberOf module:enyo/AnimationSupport/Actor + * @public + * @return {Object} An instance of the constructor + */ +function sceneToScene (src) { + if (!src) return; + if (!src.id) extend(AnimationSupport, src); + + var i, l = src.length(), + anim; + + for (i = 0; i < l; i++) { + anim = utils.mixin({}, src.getAnimation(i)); + this.addAnimation(anim.animate, anim.animate.duration); + } + + var span = src.span + this.span; + src.rolePlays.push({ + actor: this, + span: span, + dur: this.span + }); + return this; +} + +/** + * Add animations within a scene + * @memberOf module:enyo/AnimationSupport/Actor + * @public + * @return {Object} An instance of the constructor + */ +function updateScene (props) { + if (!props) return; + + var anims = utils.isArray(props) ? props : [props]; + for (var i = 0; i < anims.length; i++) { + this.addAnimation(anims[i], anims[i].duration || 0); + } + return this; +} + + + +/** + 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.scene) { + var fn, + proto = ctor.prototype || ctor, + sctor = scene.create(proto); + + extend(AnimationSupport, sctor); + fn = props.scene.isScene ? sceneToScene : updateScene; + proto.scene = fn.call(sctor, props.scene); + } +}; \ No newline at end of file diff --git a/src/AnimationSupport/Director.js b/src/AnimationSupport/Director.js deleted file mode 100644 index 719199534..000000000 --- a/src/AnimationSupport/Director.js +++ /dev/null @@ -1,169 +0,0 @@ -require('enyo'); - -var tween = require('./Tween'), - utils = require('../utils'); - -var pose, dur, tm, t; - -/** -* Contains the declaration for the {@link module:enyo/AnimationSupport/Director} module. -* This modules exposes the features to support 'Director' approach. -* @module enyo/AnimationSupport/Director -*/ -module.exports = { - - /** - * This method checks if all the actors of the given {@link @module enyo/AnimationSupport/Scene} object - * is rendered. If the actors are rendered then all them are initialized and prepared to be ready for animation. - * @param {@link @module enyo/AnimationSupport/Scene} scene Scene which contains actors to be prepared for animation. - * @return {boolean} Returns true if all the actors - * of the scene is active and ready for action, - * otherwise false - */ - roll: function (scene) { - var actor, - actors = scene.rolePlays ? scene.rolePlays[scene.getID()]: [], - l = actors ? actors.length: 0, - active = true; - - for (var i = 0; i < l; i++) { - actor = actors[i]; - if(actor.generated) { - tween.init(actor); - active = false; - } - } - scene.active = active; - }, - - /** - * take method is invloved in time based animation. This method will - * be executed continuously in order tween the actor for every frame until the animation - * is completed (i.e. until elapsed time is equal to the duration). - * be animated based on the delta and the acceleration. - * @param {@link @module enyo/AnimationSupport/Scene} scene Scene on which the animation will be performed - * @param {Number} ts Elapsed time since the animation of this pose has started (ratio in factor of 1) - */ - take: function (scene, ts) { - dur = scene.span; - tm = scene.timeline; - - if (isNaN(tm) || tm < 0) return; - if (tm <= dur) { - pose = scene.action(ts, pose); - } else { - scene.timeline = dur; - scene.animating = false; - } - }, - - /** - * action is the primary method which triggers the animation of the actor for every frame. - * This method calculates the start and end animation positions and the elapsed time since the animation - * has started and tweens the actor based on the these values. - * @param {Object} pose Animation poses - * @param {@link module:enyo/Component~Component} actor Component on which the animation should be performed - * @param {Number} since Elapsed time since the animation of this pose has started - * @param {Number} dur Total duration of this pose - */ - action: function (pose, actor, since, dur) { - if (!pose._startAnim) tween.init(actor, pose); - - if (since < 0) since = 0; - if (since <= dur && dur !== 0) { - t = since / dur; - tween.step(actor, pose, ( t > 0.98) ? 1 : t, dur); - } else { - tween.step(actor, pose, 1, dur); - } - }, - - /** - * Casts an actor or all the actors in the array to the given scene. - * @param {@link module:enyo/Component~Component} actors actor or Array of actors which needs to be casted in the scene. - * @param {@link @module enyo/AnimationSupport/Scene} scene Scene to which the actors has to be connected. - */ - cast: function (actors, scene) { - var acts = utils.isArray(actors) ? actors : [actors], - id = scene.getID(), - rolePlays = scene.rolePlays || {}; - - if (!rolePlays[id]) { - rolePlays[id] = acts; - } else { - rolePlays[id] = acts.reduce(function(actors, actor) { - actors.push( actor ); - return actors; - }, rolePlays[id]); - } - scene.rolePlays = rolePlays; - }, - - /** - * Disconnects actor or Array of actors from the scene - * @param {Array.} actors actor or Array of actors which needs to be casted in the scene. - * @param {@link @module enyo/AnimationSupport/Scene} scene Scene from which the actors has to be removed. - */ - reject: function (scene, actors) { - var id = scene.getID(), acts, - rolePlays = scene.rolePlays || []; - actors = actors || rolePlays[id]; - acts = utils.isArray(actors) ? actors : [actors]; - if (rolePlays[id]) { - rolePlays[id] = acts.reduce(function(actors, actor) { - var i = actors.indexOf(actor); - if (i >= 0) actors.splice(i, 1); - return actors; - }, rolePlays[id]); - } - scene.rolePlays = rolePlays; - }, - - /** - * shot method is invloved in distance based animation in which the distance definite and - * indefinite (Event based animations). This method calculates the distance to which the actor has to - * be animated based on the delta and the acceleration. - * @param {@link module:enyo/Component~Component} actor Component on which the animation should be performed - * @param {Number} ts delta distance - * @return {Number} The distance to which the actor has to be transformed - */ - shot: function(actor, ts) { - var v1, s, a, v = 0, - t = ts, - dt = actor.getAnimationDelta(), - dir = this.angle(actor.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; - } - return dt[dir] > 0 ? v : -v; - }, - - /** - * @private - */ - 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 deleted file mode 100644 index 8ec19ab99..000000000 --- a/src/AnimationSupport/Easings.js +++ /dev/null @@ -1,635 +0,0 @@ -/** - * Beginning time - * @type {number} - */ -var b = 0, - /** - * Change in time - * @type {number} - */ - c = 1, - temp = null, - tempProp = null, - tempOldState = [], - tempNewState = []; -/** - * This module provdes an interface to achieve various types of Easings in animations - * - * @module enyo/AnimationSupport/Easings - */ -var easings = module.exports = { - - /** - * Use this function to check whether the ease object has changed or not. - * @public - * @param {object} currentEase - ease object which we want to check - * @return {boolean} - Boolean value for easechanged. - * True - Yes. The ease object got changed. - * False - No. The ease object has not changed. - */ - 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; - } - - } - }, - - /** - * Use this function to check whether the animating property of the object has changed or not - * @public - * @param {string} currentProp - Name of the animating property like - "Translate/Opacity/Scale/Rotate" - * @return {boolean} - Boolean value for propChange. Either True or False - */ - 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; - } - - } - }, - - /** - * Use this function to check whether the oldState of the object has changed or not - * @public - * @param {object} currentOldState - currentOldState object - * @return {boolean} - Boolean value for oldStateChange. Either True or False - */ - 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; - } - - } - - }, - - /** - * Use this function to check whether the newStateChange of the object has changed or not - * @public - * @param {object} currentNewState -currentNewState object - * @return {boolean} - Boolean value for newStateChange. Either True or False - */ - 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; - } - - } - - }, - - /** - * Use this function to compare the states which are arrays - * @public - * @param {Number[]} x - old array of the same - * @param {Number[]} y - current array of the same - * @return {boolean} - True/ False after comparing the parameters - */ - 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; - }, - - /** - * This function returns the coefficents based on the order and the current position - * @private - * @param {number} n - order - * @param {number} k - current position - * @return {object} - coefficients - */ - 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; - }, - - /** - * Function to get the bezier coeffients based on the time and order - * @public - * @param {number} t - time - * @param {number} n - order - * @return {object} - bezier coefficients - */ - 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; - }, - - /** - * Current time multiplied with duration - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} - time - */ - timeCheck: function(t, d) { - t = t * d; - return t; - }, - - /** - * EaseInQuad - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} - calculated time - */ - easeInQuad: function(t, d) { - t = easings.timeCheck(t, d); - return c * (t /= d) * t + b; - }, - - /** - * EaseOutQuad - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - easeOutQuad: function(t, d) { - t = easings.timeCheck(t, d); - return -c * (t /= d) * (t - 2) + b; - }, - - /** - * EaseInOutQuad - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - 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; - }, - - /** - * EaseInCubic - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - easeInCubic: function(t, d) { - t = easings.timeCheck(t, d); - return c * (t /= d) * t * t + b; - }, - - /** - * EaseOutCubic - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - easeOutCubic: function(t, d) { - t = easings.timeCheck(t, d); - return c * ((t = t / d - 1) * t * t + 1) + b; - }, - - /** - * EaseInOutCubic - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - 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; - }, - - /** - * EaseInQuart - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - easeInQuart: function(t, d) { - t = easings.timeCheck(t, d); - return c * (t /= d) * t * t * t + b; - }, - - /** - * EaseOutQuart - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - easeOutQuart: function(t, d) { - t = easings.timeCheck(t, d); - return -c * ((t = t / d - 1) * t * t * t - 1) + b; - }, - - /** - * EaseInOutQuart - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - 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; - }, - - /** - * EaseInQuint - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - easeInQuint: function(t, d) { - t = easings.timeCheck(t, d); - return c * (t /= d) * t * t * t * t + b; - }, - /** - * EaseOutQuint - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - easeOutQuint: function(t, d) { - t = easings.timeCheck(t, d); - return c * ((t = t / d - 1) * t * t * t * t + 1) + b; - }, - - /** - * EaseInOutQuint - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - 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; - }, - - /** - * EaseInSine - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - easeInSine: function(t, d) { - t = easings.timeCheck(t, d); - return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; - }, - - /** - * EaseOutSine - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - easeOutSine: function(t, d) { - t = easings.timeCheck(t, d); - return c * Math.sin(t / d * (Math.PI / 2)) + b; - }, - - /** - * EaseInOutSine - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - easeInOutSine: function(t, d) { - t = easings.timeCheck(t, d); - return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; - }, - - /** - * EaseInExpo - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - easeInExpo: function(t, d) { - t = easings.timeCheck(t, d); - return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b; - }, - - /** - * EaseOutExpo - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - easeOutExpo: function(t, d) { - t = easings.timeCheck(t, d); - return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b; - }, - - /** - * EaseInOutExpo - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - 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; - }, - - /** - * EaseInCirc - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - easeInCirc: function(t, d) { - t = easings.timeCheck(t, d); - return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; - }, - - /** - * EaseOutCirc - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - easeOutCirc: function(t, d) { - t = easings.timeCheck(t, d); - return c * Math.sqrt(1 - (t = t / d - 1) * t) + b; - }, - - /** - * EaseInOutCirc - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - 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; - }, - - /** - * EaseInElastic - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - 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; - }, - - /** - * EaseOutElastic - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - 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; - }, - - /** - * EaseInOutElastic - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - 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; - }, - - /** - * EaseInBack - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - 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; - }, - - /** - * EaseOutBack - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - 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; - }, - - /** - * EaseInOutBack - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - 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; - }, - - /** - * EaseInBounce - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - easeInBounce: function(t, d) { - t = easings.timeCheck(t, d); - return c - easings.easeOutBounce((d - t) / d, d) + b; - }, - - /** - * EaseOutBounce - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - 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; - } - }, - - /** - * EaseInOutBounce - * @public - * @param {number} t - current time - * @param {number} d - duration - * @return {number} calculated time - */ - 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; - } - -}; diff --git a/src/AnimationSupport/Frame.js b/src/AnimationSupport/Frame.js deleted file mode 100644 index 57a3a2bb5..000000000 --- a/src/AnimationSupport/Frame.js +++ /dev/null @@ -1,398 +0,0 @@ -/*jslint white: true*/ -require('enyo'); - -var - Dom = require('../dom'), - Vector = require('./Vector'), - utils = require('../utils'), - Matrix = require('./Matrix'); - -var - COLOR = {color: 1, backgroundColor: 1}, - INT_UNIT = {zIndex: 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 initial 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 = { - /** - * Calculate matrix3d of a frame based on transformation vectors. - * @public - * @param {Number[]} trns Translate vector - * @param {Number[]} rot Rotate quaternion vector - * @param {Number[]} sc Scale vector - * @param {Number[]} sq Skew vector - * @param {Number[]} per Perspective vector - * @return {Number[]} Final Matrix3d for particular frame - */ - 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; - }, - - /** - * Decompose transformation vectors into various properties out of matrix3d. - * @public - * @param {Number[]} matrix Matrix3d - * @param {Object} ret To store various transformation properties like translate, rotate, scale, skew and perspective. - * @return {Boolean} true, if matrix exists else false. - */ - 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, orthogonalized 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 - * @param {Object} v Object with transformation properties like translate, rotate, scale, skew and perspective. - * @param {Number} offset Determine how many Object to copy. - * @return {Number[]} Array with sliced value based on offset. - */ - copy: function (v, offset) { - return Array.prototype.slice.call(v, offset || 0); - }, - - /** - * Validates if property is a transform property. - * @public - * @param {String} transform Any transform property, for which we want to identify whether or not the property is transform. - * @return {Number} Value of the required transform property. - */ - isTransform: function (transform) { - return TRANSFORM[transform]; - }, - - /** - * Applies transformation to DOM element with the Matrix3d values. - * @public - * @param {enyo.Component} actor Component to be animated. - * @param {Number[]} m Matrix3d - */ - accelerate: function (actor, m) { - m = m ? m : Matrix.identity(); - frame.setTransformProperty(actor, m); - }, - - /** - * Reform matrix 2D to 3D - * @public - * @param {Number[]} v Matrix(2d) - * @return {Number[]} Matrix3d - */ - 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 separated values to array. - * @public - * @param {String} val Value of required animation in any property. - * @return {Number[]} Create array from val. - */ - parseValue: function (val) { - return val.toString().split(",").map(function(v) { - return parseFloat(v, 10); - }); - }, - - /** - * Gets a matrix for DOM element. - * @public - * @param {HTMLElement} style CSS style declaration. - * @return {Number[]} Matrix3d - */ - 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. - * @public - * @param {HTMLElement} style Computed style of a DOM. - * @param {String} key Property name for which style has to be fetched. - * @return {Number|HTMLElement} - */ - 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 - * @param {enyo.Component} actor Component to be animated. - * @param {String} prop CSS property to be applied. - * @param {Number} val Value of the property applied. - */ - setProperty: function (actor, prop, val) { - if (COLOR[prop]) { - val = val.map(function(v) { return parseInt(v, 10);}); - val = 'rgb('+ val + ')'; - } else if(INT_UNIT[prop]) { - val = parseInt(val[0], 10); - } else if (prop == 'opacity') { - val = val[0].toFixed(6); - val = (val <= 0) ? '0.000001' : val; - } else { - val = val[0] + 'px'; - } - - actor.addStyles(prop + ':' + val + ';'); - }, - - /** - * Applies transform property to DOM element. - * @public - * @param {enyo.Component} actor Component to be animated. - * @param {Number[]} matrix Matrix3d - */ - setTransformProperty: function (actor, 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;*/ - actor.addStyles('transform:' + mat + ';' - + 'webkitTransform:' + mat + ';' - + 'MozTransform:' + mat + ';' - + 'msTransform' + mat + ';' - + 'OTransform' + mat + ';'); - }, - - /** - * Get DOM node animation properties. - * @public - * @param {HTMLElement} node DOM node - * @param {Object} props Properties to fetch from DOM. - * @param {Object} initial Default properties to be applied. - * @return {Object} Object with various animation properties. - */ - getComputedProperty: function (node, props, initial) { - if(!node) return; - - var eP = {}, - sP = initial ? utils.mixin({}, 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.length < 4 ? Vector.toQuant(initial.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}; - }, - - /** - * Get DOM node animation distance. - * @public - * @param {HTMLElement} prop DOM node properties. - * @param {Object} initalProp Initial properties to fetch from DOM. - * @param {Object} finalProp Final properties to be applied. - * @return {Object} Total computed distance to animate. - */ - 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/Matrix.js b/src/AnimationSupport/Matrix.js deleted file mode 100644 index 9befb02c4..000000000 --- a/src/AnimationSupport/Matrix.js +++ /dev/null @@ -1,279 +0,0 @@ -require('enyo'); - -/** - * Matrix module for matrix related calculation - * - * @module enyo/AnimationSupport/Matrix - */ -module.exports = { - /** - * To create Identity Matrix3d (4X4 order). - * @public - * @return {Number[]} Identity Matrix3d - */ - identity: function() { - return [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]; - }, - - /** - * To translate in any dimension based on co-ordinates. - * @public - * @param {Number} x Translate value in X axis - * @param {Number} y Translate value in Y axis - * @param {Number} z Translate value in Z axis - * @return {Number[]} Matrix3d - */ - 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]; - }, - - /** - * To translate in x dimension - * @public - * @param {Number} x Translate value in X axis - * @return {Number[]} Matrix3d - */ - translateX: function (x) { - return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x ? x : 0, 0, 0, 1]; - }, - - /** - * To translate in y dimension - * @public - * @param {Number} y Translate value in Y axis - * @return {Number[]} Matrix3d - */ - translateY: function (y) { - return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, y ? y : 0, 0, 1]; - }, - - /** - * To translate in z dimension - * @public - * @param {Number} z Translate value in Z axis - * @return {Number[]} Matrix3d - */ - translateZ: function (z) { - return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, z ? z : 0, 1]; - }, - - /** - * To scale in any dimension - * @public - * @param {Number} x Scale value in X axis - * @param {Number} y Scale value in Y axis - * @param {Number} z Scale value in Z axis - * @return {Number[]} Matrix3d - */ - 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]; - }, - - /** - * To skew in any dimension (skew can only happen in 2d) - * @public - * @param {Number} a Skew value in X axis - * @param {Number} b Skew value in Y axis - * @return {Number[]} Matrix3d - */ - 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]; - }, - - /** - * To rotate in x-axis - * @public - * @param {Number} a Rotate value in X axis - * @return {Number[]} Matrix3d - */ - 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]; - }, - - /** - * To rotate in y-axis - * @public - * @param {Number} b Rotate value in Y axis - * @return {Number[]} Matrix3d - */ - 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]; - }, - - /** - * To rotate in z-axis - * @public - * @param {Number} g Rotate value in Z axis - * @return {Number[]} Matrix3d - */ - 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]; - }, - - /** - * To rotate in any dimension - * @public - * @param {Number} a Rotate value in X axis - * @param {Number} b Rotate value in Y axis - * @param {Number} g Rotate value in Z axis - * @return {Number[]} Matrix3d - */ - 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; - }, - - /** - * To multiply 2 Martix3d (4x4 order) - * @public - * @param {Number[]} m1 1st Matrix3d - * @param {Number[]} m2 2nd Matrix3d - * @return {Number[]} Resultant Matrix3d - */ - 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 - ]; - }, - - /** - * To multiply 2 matrices (NxN order) - * @public - * @param {Number[]} m1 1st Matrix of order N - * @param {Number[]} m2 2nd Matrix of order N - * @return {Number[]} Resultant Matrix of order N - */ - 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; - }, - - /** - * To inverse matrix of order N - * @public - * @param {Number[]} matrix Matrix (NxN order) - * @param {Number} n Order of the matrix - * @return {Number[]} Inverted Matrix - */ - 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; - }, - - /** - * Convert Matrix3d array to Matrix3d String - * @public - * @param {Number[]} m Matrix3d Array - * @return {String} Matrix3d String - */ - 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/Scene.js b/src/AnimationSupport/Scene.js deleted file mode 100644 index 3be48f4b9..000000000 --- a/src/AnimationSupport/Scene.js +++ /dev/null @@ -1,388 +0,0 @@ -var - editor = require('./SceneEditor'), - director = require('./Director'), - animation = require('../animation'), - utils = require('../utils'); - -/** - * This module exports "Scene" which is a class/constructor so that we can create an instance of the same. - * We can define all the animation properties we want in the application in the instance of the "Scene". - * - * @module enyo/AnimationSupport/Scene - */ -var Scene = module.exports = function(props) { - var scene = Scene.create(), - dur = props.duration || 0; - - utils.mixin(scene, editor); - - if (props.animation) { - var anims = utils.isArray(props.animation) ? props.animation : [props.animation]; - - for (var i = 0; i < anims.length; i++) { - scene.addAnimation(anims[i], anims[i].span || anims[i].duration || dur); - delete anims[i].duration; - } - delete props.animation; - delete props.duration; - } - - utils.mixin(scene, props); - utils.mixin(scene, SceneAction); - return scene; -}; - - -/** - * Creates a empty instance of scene. - * Can be used for runtime creation of animations - * @memberOf module:enyo/AnimationSupport/Scene - * @public - * @return {Object} An instance of the constructor - */ -Scene.create = function() { - return new sceneConstructor(utils.uid("@")); -}; - - -/** - * Connects an actor/s to a scene. - * All the actors should be added before initiating animation otherwise actors will animate for remaining time span - * @memberOf module:enyo/AnimationSupport/Scene - * @public - * @param {Object} actors - The elements which needs to be animated - * @param {Object} scene - The instance of the Scene we've created in the application - */ -Scene.link = function(actors, scene) { - director.cast(actors, scene); -}; - - -/** - * Disconnects an actor/s from a scene. - * (Actors could be delinked during the animation - * however they will current their state when delinked) - * @memberOf module:enyo/AnimationSupport/Scene - * @public - * @param {Object} actors - The elements which needs to be animated - * @param {Object} scene - The instance of the Scene we've created in the application - */ -Scene.delink = function(actors, scene) { - director.reject(scene, actors); -}; - - -/** - * Function to construct all the scenes instantiated from the Scene - * @memberOf module:enyo/AnimationSupport/Scene - * @private - * @param {number} id - id of the scene generated when created - * @return {object} Constructed instance - */ -var sceneConstructor = function(id) { - var - _ts, _wasts, _req, - _framerate = 16.6, - /** - * Stores the id of the instance created - * @memberOf module:enyo/AnimationSupport/Scene - * @private - * @type {Array} - */ - _id = id, - - /** - * Holds refereneces of the all animations added to this scene. - * @memberOf module:enyo/AnimationSupport/Scene - * @private - * @type {Array} - */ - _poses = [], - - /** - * Holds old animation time span, useful for scenarios where same - * time span is expected to be added for the latest added animation. - * This provides the felxibility to add animation without duration. - * - * Like: scene.addAnimation({translate: '50,0,0'}); - * - * As no duration is mentioned the old animations duration is taken. - * @type {Number} - * @memberOf module:enyo/AnimationSupport/Scene - * @private - */ - _prevDur = 0; - - /** - * An exposed property to know if know the animating state of this scene. - * 'true' - the scene is asked for animation(doesn't mean animation is happening) - * 'false' - the scene is not active(has completed or its actors are not visible) - * @type {Boolean} - * @memberOf module:enyo/AnimationSupport/Scene - * @public - */ - this.animating = false; - - /** - * An exposed property to know if the scene is ready with actors performing action. - * 'true' - the scene actors are ready for action - * 'false' - some or all actors are not ready - * @type {Boolean} - * @memberOf module:enyo/AnimationSupport/Scene - * @public - */ - this.active = false; - - /** - * Holds refereneces of complete time span for this scene. - * @type {Number} - * @memberOf module:enyo/AnimationSupport/Scene - * @public - */ - this.span = 0; - - - /** - * Function used to loop in all the animations in a scene - * @memberOf module:enyo/AnimationSupport/Scene - * @private - */ - function loop() { - if (this.animating) { - _ts = utils.perfNow(); - _ts = _ts - (_wasts !== undefined ? _wasts : _ts); - _ts = (_ts > _framerate) ? _framerate : _ts; - director.take(this, _ts); - _wasts = _ts; - this.trigger(true); - } else { - _wasts = undefined; - this.cancel(); - this.completed && this.completed(); - } - } - /** - * Function used to make start the animation if it is "true" for animating. - * @memberOf module:enyo/AnimationSupport/Scene - * @public - */ - this.ready = function() { - if (this.animating) { - if (!this.active) { - director.roll(this); - } - return this.active; - } - return false; - }; - - /** - * Cancel the animation - * @memberOf module:enyo/AnimationSupport/Scene - * @public - */ - this.cancel = function() { - animation.cancelRequestAnimationFrame(_req); - }; - - - /** - * Triggers the Request Animation Frame - * @param {boolean} force - A boolean value for letting the rAF start. - * @memberOf module:enyo/AnimationSupport/Scene - * @public - */ - this.trigger = function(force) { - if (force || !this.animating) { - _req = animation.requestAnimationFrame(utils.bindSafely(this, loop)); - } - }; - - - /** - * Gets the unique ID assigned to this sceen. - * @return {number} - id - * @memberOf module:enyo/AnimationSupport/Scene - * @public - */ - this.getID = function() { - return _id; - }; - - - /** - * Returns the life span/duration of this sceen. - * @return {number} life span/duration of this sceen - * @memberOf module:enyo/AnimationSupport/Scene - * @public - */ - this.totalSpan = function() { - return this.span; - }; - - /** - * Adds new animation on already existing animation for this character. - * @memberOf module:enyo/AnimationSupport/Scene - * @public - */ - this.addAnimation = function(newProp, span) { - if (_prevDur === 0 && span === 0) { - _poses[0] = { - animate: newProp, - span: 0 - }; - } else { - _prevDur = span || _prevDur; - this.span += _prevDur; - _poses.push({ - animate: newProp, - span: this.span - }); - } - }; - /** - * Function which returns the length of the poses. - * @memberOf module:enyo/AnimationSupport/Scene - * @public - * @return {number} - length of the poses - */ - this.length = function() { - return _poses.length; - }; - /** - * Returns animation pose index for a particular - * instance of time from the list of - * animations added to the scene. - * @param {number} span - Time span from the animation timeline - * @return {number} - index of the animation - * @memberOf module:enyo/AnimationSupport/Scene - * @public - */ - this.animateAtTime = function(span) { - var startIndex = 0, - stopIndex = _poses.length - 1, - middle = Math.floor((stopIndex + startIndex) / 2); - - if (span === 0) { - return startIndex; - } - - while (_poses[middle].span != span && startIndex < stopIndex) { - if (span < _poses[middle].span) { - stopIndex = middle; - } else if (span > _poses[middle].span) { - startIndex = middle + 1; - } - - middle = Math.floor((stopIndex + startIndex) / 2); - } - return (_poses[middle].span != span) ? startIndex : middle; - }; - - /** - * Clears/removes the animation - * @memberOf module:enyo/AnimationSupport/Scene - * @public - */ - this.clearAnimation = function() { - for (var i = 0; i < _poses.length; i++) { - _poses[i]._startAnim = undefined; - } - }; - - /** - * Returns animation pose based on index from the list of - * animations added to this scene. - * @param {number} index - animation's index from the list of animations - * @return {Object} pose of the animation based on the index in the list - * @memberOf module:enyo/AnimationSupport/Scene - * @public - */ - this.getAnimation = function(index) { - return index < 0 || _poses[index]; - }; - - /** - * Sets the newly added animation to the poses - * @param {Numner} index - index to which the new animation should set - * @param {Object} pose - newly added animation - * @memberOf module:enyo/AnimationSupport/Scene - * @public - */ - this.setAnimation = function(index, pose) { - _poses[index] = pose; - }; - - - //TODO: Move these events to Event Delegator - /** - * Event to identify when the scene has done animating. - * @memberOf module:enyo/AnimationSupport/Scene - * @public - */ - this.completed = function() {}; - - /** - * Event to identify when the scene has done a step(rAF updatation of time) in the animation. - * @memberOf module:enyo/AnimationSupport/Scene - * @public - */ - this.step = function() {}; - - /** - * Event to identify when the actor has done animating. - * @param {Object} actor - animating element - * @memberOf module:enyo/AnimationSupport/Scene - * @public - */ - this.actorCompleted = function(actor) {}; -}; - -/** - * SceneAction exposes the api which performs the action on the animation in a given scene - * @type {Object} - * @memberOf module:enyo/AnimationSupport/Scene - * @private - */ -var SceneAction = { - /** - * This function initiates action on the animation - * from the list of animations for a given scene. - * @param {number} ts - timespan - * @param {Object} pose - pose from the animation list - * @return {Object} - pose - * @memberOf module:enyo/AnimationSupport/Scene - * @private - */ - action: function(ts, pose) { - var past, - actor, - actors,i, - tm = this.rolePlay(ts), - index = this.animateAtTime(tm); - - if (index < 0) { - return; - } - pose = this.getAnimation(index); - past = pose.animate; - - if (past instanceof sceneConstructor) { - past._frameSpeed = this._frameSpeed; - director.take(past, ts); - } else { - past = index ? this.getAnimation(index - 1).span : 0; - actors = this.rolePlays[this.getID()]; - for (i = 0; (actor = actors[i]); i++) { - if (actor.generated) { - director.action(pose, - actors[i], - tm - past, - pose.span - past); - this.step && this.step(actor); - } - } - } - return pose; - } -}; \ No newline at end of file diff --git a/src/AnimationSupport/SceneActor.js b/src/AnimationSupport/SceneActor.js deleted file mode 100644 index 81eb194fd..000000000 --- a/src/AnimationSupport/SceneActor.js +++ /dev/null @@ -1,65 +0,0 @@ -var - Scene = require('./Scene'), - director = require('./Director'), - utils = require('../utils'); - -var - tm, actor, actors, len, dur; - -var CharacterAction = { - /** - * Overridden function initiates action on the animation - * for the given scene actor. - * @param {number} ts - timespan - * @param {Object} pose - pose from the animation list - * @return {Object} - pose - * @memberOf module:enyo/AnimationSupport/SceneActor - * @private - * @override - */ - action: function (ts, pose) { - var i, past, index; - - actors = this.rolePlays[this.getID()]; - len = actors.length; - dur = this.span; - for (i = 0; (actor = actors[i]); i++) { - //give priority to individual actor than scene. - if (!actor._frameSpeed) { - actor._frameSpeed = this._frameSpeed; - } - - if (actor.generated && actor._frameSpeed) { - tm = this.rolePlay(ts, actor); - if (isNaN(tm) || tm < 0) continue; - else if (tm <= dur) { - index = this.animateAtTime(tm); - pose = this.getAnimation(index); - past = index ? this.getAnimation(index - 1).span : 0; - director.action(pose, actor, tm - past, pose.span - past); - this.step && this.step(actor); - } else { - actor.timeline = dur; - actor._frameSpeed = 0; - this.actorCompleted && this.actorCompleted(actor); - } - } - } - return pose; - } -}; - -/** - * Scene Actor is used to individually manage all the actors

- * The Scene Actor is similar to Scene but can receive - * an actor for playing the animation.
- * Scene Actor's play when called without the actor, - * it works same as Scene playing all the actors.

- * Usage - SceneActorInstance.play(actor) - * @module enyo/AnimationSupport/SceneActor - */ -module.exports = function(props) { - var scene = Scene(props); - utils.mixin(scene, CharacterAction); - return scene; -}; diff --git a/src/AnimationSupport/SceneEditor.js b/src/AnimationSupport/SceneEditor.js deleted file mode 100644 index 14824613a..000000000 --- a/src/AnimationSupport/SceneEditor.js +++ /dev/null @@ -1,192 +0,0 @@ -require('enyo'); - -/** -* @module enyo/AnimationSupport/SceneEditor -*/ - -/** -* This modules exposes API's for controlling animations. -* @private -*/ -module.exports = { - /** - * @private - */ - timeline: 0, - /** - * @private - */ - _cachedValue: 0, - /** - * @private - */ - _frameSpeed: 0, - /** - * @private - */ - _startTime: 0, - - /** - * @private - */ - cache: function(actor) { - actor = actor || this; - if(actor._frameSpeed === 0){ - actor._frameSpeed = actor._cachedValue; - } - this.animating = true; - }, - - /** - * Starts the animation of the actor given in argument. - * If actor is not provided, animation of all the components linked to the {@link module:enyo/AnimationSupport/Scene} will be started. - * @param [Component]{@link module:enyo/Component~Component} actor The component to be animated - * @public - */ - play: function (actor) { - actor = actor || this; - actor._frameSpeed = 1; - if (isNaN(actor.timeline) || !actor.timeline) { - actor.timeline = 0; - } - this.trigger(); - this.animating = true; - }, - - /** - * Resumes the paused animation of the actor given in argument. - * If actor is not provided, animation of all the components linked to the {@link module:enyo/AnimationSupport/Scene} will be resumed. - * @param [Component]{@link module:enyo/Component~Component} actor The component to be animated - * @public - */ - resume: function(actor) { - this.cache(actor); - actor = actor || this; - actor._frameSpeed *= 1; - }, - - /** - * Pauses the animation of the actor given in argument. - * If actor is not provided, animation of all the components linked to the {@link module:enyo/AnimationSupport/Scene} will be paused. - * @param [Component]{@link module:enyo/Component~Component} actor The component to be animated - * @public - */ - pause: function (actor) { - actor = actor || this; - actor._cachedValue = actor._frameSpeed; - actor._frameSpeed = 0; - }, - - /** - * Reverses the animation of the actor given in argument. - * If actor is not provided, animation of all the components linked to the {@link module:enyo/AnimationSupport/Scene} will be reversed. - * @param [Component]{@link module:enyo/Component~Component} actor The component to be animated - * @public - */ - reverse: function (actor) { - this.cache(actor); - actor = actor || this; - actor._frameSpeed *= -1; - }, - - /** - * fast description goes here - * @param {Number} mul description goes here - * @param [Component]{@link module:enyo/Component~Component} actor description goes here - * @public - */ - fast: function (mul, actor) { - this.cache(actor); - actor = actor || this; - actor._frameSpeed *= mul; - }, - - /** - * slow description goes here - * @param {Number} mul description goes here - * @param [Component]{@link module:enyo/Component~Component} actor description goes here - * @public - */ - slow: function (mul, actor) { - this.cache(actor); - actor = actor || this; - actor._frameSpeed *= mul; - }, - - /** - * Changes the speed of the animation.
- * Speed of the animation changed based on the factor.
- * To slow down the speed use values between 0 and 1. For Example 0.5 to reduce the speed by 50%.
- * To increase the speed use values above 1. For Example 2 to increase the speed by 200%.
- * Animation will be paused if factor is 0. To pause the animation use {@link enyo/AnimationSupport/SceneEditor.pause pause} API.
- * Speed will not be affected incase of negative multiplication factor. - * @param {Number} factor Multiplication factor which changes the speed - * @param [Component {@link module:enyo/Component~Component}] actor The component whose animating speed should be changed - * @public - */ - speed: function(mul, actor) { - if (mul < 0) return; - this.cache(actor); - actor = actor || this; - actor._frameSpeed *= mul; - }, - - /** - * Stops the animation of the actor given in argument. - * If actor is not provided, animation of all the components linked to the {@link module:enyo/AnimationSupport/Scene} will be stopped. - * @param [Component]{@link module:enyo/Component~Component} actor The component to be animated - * @public - */ - stop: function (actor) { - actor = actor || this; - actor._cachedValue = 1; - actor._frameSpeed = 0; - actor.timeline = 0; - this.animating = false; - this.cancel(); - }, - - /** - * Seeks the animation of the actor to the position provided in timeline - * The value of timeline should be between 0 to duration of the animation. - * @param {Number} timeline Value in timeline where the animation has to be seeked - * @param [Component]{@link module:enyo/Component~Component} actor The component to be animated - * @public - */ - seek: function(timeline, actor) { - actor = actor || this; - if (this.animating !== true) { - this.play(actor); - this.pause(actor); - } - actor.timeline = timeline; - }, - - /** - * rolePlay updated the timeline of the actor which is currently animating. - * @param {Number} t Elapsed time since the animation of this pose has started (ratio in factor of 1) - * @param {@link module:enyo/Component~Component} actor The component which is animating - * @return {Number} Returns the updated timeline of the actor - * @access public - */ - rolePlay: function (t, actor) { - actor = actor || this; - if (actor.timeline === undefined || actor.timeline < 0) - actor.timeline = 0; - - if(actor.delay > 0) { - actor.delay -= _rolePlay(t, actor._frameSpeed); - } else { - actor.timeline += _rolePlay(t, actor._frameSpeed); - } - return actor.timeline; - } -}; - -/** - * Returns the time based on the current speed of animation. - * @private - */ -function _rolePlay(t, mul) { - return mul * t; -} \ No newline at end of file diff --git a/src/AnimationSupport/SceneEvent.js b/src/AnimationSupport/SceneEvent.js index 0a13e1040..f379c824d 100644 --- a/src/AnimationSupport/SceneEvent.js +++ b/src/AnimationSupport/SceneEvent.js @@ -1,6 +1,5 @@ var - Scene = require('./Scene'), - director = require('./Director'), + Scene = require('../scene'), dispatcher = require('../dispatcher'), emitter = require('../EventEmitter'), utils = require('../utils'); @@ -188,8 +187,8 @@ var EventAction = { /** * To get event changes captured, for delta values of x, y and z. - * @return {Object} delta - pose - * @memberOf module:enyo/AnimationSupport/SceneEvent + * @return {Object} delta - pose + * @memberOf module:enyo/AnimationSupport/SceneEvent * @public */ getAnimationDelta: function() { @@ -198,8 +197,8 @@ var EventAction = { /** * To be when an virtual event has to be triggered for the last event captured. - * @return {void} - * @memberOf module:enyo/AnimationSupport/SceneEvent + * @return {void} + * @memberOf module:enyo/AnimationSupport/SceneEvent * @public */ triggerEvent: function() { @@ -211,9 +210,9 @@ var EventAction = { * To be used when an actor is registerd for event tracking. * Its keeps track of only on actor which is refered as * the originator. - * @param {Object} actor - Component on which events will be captured. - * @return {void} - * @memberOf module:enyo/AnimationSupport/SceneEvent + * @param {Object} actor - Component on which events will be captured. + * @return {void} + * @memberOf module:enyo/AnimationSupport/SceneEvent * @public */ register: function (actor) { @@ -224,19 +223,19 @@ var EventAction = { }, /** - * Overridden function initiates action on the animation - * for the given scene event. - * @param {number} ts - timespan - * @param {Object} pose - pose from the animation list - * @return {Object} - pose - * @memberOf module:enyo/AnimationSupport/SceneEvent - * @private - * @override - */ + * Overridden function initiates action on the animation + * for the given scene event. + * @param {number} ts - timespan + * @param {Object} pose - pose from the animation list + * @return {Object} - pose + * @memberOf module:enyo/AnimationSupport/SceneEvent + * @private + * @override + */ action: function (ts, pose) { if (_isTriggered && _triggerer && this.handlers && this.handlers[_triggerer] !== undefined) { if (this.handlers[_triggerer] === "") { - ts = director.shot(this, ts); + ts = this.shot(this, ts); pose = sup.call(this, ts, pose); if(ts === 0) _isTriggered = false; } else { @@ -244,6 +243,54 @@ var EventAction = { } } return pose; + }, + + /** + * shot method is invloved in distance based animation in which the distance definite and + * indefinite (Event based animations). This method calculates the distance to which the actor has to + * be animated based on the delta and the acceleration. + * @param {@link module:enyo/AnimationSupport/Scene} scene scene applied for achieving animation + * @param {Number} ts delta distance + * @return {Number} The distance to which the actor has to be transformed + */ + shot: function(scene, ts) { + var v1, s, a, v = 0, + t = ts, + dt = scene.getAnimationDelta(), + dir = this.angle(scene.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; + } + return dt[dir] > 0 ? v : -v; + }, + + /** + * @private + */ + angle: function (direction) { + switch(direction) { + case "X" : + return "dX"; + case "Y" : + return "dY"; + case "Z" : + return "dZ"; + default: + return "dX"; + } } }; diff --git a/src/AnimationSupport/Vector.js b/src/AnimationSupport/Vector.js deleted file mode 100644 index 9f0db08e3..000000000 --- a/src/AnimationSupport/Vector.js +++ /dev/null @@ -1,317 +0,0 @@ -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. - * @param {Number[]} v - vector - * @param {Number} s - scalar value to divide - * @return {Number[]} resultant vector - * @memberOf module:enyo/AnimationSupport/Vector - * @public - */ - divide: function (v, s) { - return [v[0] / s, v[1] / s, v[2] / s]; - }, - - /** - * Add vector/quant with a vector/quant. - * @param {Number[]} q1 - vector/quant - * @param {Number[]} q2 - vector/quant - * @return {Number[]} added vector/quant - * @memberOf module:enyo/AnimationSupport/Vector - * @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. - * @param {Number[]} q1 - vector/quant - * @param {Number[]} q2 - vector/quant - * @return {Number[]} subracted vector/quant - * @memberOf module:enyo/AnimationSupport/Vector - * @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. - * @param {Number[]} v - vector - * @param {Number} s - scalar value to divide - * @return {Number[]} resultant vector - * @memberOf module:enyo/AnimationSupport/Vector - * @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. - * @param {Number[]} q - vector/quant - * @param {Number} max - maximum range value - * @param {Number} min - minimum range value - * @return {Number[]} resultant vector/quant - * @memberOf module:enyo/AnimationSupport/Vector - * @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]; - } - }, - - /** - * Evaluates the gap between two vector values. - * Returns the absolute distance between two vectors. - * @param {Number[]} v1 - vector - * @param {Number[]} v2 - vector - * @param {Number} d - distance between vector - * @return {Number} distance between vector - * @memberOf module:enyo/AnimationSupport/Vector - * @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. - * @param {Number[]} v1 - quant - * @param {Number[]} v2 - quant - * @param {Number} d - distance between quant - * @return {Number} distance between quant - * @memberOf module:enyo/AnimationSupport/Vector - * @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. - * @param {Number[]} v1 - quant - * @param {Number[]} v2 - quant - * @return {boolean} true if positive, false otherwise. - * @memberOf module:enyo/AnimationSupport/Vector - * @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. - * @param {Number[]} q - quant - * @return {Number[]} resultant quant - * @memberOf module:enyo/AnimationSupport/Vector - * @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 a vector - * @param {Number[]} v - vetor - * @return {Number} resultant length - * @memberOf module:enyo/AnimationSupport/Vector - * @public - */ - len: function (v) { - return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); - }, - - /** - * Dot product of 3D vectors - * @param {Number[]} v1 - vetor - * @param {Number[]} v2 - vetor - * @return {Number} resultant dot product - * @memberOf module:enyo/AnimationSupport/Vector - * @public - */ - dot: function (v1, v2) { - return (v1[0] * v2[0]) + (v1[1] * v2[1]) + (v1[2] * v2[2]) + (v1[3] !== undefined && v2[3] !== undefined ? (v1[3] * v2[3]) : 0); - }, - - /** - * Dot product of 3D quanterion - * @param {Number[]} q1 - quanterion - * @param {Number[]} q2 - quanterion - * @return {Number} resultant dot product - * @memberOf module:enyo/AnimationSupport/Vector - * @public - */ - quantDot: function (q1, q2) { - return (q1[0] * q2[0]) + (q1[1] * q2[1]) + (q1[2] * q2[2]) + (q1[3] * q2[3]); - }, - - /** - * Quant Cross product of 3D quanterion - * @param {Number[]} q1 - quanterion - * @param {Number[]} q2 - quanterion - * @return {Number[]} resultant cross product - * @memberOf module:enyo/AnimationSupport/Vector - * @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 - * @param {Number[]} v1 - vetor - * @param {Number[]} v2 - vetor - * @return {Number[]} resultant cross product - * @memberOf module:enyo/AnimationSupport/Vector - * @public - */ - cross: function (v1, v2) { - return [ - v1[1] * v2[2] - v1[2] * v2[1], - v1[2] * v2[0] - v1[0] * v2[2], - v1[0] * v2[1] - v1[1] * v2[0] - ]; - }, - - /** - * Normalizing a vector is obtaining another unit vector in the same direction. - * To normalize a vector, divide the vector by its magnitude. - * @param {Number[]} q1 - quanterion - * @return {Number[]} resultant quanterion - * @memberOf module:enyo/AnimationSupport/Vector - * @public - */ - normalize: function (q) { - return this.divide(q, this.len(q)); - }, - - /** - * Combine scalar values with two vectors. - * Required during parsing scaler values matrix. - * @param {Number[]} a - first vector - * @param {Number[]} b - second vector - * @param {Number[]} ascl - first vector scalar - * @param {Number[]} ascl - second vector scalar - * @return {Number[]} resultant vector - * @memberOf module:enyo/AnimationSupport/Vector - * @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. - * @param {Number[]} rv - quanterion - * @return {Number[]} resultant rotation vector - * @memberOf module:enyo/AnimationSupport/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. - * @param {Number[]} v - vector - * @return {Number[]} resultant quaternion - * @memberOf module:enyo/AnimationSupport/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; - }, - - /** - * Converts a quaternion vector vector to a rotation vector. - * @param {Number[]} q - quaternion - * @return {Number[]} resultant vector - * @memberOf module:enyo/AnimationSupport/Vector - * @public - */ - 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/Animator.js b/src/Animator.js index fbb8df5a1..8431a48ec 100644 --- a/src/Animator.js +++ b/src/Animator.js @@ -8,6 +8,7 @@ require('enyo'); var kind = require('./kind'), utils = require('./utils'), + easing = require('./easing'), animation = require('./animation'); var @@ -59,13 +60,13 @@ var * to [endValue]{@link module:enyo/Animator~Animator#endValue} during the animation, based on the * [function]{@glossary Function} referenced by the * [easingFunction]{@link module:enyo/Animator~Animator#easingFunction} property. -* +* * Event handlers may be specified as functions. If specified, the handler function will * be used to handle the event directly, without sending the event to its * [owner]{@link module:enyo/Component~Component#owner} or [bubbling]{@link module:enyo/Component~Component#bubble} it. * The [context]{@link module:enyo/Animator~Animator#context} property may be used to call the supplied * event functions in a particular `this` context. -* +* * During animation, an {@link module:enyo/jobs} priority of 5 is registered to defer low priority * tasks. * @@ -79,14 +80,14 @@ module.exports = kind( /** * A context in which to run the specified {@glossary event} handlers. If this is * not specified or is falsy, then the [global object]{@glossary global} is used. - * + * * @name context * @type {Object} * @default undefined * @memberOf module:enyo/Animator~Animator.prototype * @public */ - + name: 'enyo.Animator', /** @@ -97,10 +98,10 @@ module.exports = kind( /** * @private */ - published: + published: /** @lends module:enyo/Animator~Animator.prototype */ { - - /** + + /** * Animation duration in milliseconds * * @type {Number} @@ -109,7 +110,7 @@ module.exports = kind( */ duration: 350, - /** + /** * Value of [value]{@link module:enyo/Animator~Animator#value} property at the beginning of an animation. * * @type {Number} @@ -118,7 +119,7 @@ module.exports = kind( */ startValue: 0, - /** + /** * Value of [value]{@link module:enyo/Animator~Animator#value} property at the end of an animation. * * @type {Number} @@ -127,8 +128,8 @@ module.exports = kind( */ endValue: 1, - /** - * Node that must be visible in order for the animation to continue. This reference is + /** + * Node that must be visible in order for the animation to continue. This reference is * destroyed when the animation ceases. * * @type {Object} @@ -137,17 +138,17 @@ module.exports = kind( */ node: null, - /** - * [Function]{@glossary Function} that determines how the animation progresses from + /** + * [Function]{@glossary Function} that determines how the animation progresses from * [startValue]{@link module:enyo/Animator~Animator#startValue} to [endValue]{@link module:enyo/Animator~Animator#endValue}. - * + * * @type {Function} * @default module:enyo/easing~easing.cubicOut * @public */ - easingFunction: animation.easing.cubicOut + easingFunction: easing.cubicOut }, - + /* * @private */ @@ -179,7 +180,7 @@ module.exports = kind( }; }), - /** + /** * Plays the animation. * * @param {Object} props - As a convenience, this [hash]{@glossary Object} will be mixed @@ -203,7 +204,7 @@ module.exports = kind( return this; }, - /** + /** * Stops the animation and fires the associated {@glossary event}. * * @fires module:enyo/Animator~Animator#onStop @@ -235,9 +236,9 @@ module.exports = kind( return this; }, - /** + /** * Reverses the direction of a running animation. - * + * * @return {this} The callee for chaining. * @public */ @@ -277,7 +278,7 @@ module.exports = kind( * @private */ cancel: function () { - animation.cancelRequestAnimationFrame(this.job); + animation.cancelAnimationFrame(this.job); this.node = null; this.job = null; diff --git a/src/BackgroundTaskManager.js b/src/BackgroundTaskManager.js index 58fa189c6..039e21c89 100644 --- a/src/BackgroundTaskManager.js +++ b/src/BackgroundTaskManager.js @@ -2,6 +2,7 @@ require('enyo'); /** * Returns the BackgroundTaskManager singleton. +* @wip * @module enyo/BackgroundTaskManager */ diff --git a/src/Binding.js b/src/Binding.js index 79aeafc4b..1d627e60a 100644 --- a/src/Binding.js +++ b/src/Binding.js @@ -95,8 +95,13 @@ function ready (binding) { /*jshint +W093 */ } + +/** +* @class PassiveBinding +* @public +*/ var PassiveBinding = kind( - /** @lends enyo.PassiveBinding.prototype */ { + /** @lends module:enyo/Binding~PassiveBinding.prototype */ { name: 'enyo.PassiveBinding', @@ -680,4 +685,8 @@ exports.DIRTY_TO = DIRTY_TO; */ exports.defaultBindingKind = exports; +/** +* The kind declaration for the [PassiveBinding]{@link module:enyo/Binding~PassiveBinding} kind +* @public +*/ exports.PassiveBinding = PassiveBinding; diff --git a/src/Checkbox/Checkbox.js b/src/Checkbox/Checkbox.js index d58885aa8..812988201 100644 --- a/src/Checkbox/Checkbox.js +++ b/src/Checkbox/Checkbox.js @@ -7,8 +7,7 @@ require('enyo'); var kind = require('../kind'), - utils = require('../utils'), - platform = require('../platform'); + utils = require('../utils'); var Input = require('../Input'); @@ -60,36 +59,36 @@ module.exports = kind( /** * @private */ - published: + published: /** @lends module:enyo/Checkbox~Checkbox.prototype */ { - + /** * Value of the checkbox; will be `true` if checked. - * + * * @type {Boolean} * @default false * @public */ checked: false, - + /** * A [Group API]{@link module:enyo/Group~Group} requirement for determining the selected item. - * + * * @type {Boolean} * @default false * @public */ active: false, - + /** * @private */ type: 'checkbox' }, - + /** * Disable classes inherited from {@link module:enyo/Input~Input}. - * + * * @private */ kindClasses: "", @@ -98,8 +97,7 @@ module.exports = kind( * @private */ handlers: { - onchange: 'change', - onclick: 'click' + onchange: 'change' }, /** @@ -129,7 +127,7 @@ module.exports = kind( * The [active]{@link module:enyo/Checkbox~Checkbox#active} property and `onActivate` * {@glossary event} are part of the [GroupItem]{@link module:enyo/GroupItem~GroupItem} * interface supported by this [object]{@glossary Object}. - * + * * @private */ activeChanged: function () { @@ -139,7 +137,7 @@ module.exports = kind( }, /** - * All [input]{@link module:enyo/Input~Input} type [controls]{@link module:enyo/Control~Control} support the + * All [input]{@link module:enyo/Input~Input} type [controls]{@link module:enyo/Control~Control} support the * [value]{@link module:enyo/Input~Input#value} property. * * @param {Boolean} val - Whether or not the [checkbox]{@link module:enyo/Checkbox~Checkbox} should @@ -167,32 +165,19 @@ module.exports = kind( * @private */ valueChanged: function () { + // inherited behavior is to set "value" attribute and node-property + // which does not apply to checkbox (uses "checked") so + // we squelch the inherited method + }, /** * @private */ - // inherited behavior is to set "value" attribute and node-property - // which does not apply to checkbox (uses "checked") so - // we squelch the inherited method - }, change: function () { var nodeChecked = utils.isTrue(this.getNodeProperty('checked')); this.setActive(nodeChecked); }, - /** - * @private - */ - click: function (sender, e) { - // Various versions of IE (notably IE8) do not fire 'onchange' for - // checkboxes, so we discern change via 'click'. - // Note: keyboard interaction (e.g. pressing space when focused) fires - // a click event. - if (platform.ie <= 8) { - this.bubble('onchange', e); - } - }, - // Accessibility /** diff --git a/src/Collection.js b/src/Collection.js index 21241f53a..d3df3b700 100644 --- a/src/Collection.js +++ b/src/Collection.js @@ -157,7 +157,7 @@ var BaseCollection = kind({ * {@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. @@ -186,7 +186,7 @@ var BaseCollection = kind({ * {@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 @@ -250,7 +250,7 @@ var BaseCollection = kind({ /** * 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 @@ -259,19 +259,19 @@ var BaseCollection = kind({ */ 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}, @@ -287,7 +287,7 @@ exports = module.exports = kind( * @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 @@ -307,19 +307,19 @@ exports = module.exports = kind( * @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)} + * + * @type module:enyo/Model~Model * @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 @@ -335,7 +335,7 @@ exports = module.exports = kind( * @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 @@ -348,14 +348,14 @@ exports = module.exports = kind( * @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 */ @@ -373,7 +373,7 @@ exports = module.exports = kind( 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 @@ -383,7 +383,7 @@ exports = module.exports = kind( * 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}. @@ -395,7 +395,7 @@ exports = module.exports = kind( 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. @@ -406,7 +406,7 @@ exports = module.exports = kind( * 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 @@ -428,16 +428,16 @@ exports = module.exports = kind( , 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 @@ -449,50 +449,50 @@ exports = module.exports = kind( , 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; @@ -999,9 +1011,9 @@ exports = module.exports = kind( } 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; @@ -1009,38 +1021,39 @@ exports = module.exports = kind( 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 + + // 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); - + + // remove the models from the collection which will also remove the its event listeners + if (this.length) 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 @@ -1055,7 +1068,7 @@ exports = module.exports = kind( * @public */ comparator: null, - + /** * Used during [add()]{@link module:enyo/Collection~Collection#add} when `create` is `true` and * the data is a [hash]{@glossary Object}. @@ -1066,19 +1079,19 @@ exports = module.exports = kind( 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 @@ -1099,7 +1112,7 @@ exports = module.exports = kind( */ committed: function (opts, res, source) { var idx; - + if (this._waiting) { idx = this._waiting.findIndex(function (ln) { return (ln instanceof Source ? ln.name : ln) == source; @@ -1107,15 +1120,15 @@ exports = module.exports = kind( 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 @@ -1136,7 +1149,7 @@ exports = module.exports = kind( */ fetched: function (opts, res, source) { var idx; - + if (this._waiting) { idx = this._waiting.findIndex(function (ln) { return (ln instanceof Source ? ln.name : ln) == source; @@ -1144,22 +1157,22 @@ exports = module.exports = kind( 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} @@ -1168,7 +1181,7 @@ exports = module.exports = kind( * 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 @@ -1180,25 +1193,25 @@ exports = module.exports = kind( */ 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 @@ -1211,7 +1224,7 @@ exports = module.exports = kind( clearError: function () { return this.set('status', States.READY); }, - + /** * @private */ @@ -1225,7 +1238,7 @@ exports = module.exports = kind( break; } }, - + /** * Responds to changes to the [models]{@link module:enyo/Collection~Collection#models} property. * @@ -1237,12 +1250,12 @@ exports = module.exports = kind( 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}. * @@ -1259,48 +1272,51 @@ exports = module.exports = kind( 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; + } else { + // ensure we have our own copy of options + this.options = utils.clone(this.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 @@ -1308,12 +1324,12 @@ exports = module.exports = kind( 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(); }; }) - + }); /** @@ -1323,7 +1339,7 @@ exports = module.exports = kind( */ exports.concat = function (ctor, props) { var proto = ctor.prototype || ctor; - + if (props.options) { proto.options = utils.mixin({}, [proto.options, props.options]); delete props.options; diff --git a/src/Component.js b/src/Component.js index 541d5cbd0..ea9cf0f77 100644 --- a/src/Component.js +++ b/src/Component.js @@ -14,6 +14,7 @@ var CoreObject = require('./CoreObject'), ApplicationSupport = require('./ApplicationSupport'), ComponentBindingSupport = require('./ComponentBindingSupport'), + AnimationSupport = require('./AnimationSupport/AnimationSupport'), Jobs = require('./jobs'); var @@ -43,7 +44,7 @@ var * var c = new Component({ * name: 'me', * components: [ -* {kind: 'Component', name: 'other'} +* {kind: Component, name: 'other'} * ] * }); * @@ -55,7 +56,7 @@ var * var c = new Component({ * name: 'me', * components: [ -* {kind: 'Component', name: 'other'} +* {kind: Component, name: 'other'} * ] * }); * @@ -83,7 +84,7 @@ var * var c = new Component({ * name: 'me', * components: [ -* {kind: 'Component', name: 'other', publish: true} +* {kind: Component, name: 'other', publish: true} * ] * }); * diff --git a/src/Control/fullscreen.js b/src/Control/fullscreen.js index 82e128bd5..8a6144b84 100644 --- a/src/Control/fullscreen.js +++ b/src/Control/fullscreen.js @@ -236,12 +236,9 @@ module.exports = function (Control) { * @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); - } + document.addEventListener('webkitfullscreenchange', utils.bind(fullscreen, 'detectFullscreenChangeEvent'), false); + document.addEventListener('mozfullscreenchange', utils.bind(fullscreen, 'detectFullscreenChangeEvent'), false); + document.addEventListener('fullscreenchange', utils.bind(fullscreen, 'detectFullscreenChangeEvent'), false); }); /** diff --git a/src/DataGridList/DataGridList.js b/src/DataGridList/DataGridList.js index ba2c9e5ad..c63193485 100644 --- a/src/DataGridList/DataGridList.js +++ b/src/DataGridList/DataGridList.js @@ -80,7 +80,7 @@ var DataGridList = module.exports = kind( * @method * @private */ - constructor: kind.inherit(function (sup) { + create: kind.inherit(function (sup) { return function () { var o = this.orientation; // this will only remap _vertical_ and _horizontal_ meaning it is still possible to diff --git a/src/DataList/DataList.js b/src/DataList/DataList.js index 7bce1a45e..d65eb00dc 100644 --- a/src/DataList/DataList.js +++ b/src/DataList/DataList.js @@ -643,7 +643,12 @@ var DataList = module.exports = kind( * * @private */ - statics: {delegates: {vertical: VerticalDelegate, horizontal: HorizontalDelegate}}, + statics: /** @lends module:enyo/DataList~DataList */ { + /** + * @private + */ + delegates: {vertical: VerticalDelegate, horizontal: HorizontalDelegate} + }, /** * An [array]{@glossary Array} of the actual page references for easier access. diff --git a/src/DataRepeater.js b/src/DataRepeater.js index 49de3d17c..059988bea 100644 --- a/src/DataRepeater.js +++ b/src/DataRepeater.js @@ -769,19 +769,36 @@ var DataRepeater = module.exports = kind( childMixins: [RepeaterChildSupport], /** - * @private + * The name of the container specified in + * [containerOptions]{@link module:enyo/DataRepeater~DataRepeater#containerOptions}. This may or + * may not have the same value as + * [controlParentName]{@link module:enyo/DataRepeater~DataRepeater#controlParentName}. + * + * @type {String} + * @default 'container' + * @public */ - controlParentName: 'container', + containerName: 'container', /** - * @private + * A [Kind]{@glossary Kind} definition that will be used as the chrome for the container + * of the DataRepeater. When specifying a custom definition be sure to include a container + * component that has the name specified in + * [controlParentName]{@link module:enyo/DataRepeater~DataRepeater#controlParentName}. + * + * @type {Object} + * @default {name: 'container', classes: 'enyo-fill enyo-data-repeater-container'} + * @public */ - containerName: 'container', + containerOptions: {name: 'container', classes: 'enyo-fill enyo-data-repeater-container'}, /** - * @private + * See {@link module:enyo/UiComponent~UiComponent#controlParentName} + * @type {String} + * @default 'container' + * @public */ - containerOptions: {name: 'container', classes: 'enyo-fill enyo-data-repeater-container'}, + controlParentName: 'container', /** * @private diff --git a/src/EventEmitter.js b/src/EventEmitter.js index 954f67968..38bb50135 100644 --- a/src/EventEmitter.js +++ b/src/EventEmitter.js @@ -6,6 +6,7 @@ require('enyo'); var + kind = require('./kind'), utils = require('./utils'); var @@ -21,7 +22,7 @@ function addListener(obj, e, fn, ctx) { method: fn, ctx: ctx || obj }); - + return obj; } @@ -31,14 +32,14 @@ function addListener(obj, e, fn, ctx) { 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; } @@ -49,7 +50,7 @@ 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); @@ -59,10 +60,10 @@ function emit(obj, args) { } for (var i=0, ln; (ln=listeners[i]); ++i) ln.method.apply(ln.ctx, args); - + return true; } - + return false; } @@ -70,29 +71,29 @@ function emit(obj, args) { * {@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}. +* 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 @@ -107,7 +108,7 @@ var EventEmitter = { 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 @@ -127,7 +128,7 @@ var EventEmitter = { } return this; }, - + /** * Determines whether the callee is currently [silenced]{@link module:enyo/EventEmitter~EventEmitter.silence}. * @@ -138,20 +139,19 @@ var EventEmitter = { isSilenced: function () { return this._silenced; }, - + /** - * @alias enyo.EventEmitter.on - * @deprecated + * @deprecated Replaced by {@link module:enyo/EventEmitter~EventEmitter.on} * @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}. + * [emitted]{@link module:enyo/EventEmitter~EventEmitter.emit}. * * @param {String} e - The {@glossary event} name to register for. * @param {Function} fn - The listener. @@ -162,16 +162,15 @@ var EventEmitter = { on: function (e, fn, ctx) { return addListener(this, e, fn, ctx); }, - + /** - * @alias enyo.EventEmitter.off - * @deprecated + * @deprecated Replaced by {@link module:enyo/EventEmitter~EventEmitter.off} * @public */ removeListener: function (e, fn, ctx) { return removeListener(this, e, fn, ctx); }, - + /** * Removes an {@glossary event} listener. * @@ -185,7 +184,7 @@ var EventEmitter = { off: function (e, fn, ctx) { return removeListener(this, e, fn, ctx); }, - + /** * Removes all listeners, or all listeners for a given {@glossary event}. * @@ -195,20 +194,20 @@ var EventEmitter = { 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; + delete eventTable[euid]; } } - + 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). @@ -222,21 +221,20 @@ var EventEmitter = { 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 + * @deprecated Replaced by {@link module:enyo/EventEmitter~EventEmitter.emit} * @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. @@ -248,7 +246,17 @@ var EventEmitter = { */ emit: function () { return !this._silenced? emit(this, arguments): false; - } + }, + + /** + * @private + */ + destroy: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + this.removeAllListeners(); + }; + }) }; module.exports = EventEmitter; diff --git a/src/Filter.js b/src/Filter.js index a8927993c..755d75541 100644 --- a/src/Filter.js +++ b/src/Filter.js @@ -14,7 +14,7 @@ var Collection = require('./Collection'); /** * Used internally (re-use) for filters that do not have a valid filter. This means they will * always keep a mirrored copy of the entire current dataset of the parent filter. -* +* * @private */ function alwaysTrue () { @@ -28,31 +28,31 @@ function alwaysTrue () { * collection, which keeps its own set of [model]{@link module:enyo/Model~Model} instances (and can * create, remove, or destroy them), an {@link module:enyo/Filter~Filter} uses another instance of * `enyo.Collection` as its dataset and safely proxies its models as a complete set or -* according to the needs of its subkind. `enyo.Filter` is not intended to communicate +* according to the needs of its subkind. `enyo/Filter` is not intended to communicate * with [sources]{@link module:enyo/Source~Source} (e.g., via [fetch()]{@link module:enyo/Collection~Collection#fetch}). * It maintains an implementation-specific API (from its subkinds) and propagates the -* events and APIs inherited from `enyo.Collection` that are needed to interact with +* events and APIs inherited from `enyo/Collection` that are needed to interact with * [controls]{@link module:enyo/Control~Control}. -* +* * @class Filter * @extends module:enyo/Collection~Collection * @protected */ var Filter = module.exports = kind( /** @lends module:enyo/Filter~Filter.prototype */ { - + name: 'enyo.Filter', - + /** * @private */ kind: Collection, - + /** * @private */ - + /** * Provide a filter-method that will be applied to each [model]{@link module:enyo/Model~Model} in the * current set of models. This method will accept parameters according to those supplied @@ -64,37 +64,37 @@ var Filter = module.exports = kind( * @public */ method: null, - + /** * The actual {@link module:enyo/Collection~Collection} content to proxy. How the collection is * used varies depending on the [subkind]{@glossary subkind} implementing the * feature. - * + * * @type module:enyo/Collection~Collection * @default null * @public */ collection: null, - + /** * Once all components have been created, those that are [filters]{@link module:enyo/Filter~Filter} * (or [subkinds]{@glossary subkind}) will be added to this [array]{@glossary Array}. * This array is primarily for internal use and should not be modified directly. - * + * * @type Array * @default null * @readonly * @public */ filters: null, - + /** * @private */ defaultProps: { kind: null // replaced after the fact }, - + /** * @private */ @@ -102,23 +102,23 @@ var Filter = module.exports = kind( return function (props) { // all filters are public...always...except when they aren't... if (props.publish !== false) props.publish = true; - + sup.apply(this, arguments); - + // now to ensure that there is the correct kind associated with the child component if (typeof props.kind == 'string') props.kind = kind.constructorForKind(props.kind); if (props.kind && props.kind.prototype instanceof Filter) { if (!props.name) { throw 'enyo.Filter.adjustComponentProps: Child filters must have a name'; } - + // if no method is named explicitly we assume the same name as the filter if (!props.method) props.method = props.name; - + // most likely it will be a string but it is possible that the filter method // be declared inline in the component descriptor block if (typeof props.method == 'string') props.method = this[props.method]; - + // we assign an always true method if none exists just because we assume it was // mean to be a mirror filter for the entire dataset if (typeof props.method != 'function') { @@ -128,30 +128,30 @@ var Filter = module.exports = kind( } }; }), - + /** * @private */ addComponent: kind.inherit(function (sup) { return function (comp) { - + // if the component is a filter we add it to the array if (comp instanceof Filter) this.filters.push(comp); - + return sup.apply(this, arguments); }; }), - + /** * Resets the [filter]{@link module:enyo/Filter~Filter} to its initial state. Behavior will * vary depending on the [subkind]{@glossary subkind} implementation. - * + * * @virtual * @method * @public */ reset: utils.nop, - + /** * @private */ @@ -159,16 +159,16 @@ var Filter = module.exports = kind( return function () { // ensure we have an array to work with this.filters = []; - + // unfortunately we must maintain data structures that need remain out of our // proxy path so we each must create a collection instance for internal use this._internal = new Collection({options: {modelEvents: false}}); this._internal.on('*', this._internalEvent, this); - + sup.apply(this, arguments); }; }), - + /** * @private */ @@ -176,37 +176,37 @@ var Filter = module.exports = kind( return function () { var collection, owner; - + sup.apply(this, arguments); - + // we allow filters to be nested so they need to receive events from the // parent-filter and do with them as they need this.isChildFilter = ((owner = this.owner) && owner instanceof Filter); if(this.isChildFilter) { - + // if we're a child collection we don't want to monitor our parent's own state // we want to monitor their entire dataset this.collection = owner._internal; - + // register especially for owner events as we will differentiate them from // normal collection events this.collection.on('*', this._ownerEvent, this); } - + collection = this.collection; - + // if there is a collection instance already we need to initialize it if (collection) this.collectionChanged(null, collection); }; }), - + /** * @private */ destroy: kind.inherit(function (sup) { return function () { var collection = this.collection; - + // make sure that we remove our listener if we're being destroyed for some // reason (this would seem to be an irregular practice) if (collection) { @@ -215,42 +215,42 @@ var Filter = module.exports = kind( } else { collection.off('*', this._collectionEvent, this); } - + collection.unobserve('destroyed', this._collectionDestroyed, this); } - + sup.apply(this, arguments); - + // free our internal collection this._internal.destroy(); this._internal = null; }; }), - + /** * @private */ collectionChanged: function (was, is) { var internal = this._internal; - + if (was) { was.off('*', this._collectionEvent, this); was.unobserve('destroyed', this._collectionDestroyed, this); } - + // ensure that child-filters cannot have their internal/external collections reset if (is && !(was && this.isChildFilter && was === this.owner._internal)) { - + // case of child-filter whose collection is its owner does not need to receive // these events since it will receive them in a special handler to differentiate // these cases if (!this.isChildFilter || (is !== this.owner._internal)) { is.on('*', this._collectionEvent, this); } - + // if for any reason the collection is destroyed we want to know about it is.observe('destroyed', this._collectionDestroyed, this); - + // reset the models (causing reset to propagate to children or bound parties) internal.set('models', is.models.copy()); } else { @@ -258,7 +258,7 @@ var Filter = module.exports = kind( if (internal.length) internal.empty(); } }, - + /** * This method is invoked when events are received from a * [collection]{@link module:enyo/Collection~Collection} that is not the owner of this @@ -267,42 +267,42 @@ var Filter = module.exports = kind( * As long as we are consistent about applying the same action against ourselves, * we should remain in sync and propagate the same event again, except that * `sort` will end up being a `reset`. - * + * * @private */ _collectionEvent: function (sender, e, props) { // we are listening for particular events to signal that we should update according // to its changes if we are a nested filter - + var models = props.models, internal = this._internal; - + switch (e) { case 'add': - + // will ensure an add gets propagated if the models are new internal.add(models, {merge: false}); break; case 'reset': case 'sort': - + // will ensure a reset gets propagated internal.empty(models); break; case 'remove': - + // will ensure a remove gets propagated (assuming something is removed) internal.remove(models); break; case 'change': - + // we need to propagate the change event as our internal collection's own so that // child filters and/or subclasses will be able to handle this as they need to internal.emit(e, props); break; } }, - + /** * When the collection is destroyed we can't use it anymore so we need to remove it as our * collection to prevent weird things from happening. @@ -312,7 +312,7 @@ var Filter = module.exports = kind( _collectionDestroyed: function () { this.set('collection', null); }, - + /** * To be implemented by [subkind]{@glossary subkind}; for internal use only. * @@ -320,7 +320,7 @@ var Filter = module.exports = kind( * @private */ _internalEvent: utils.nop, - + /** * To be implemented by [subkind]{@glossary subkind}; for internal use only. * @@ -328,7 +328,7 @@ var Filter = module.exports = kind( * @private */ _ownerEvent: utils.nop, - + /** * Unavailable on {@link module:enyo/Filter~Filter} and [subkinds]{@glossary subkind}. * @@ -336,7 +336,7 @@ var Filter = module.exports = kind( * @public */ add: utils.nop, - + /** * Unavailable on {@link module:enyo/Filter~Filter} and [subkinds]{@glossary subkind}. * @@ -344,7 +344,7 @@ var Filter = module.exports = kind( * @public */ remove: utils.nop, - + /** * Unavailable on {@link module:enyo/Filter~Filter} and [subkinds]{@glossary subkind}. * @@ -352,7 +352,7 @@ var Filter = module.exports = kind( * @public */ fetch: utils.nop, - + /** * Unavailable on {@link module:enyo/Filter~Filter} and [subkinds]{@glossary subkind}. * @@ -360,7 +360,7 @@ var Filter = module.exports = kind( * @public */ sort: utils.nop, - + /** * Unavailable on {@link module:enyo/Filter~Filter} and [subkinds]{@glossary subkind}. * @@ -368,7 +368,7 @@ var Filter = module.exports = kind( * @public */ commit: utils.nop, - + /** * Overloaded implementation. * @@ -381,7 +381,7 @@ var Filter = module.exports = kind( return this.models ? sup.apply(this.models.at ? this.models : this, arguments) : undefined; }; }), - + /** * Unavailable on {@link module:enyo/Filter~Filter} and [subkinds]{@glossary subkind}. * @@ -389,7 +389,7 @@ var Filter = module.exports = kind( * @public */ raw: utils.nop, - + /** * Unavailable on {@link module:enyo/Filter~Filter} and [subkinds]{@glossary subkind}. * @@ -397,7 +397,7 @@ var Filter = module.exports = kind( * @public */ toJSON: utils.nop, - + /** * Overloaded implementation. * @@ -410,7 +410,7 @@ var Filter = module.exports = kind( return this.models ? sup.apply(this, arguments) : false; }; }), - + /** * Overloaded implementation. * @@ -423,7 +423,7 @@ var Filter = module.exports = kind( return this.models ? sup.apply(this, arguments) : undefined; }; }), - + /** * Overloaded implementation. * @@ -436,7 +436,7 @@ var Filter = module.exports = kind( return this.models ? sup.apply(this, arguments) : []; }; }), - + /** * Overloaded implementation. * @@ -449,7 +449,7 @@ var Filter = module.exports = kind( return this.models ? sup.apply(this, arguments) : undefined; }; }), - + /** * Overloaded implementation. * @@ -462,7 +462,7 @@ var Filter = module.exports = kind( return this.models ? sup.apply(this, arguments) : []; }; }), - + /** * Overloaded implementation. * @@ -475,7 +475,7 @@ var Filter = module.exports = kind( return this.models ? sup.apply(this, arguments) : -1; }; }), - + /** * Unavailable on {@link module:enyo/Filter~Filter} and [subkinds]{@glossary subkind}. * diff --git a/src/FluxDispatcher.js b/src/FluxDispatcher.js index e56aac955..723eea776 100644 --- a/src/FluxDispatcher.js +++ b/src/FluxDispatcher.js @@ -1,5 +1,6 @@ /** * Returns the FluxDispatcher singleton. +* @wip * @module enyo/FluxDispatcher */ diff --git a/src/FluxStore.js b/src/FluxStore.js index 57ac44915..9ee1bee57 100644 --- a/src/FluxStore.js +++ b/src/FluxStore.js @@ -1,5 +1,6 @@ /** * Contains the declaration for the {@link module:enyo/FluxStore~FluxStore} kind. +* @wip * @module enyo/FluxStore */ @@ -50,13 +51,6 @@ module.exports = kind( */ kind: CoreObject, - - /** - * @private - */ - data: {}, - - /** * How a store is identitified to the Flux Dispatcher. This ID is used for * subscribing to a store's state notification change. @@ -67,6 +61,9 @@ module.exports = kind( */ id: -1, + /** + * @private + */ mixins: [EventEmitter, StateSupport], /** @@ -78,6 +75,10 @@ module.exports = kind( */ source: '', + /** + * @private + * @lends module:enyo/FluxStore~FluxStore.prototype + */ published: { /** @@ -98,6 +99,7 @@ module.exports = kind( constructor: kind.inherit(function (sup) { return function () { sup.apply(this, arguments); + this.reset(); //id the store with the dispatcher this.id = FluxDispatcher.subscribe(); diff --git a/src/Group/Group.js b/src/Group/Group.js index eabd555e3..ec72b5cd5 100644 --- a/src/Group/Group.js +++ b/src/Group/Group.js @@ -14,7 +14,7 @@ var * The extended {@glossary event} [object]{@glossary Object} that is provided when the * [onActiveChanged]{@link module:enyo/Group~Group#onActiveChanged} event is fired. * -* @typedef {Object} enyo.Group~ActiveChangedEvent +* @typedef {Object} module:enyo/Group~Group~ActiveChangedEvent * @property {module:enyo/Control~Control} active - The active [control]{@link module:enyo/Control~Control} for the * [group]{@link module:enyo/Group~Group}. */ diff --git a/src/History.js b/src/History.js index eaaeb9890..433ed2688 100644 --- a/src/History.js +++ b/src/History.js @@ -227,7 +227,7 @@ var EnyoHistory = module.exports = kind.singleton( _processing = true; if (_queue.length) { this.processQueue(); - } else { + } else if (_history.length) { this.processPopEntry(_history.pop()); } _processing = false; @@ -394,7 +394,7 @@ var EnyoHistory = module.exports = kind.singleton( * @private */ handlePop: function (event) { - if (this.enabled && _history.length) { + if (this.enabled) { this.processState(event.state); } }, diff --git a/src/Input/Input.js b/src/Input/Input.js index a450ca098..4232514fd 100644 --- a/src/Input/Input.js +++ b/src/Input/Input.js @@ -198,14 +198,7 @@ module.exports = kind( rendered: kind.inherit(function (sup) { return function() { sup.apply(this, arguments); - dispatcher.makeBubble(this, 'focus', 'blur'); - - //Force onchange event to be bubbled inside Enyo for IE8 - if(platform.ie == 8){ - this.setAttribute('onchange', dispatcher.bubbler); - } - this.disabledChanged(); if (this.defaultFocus) { this.focus(); @@ -264,8 +257,8 @@ module.exports = kind( */ iekeyup: function (sender, e) { var ie = platform.ie, kc = e.keyCode; - // input event missing on ie 8, fails to fire on backspace and delete keys in ie 9 - if (ie <= 8 || (ie == 9 && (kc == 8 || kc == 46))) { + // input event fails to fire on backspace and delete keys in ie 9 + if (ie == 9 && (kc == 8 || kc == 46)) { this.bubble('oninput', e); } }, diff --git a/src/Jsonp.js b/src/Jsonp.js index 83d0f1840..9abd03e9c 100644 --- a/src/Jsonp.js +++ b/src/Jsonp.js @@ -38,11 +38,11 @@ var JsonpRequest = module.exports = kind( * @private */ published: { + /** @lends module:enyo/Jsonp~JsonpRequest.prototype */ /** * The URL for the service. * - * @memberof enyo.JsonpRequest.prototype * @type {String} * @default '' * @public @@ -52,7 +52,6 @@ var JsonpRequest = module.exports = kind( /** * The optional character set to use to interpret the return data. * - * @memberof enyo.JsonpRequest.prototype * @type {String} * @default null * @public @@ -65,7 +64,6 @@ var JsonpRequest = module.exports = kind( * This may also be set to `null` in some cases. * * @see module:enyo/Jsonp~JsonpRequest.overrideCallback - * @memberof enyo.JsonpRequest.prototype * @type {String} * @default 'callback' * @public @@ -77,7 +75,6 @@ var JsonpRequest = module.exports = kind( * to (attempt to) force a new fetch of the resource instead of reusing a * local cache. * - * @memberof enyo.JsonpRequest.prototype * @type {Boolean} * @default true * @public @@ -92,7 +89,6 @@ var JsonpRequest = module.exports = kind( * name and only one [JsonpRequest]{@link module:enyo/Jsonp~JsonpRequest} using this property may * be active at a time. * - * @memberof enyo.JsonpRequest.prototype * @type {String} * @default null * @public diff --git a/src/LightPanels/LightPanel.js b/src/LightPanels/LightPanel.js index fe334da90..9af7cffe5 100644 --- a/src/LightPanels/LightPanel.js +++ b/src/LightPanels/LightPanel.js @@ -1,5 +1,10 @@ /** * Contains the declaration for the {@link module:enyo/LightPanels~LightPanel} kind. +* +* Note: LightPanel is exported from {@link module:enyo/LightPanels}. This module should not be +* required directly. +* @wip +* @ignore * @module enyo/LightPanels */ @@ -28,8 +33,10 @@ var States = { * between child components. * * @class LightPanel +* @name module:enyo/LightPanels~LightPanel * @extends module:enyo/Control~Control * @ui +* @wip * @public */ module.exports = kind( @@ -70,4 +77,4 @@ module.exports = kind( }); -module.exports.States = States; \ No newline at end of file +module.exports.States = States; diff --git a/src/LightPanels/LightPanels.js b/src/LightPanels/LightPanels.js index f0ce6b1a7..758ab1f7c 100644 --- a/src/LightPanels/LightPanels.js +++ b/src/LightPanels/LightPanels.js @@ -2,6 +2,7 @@ require('enyo'); /** * Contains the declaration for the {@link module:enyo/LightPanels~LightPanels} kind. +* @wip * @module enyo/LightPanels */ @@ -68,6 +69,7 @@ var Orientation = { * @extends module:enyo/Control~Control * @mixes module:enyo/TaskManagerSupport~TaskManagerSupport * @mixes module:enyo/ViewPreloadSupport~ViewPreloadSupport +* @wip * @ui * @public */ @@ -132,13 +134,13 @@ module.exports = kind( wrap: false, /** - * When `true`, previous panels are automatically popped when moving backwards. + * When `true`, previous panels are destroyed when moving backwards. * * @type {Boolean} - * @default true + * @default false * @public */ - popOnBack: true, + popOnBack: false, /** * The amount of time, in milliseconds, to run the transition animation between panels. @@ -190,6 +192,11 @@ module.exports = kind( */ reverseForRtl: false, + /** + * @private + */ + transitioning: false, + /** * @private */ @@ -324,6 +331,16 @@ module.exports = kind( return (this._panels = this._panels || (this.controlParent || this).children); }, + /** + * Whether or not we are currently in the midst of a panel transition. + * + * @return {Boolean} If `true`, we are currently transitioning; otherwise, `false`. + * @public + */ + isTransitioning: function () { + return this.transitioning; + }, + /* @@ -336,13 +353,15 @@ module.exports = kind( * Animates to the specified panel index. * * @param {Number} index - The index of the panel we wish to animate a transition to. + * @return {Boolean} If `true`, the index to animate to was successful; `false` otherwise (i.e. + * if the index is invalid, or another animation is already in progress). * @public */ animateTo: function (index) { var from = this.index; this.index = index; this.notifyObservers('index'); - this.setupTransitions(from, true); + return this.setupTransitions(from, true); }, /** @@ -390,7 +409,8 @@ module.exports = kind( * @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. + * @return {Object|undefined} The instance of the panel that was created; if + * `undefined`, the panel could not be pushed (i.e. we are currently transitioning). * @public */ pushPanel: function (info, moreInfo, opts) { @@ -402,7 +422,7 @@ module.exports = kind( var lastIndex = this.getPanels().length - 1, nextPanel = this.createPanel(info, moreInfo), - newIndex = lastIndex + 1; + targetIndex = (opts && opts.targetIndex != null) ? opts.targetIndex : lastIndex + 1; if (this.cacheViews) { this.pruneQueue([info]); } @@ -413,8 +433,8 @@ module.exports = kind( nextPanel.postTransition(); } - if (!this.animate || (opts && opts.direct)) this.set('index', newIndex, {force: opts && opts.force}); - else this.animateTo(newIndex); + if (!this.animate || (opts && opts.direct)) this.set('index', targetIndex, {force: opts && opts.force}); + else this.animateTo(targetIndex); // 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. @@ -429,12 +449,12 @@ module.exports = kind( * @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. + * @return {Object[]|undefined} Array of the panels that were created on top of the stack; if + * `undefined`, the panels could not be pushed (i.e. we are currently transitioning). * @public */ pushPanels: function (info, moreInfo, opts) { - if (this.transitioning) return true; + if (this.transitioning) return; if (opts && opts.purge) { this.purge(); @@ -469,51 +489,6 @@ module.exports = kind( 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). @@ -524,25 +499,32 @@ module.exports = kind( * @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). + * @param {module:enyo/LightPanels~PushPanelOptions} opts - Additional options to be used when + * pushing multiple panels. Note that for the case of "targetIndex", if this is not specified, + * then the default behavior is to display the first replacement panel. + * @return {Object|Object[]|undefined} The panel or array of the panels that were pushed; if + * `undefined`, the replacement could not be processed (i.e. we are currently transitioning). * @public */ - replaceAt: function (start, count, info) { - var panels = this.getPanels(), - insertBefore, commonInfo, end, idx; + replaceAt: function (start, count, info, opts) { + var panels, panelsToPop, insertBefore, commonInfo, end, panelOpts, targetIndex; + + if (this.transitioning) return; + panels = this.getPanels(); start = start < 0 ? panels.length + start : start; end = start + count; insertBefore = panels[end]; commonInfo = {addBefore: insertBefore}; + targetIndex = opts && opts.targetIndex; + panelOpts = {direct: true, force: true, targetIndex: targetIndex != null ? targetIndex : start}; - // remove existing panels - for (idx = end - 1; idx >= start; idx--) { - this.removePanel(panels[idx]); - } + panelsToPop = panels.splice(start, end - start); + this.popQueue = (this.popQueue && this.popQueue.concat(panelsToPop)) || panelsToPop; // add replacement panels - if (utils.isArray(info)) this.pushPanels(info, commonInfo, {direct: true, force: true}); - else this.pushPanel(info, commonInfo, {direct: true, force: true}); + if (utils.isArray(info)) return this.pushPanels(info, commonInfo, panelOpts); + else return this.pushPanel(info, commonInfo, panelOpts); }, @@ -652,13 +634,7 @@ module.exports = kind( 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(); - }); - } + panel.postTransition && panel.postTransition(); } }, @@ -667,31 +643,40 @@ module.exports = kind( * * @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) { + transitionFinished: function (sender, ev) { var prevPanel, currPanel; - if (this.transitioning && ((ev && ev.originator === this.$.client) || direct)) { + if (this.transitioning && (!ev || ev.originator === this.$.client)) { + if (this._fallbackTimeout) { + global.clearTimeout(this._fallbackTimeout); + this._fallbackTimeout = null; + } + 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; + + utils.asyncMethod(this, function () { + this.cleanUpPanel(prevPanel); + this.cleanUpPanel(currPanel); + + if (this.popQueue && this.popQueue.length) { + this.finalizePurge(); + } else 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); + } + }); } }, @@ -721,25 +706,29 @@ module.exports = kind( * @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. + * @return {Boolean} If `true`, the transition was setup successfully, otherwise it was not (i.e. + * we are currently transitioning or there is no "next" panel to transition to). * @private */ setupTransitions: function (previousIndex, animate) { - var panels = this.getPanels(), - nextPanel = panels[this.index], - currPanel = this._currentPanel, - shiftCurrent, fnInitiateTransition; + var panels, nextPanel, currPanel, shiftCurrent, fnInitiateTransition; - this._indexDirection = 0; + if (this.transitioning) return; - // 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; + panels = this.getPanels(); + nextPanel = panels[this.index]; + currPanel = this._currentPanel; if (nextPanel) { this.transitioning = true; + 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; // prepare the panel that will be deactivated if (currPanel) { @@ -770,6 +759,8 @@ module.exports = kind( if (!this.generated || !animate) fnInitiateTransition(); else animation.requestAnimationFrame(fnInitiateTransition); + + return true; } }, @@ -789,7 +780,8 @@ module.exports = kind( 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); + if (!this.shouldAnimate() || !animate) this.transitionFinished(); + else this.setupFallback(); }, /** @@ -813,15 +805,76 @@ module.exports = kind( dom.transformValue(container, 'translate' + this.orientation, value + '%'); }, + /** + * Removes panels whose index is either greater than, or less than, the specified value, + * depending on the direction. + * + * @param {Number} index - Index at which to start 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. + * @private + */ + removePanels: function (index, direction) { + var panels = this.getPanels(), + i; + + if (direction < 0) { + for (i = panels.length - 1; i > index; i--) { + this.removePanel(panels[i], this.cacheViews && !this.popOnBack); + } + } else { + for (i = 0; i < index; i++) { + this.removePanel(panels[i], this.cacheViews, true); + } + } + }, + + /** + * Removes the specified panel. + * + * @param {Object} panel - The panel to remove. + * @param {Boolean} cache - Whether or not the panel should be cached; if `false` or not + * specified, the panel will instead be destroyed (this is the default behavior). + * @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, cache, preserve) { + if (panel) { + if (cache) { + this.cacheView(panel, preserve); + } else { + if (this.cacheViews) { + this.popView(this.getViewId(panel)); + } + panel.destroy(); + } + } + }, + /** * Destroys all panels. * * @private */ purge: function () { - var panels = this.getPanels(), - panel; + var panels = this.getPanels(); + this.popQueue = panels.slice(); + panels.length = 0; + this.index = -1; + }, + /** + * Clean-up any panels queued for destruction. + * + * @private + */ + finalizePurge: function () { + var panels = this.popQueue, + panel; while (panels.length) { panel = panels.pop(); if (this.cacheViews) { @@ -831,8 +884,6 @@ module.exports = kind( panel.destroy(); } } - - this.index = -1; }, /** @@ -846,9 +897,20 @@ module.exports = kind( for (var idx = 0; idx < viewProps.length; idx++) { this.removeTask(this.getViewId(viewProps[idx])); } + }, + + /** + * @private + */ + setupFallback: function () { + this._fallbackTimeout = global.setTimeout(this.bindSafely('transitionFinished'), this.duration); } }); +/** +* The {@link module:enyo/LightPanels~LightPanel} kind export. +* @public +*/ module.exports.Panel = LightPanel; module.exports.Direction = Direction; module.exports.Orientation = Orientation; diff --git a/src/LinkedList.js b/src/LinkedList.js index 4bddf681c..46651cfe4 100644 --- a/src/LinkedList.js +++ b/src/LinkedList.js @@ -19,37 +19,32 @@ var */ 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 */ @@ -60,9 +55,9 @@ module.exports = kind( } this.head = null; this.tail = null; - this.length = 0; + this.length = 0; }, - + /** * @private */ @@ -70,20 +65,20 @@ module.exports = kind( 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 */ @@ -91,67 +86,67 @@ module.exports = kind( 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 */ @@ -167,7 +162,7 @@ module.exports = kind( // if no node qualified it returns false return false; }, - + /** * @private */ @@ -183,7 +178,7 @@ module.exports = kind( // returns the last node (if any) that was processed in the chain return node; }, - + /** * @private */ @@ -199,7 +194,7 @@ module.exports = kind( // returns the last node (if any) that was processed in the chain return node; }, - + /** * @private */ diff --git a/src/Loop.js b/src/Loop.js index 5dd7ba374..efdc05147 100644 --- a/src/Loop.js +++ b/src/Loop.js @@ -2,13 +2,15 @@ require('enyo'); /** * This module returns the Loop singleton +* @wip * @module enyo/Loop */ var kind = require('./kind'); var - CoreObject = require('./CoreObject'); + CoreObject = require('./CoreObject'), + animation = require('./animation'); module.exports = kind.singleton({ /** @lends module:enyo/Loop */ @@ -43,7 +45,7 @@ module.exports = kind.singleton({ * @private */ trigger: function () { - global.requestAnimationFrame(this.lcb || this.initLoopCallback()); + animation.requestAnimationFrame(this.lcb || this.initLoopCallback()); }, /** * @private diff --git a/src/Media.js b/src/Media.js index 745a63b08..9bf329fc5 100644 --- a/src/Media.js +++ b/src/Media.js @@ -25,7 +25,7 @@ var * propagated the {@glossary event}. * @property {Object} event - An [object]{@glossary Object} containing event information. * @public -*/ +*/ /** * Fires when element can resume playback of the [media]{@link module:enyo/Media~Media} data, but may @@ -45,7 +45,7 @@ var * * @event module:enyo/Media~Media#onCanPlayThrough * @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 @@ -357,17 +357,17 @@ module.exports = kind( * @private */ kind: Control, - + /** * @private */ - published: + published: /** @lends module:enyo/Media~Media.prototype */ { - + /** * URL of the [media]{@link module:enyo/Media~Media} file to play; may be relative to the * application HTML file. - * + * * @type {String} * @default '' * @public @@ -376,7 +376,7 @@ module.exports = kind( /** * If `true`, [media]{@link module:enyo/Media~Media} will automatically start playback when loaded. - * + * * @type {Boolean} * @default false * @public @@ -385,7 +385,7 @@ module.exports = kind( /** * The desired speed at which the [media]{@link module:enyo/Media~Media} resource is to play. - * + * * @type {Number} * @default 1.0 * @public @@ -394,7 +394,7 @@ module.exports = kind( /** * The amount of time, in seconds, to jump forward or backward. - * + * * @type {Number} * @default 30 * @public @@ -403,7 +403,7 @@ module.exports = kind( /** * The effective playback rate. - * + * * @type {Number} * @default 1.0 * @public @@ -418,7 +418,7 @@ module.exports = kind( * slowRewind: ['-1/2', '-1'] * } * ``` - * + * * @type {Object} * @default { * fastForward: ['2', '4', '8', '16'], @@ -438,7 +438,7 @@ module.exports = kind( /** * Indicates how data should be preloaded, reflecting the `preload` HTML attribute. * Will be one of `'none'` (the default), `'metadata'`, or `'auto'`. - * + * * @type {String} * @default 'none' * @public @@ -447,7 +447,7 @@ module.exports = kind( /** * If `true`, [media]{@link module:enyo/Media~Media} playback restarts from beginning when finished. - * + * * @type {Boolean} * @default false * @public @@ -456,7 +456,7 @@ module.exports = kind( /** * If `true`, [media]{@link module:enyo/Media~Media} playback is muted. - * + * * @type {Boolean} * @default false * @public @@ -465,7 +465,7 @@ module.exports = kind( /** * If `true`, default [media]{@link module:enyo/Media~Media} controls are shown. - * + * * @type {Boolean} * @default false * @public @@ -474,7 +474,7 @@ module.exports = kind( /** * Current playback volume, as a number in the range from 0.0 to 1.0. - * + * * @type {Number} * @default 1.0 * @public @@ -515,7 +515,7 @@ module.exports = kind( onJumpBackward: '', onStart: '' }, - + /** * @private */ @@ -732,7 +732,7 @@ module.exports = kind( _canPlayThrough: function () { this.doCanPlayThrough(); }, - + /** * Called when the [duration]{@link module:enyo/Media~Media#duration} attribute has been changed. * @@ -766,7 +766,7 @@ module.exports = kind( /** * Called when an error occurs while fetching media data. - * + * * @private */ _error: function () { @@ -784,7 +784,7 @@ module.exports = kind( }, /** * Called when the media duration and dimensions of the media resource/text tracks are ready. - * + * * @private */ _loadedMetaData: function () { @@ -856,7 +856,6 @@ module.exports = kind( this.doStart(); } return { - srcElement: node, duration: node.duration, currentTime: node.currentTime, playbackRate: this.getPlaybackRate() @@ -876,7 +875,7 @@ module.exports = kind( return (pbArray.length > 1) ? parseInt(pbArray[0], 10) / parseInt(pbArray[1], 10) : parseInt(rate, 10); }, /** - * Called when either [defaultPlaybackRate]{@link module:enyo/Media~Media#defaultPlaybackRate} or + * Called when either [defaultPlaybackRate]{@link module:enyo/Media~Media#defaultPlaybackRate} or * [playbackRate]{@link module:enyo/Media~Media#playbackRate} has been updated. * * @fires module:enyo/Media~Media#onSlowforward @@ -945,7 +944,7 @@ module.exports = kind( /** * Called when the media controller position has changed. - * + * * @private */ _timeUpdate: function (sender, e) { @@ -1022,7 +1021,7 @@ module.exports = kind( * Retrieves the ranges of the [media]{@link module:enyo/Media~Media} [source]{@link module:enyo/Media~Media#src} * that have been buffered. * - * @returns {TimeRanges} The ranges of the [media]{@link module:enyo/Media~Media} + * @returns {TimeRanges} The ranges of the [media]{@link module:enyo/Media~Media} * [source]{@link module:enyo/Media~Media#src} that have been buffered. * @public */ @@ -1059,7 +1058,7 @@ module.exports = kind( return 0; }, - /** + /** * Determines whether the [media]{@link module:enyo/Media~Media} element is paused. * * @returns {Boolean} `true` if the [media]{@link module:enyo/Media~Media} is paused; @@ -1072,11 +1071,11 @@ module.exports = kind( } }, - /** - * Retrieves the ranges of the [media]{@link module:enyo/Media~Media} [source]{@link module:enyo/Media~Media#src} + /** + * Retrieves the ranges of the [media]{@link module:enyo/Media~Media} [source]{@link module:enyo/Media~Media#src} * that have been played, if any. * - * @returns {TimeRanges} The ranges of the [media]{@link module:enyo/Media~Media} + * @returns {TimeRanges} The ranges of the [media]{@link module:enyo/Media~Media} * [source]{@link module:enyo/Media~Media#src} that have been played. * @public */ @@ -1086,7 +1085,7 @@ module.exports = kind( } }, - /** + /** * Determines the [readiness]{@glossary readyState} of the [media]{@link module:enyo/Media~Media}. * * @returns {ReadyState} The [readiness]{@glossary readyState} state. @@ -1098,7 +1097,7 @@ module.exports = kind( } }, - /** + /** * Retrieves the ranges of the [media]{@link module:enyo/Media~Media} [source]{@link module:enyo/Media~Media#src} * that the user may seek to, if any. * @@ -1112,7 +1111,7 @@ module.exports = kind( } }, - /** + /** * Sets current player position in the [media]{@link module:enyo/Media~Media} element. * * @param {Number} time - The player position, in seconds. @@ -1124,7 +1123,7 @@ module.exports = kind( } }, - /** + /** * Implements custom rewind functionality (until browsers support negative playback rate). * * @public @@ -1136,7 +1135,7 @@ module.exports = kind( /** * Calculates the time that has elapsed since. - * + * * @private */ _rewind: function () { @@ -1149,7 +1148,7 @@ module.exports = kind( this.startRewindJob(); }, - /** + /** * Starts rewind job. * * @public @@ -1159,7 +1158,7 @@ module.exports = kind( Job(this.id + 'rewind', this.bindSafely('_rewind'), 100); }, - /** + /** * Stops rewind job. * * @public @@ -1168,7 +1167,7 @@ module.exports = kind( Job.stop(this.id + 'rewind'); }, - /** + /** * Determines whether the [media]{@link module:enyo/Media~Media} is currently seeking to a new position. * * @returns {Boolean} `true` if currently seeking; otherwise, `false`. @@ -1180,7 +1179,7 @@ module.exports = kind( } }, - /** + /** * Determines whether the [media]{@link module:enyo/Media~Media} is currently in a paused state. * * @returns {Boolean} `true` if paused; otherwise, `false`. @@ -1190,7 +1189,7 @@ module.exports = kind( return this.hasNode() ? this.hasNode().paused : true; }, - /** + /** * Fast forwards the [media]{@link module:enyo/Media~Media}, taking into account the current * playback state. * @@ -1248,7 +1247,7 @@ module.exports = kind( }, - /** + /** * Rewinds the [media]{@link module:enyo/Media~Media}, taking into account the current * playback state. * @@ -1322,7 +1321,7 @@ module.exports = kind( } }, - /** + /** * Jumps backward by an amount specified by the [jumpSec]{@link module:enyo/Media~Media#jumpSec} * property. * @@ -1343,7 +1342,7 @@ module.exports = kind( this.doJumpBackward(utils.mixin(this.createEventData(), {jumpSize: this.jumpSec})); }, - /** + /** * Jumps forward by an amount specified by the [jumpSec]{@link module:enyo/Media~Media#jumpSec} * property. * @@ -1364,7 +1363,7 @@ module.exports = kind( this.doJumpForward(utils.mixin(this.createEventData(), {jumpSize: this.jumpSec})); }, - /** + /** * Jumps to the beginning of the [media]{@link module:enyo/Media~Media} content. * * @public @@ -1382,7 +1381,7 @@ module.exports = kind( this._prevCommand = 'jumpToStart'; }, - /** + /** * Jumps to the end of the [media]{@link module:enyo/Media~Media} content. * * @public diff --git a/src/ModelController.js b/src/ModelController.js index 06a375eb5..fb7bce9b9 100644 --- a/src/ModelController.js +++ b/src/ModelController.js @@ -34,18 +34,18 @@ var BaseModelController = kind({ * [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 * note that bindings should **never** bind through the controller to the model * directly. -* +* * **Rules of property resolution** * * If the controller can call [hasOwnProperty()]{@glossary Object.hasOwnProperty} * and it returns `true`, it will look locally; if the property is resolved to * be a computed property, the requested property will be proxied from the given * model, when available. -* +* * @class ModelController * @extends module:enyo/Component~Component * @mixes module:enyo/ComputedSupport~ComputedSupport @@ -55,18 +55,18 @@ var BaseModelController = kind({ */ module.exports = kind( /** @lends module:enyo/ModelController~ModelController.prototype */ { - + name: 'enyo.ModelController', - + /** * @private */ kind: BaseModelController, - + /** - * The {@link module:enyo/Model~Model} to proxy. If this is set to an instance of `enyo.Model`, - * the [controller]{@link module:enyo/ModelController~ModelController} will propagate `enyo.Model` - * [events]{@glossary event} and [notifications]{@link module:enyo/ObserverSupport~ObserverSupport.notify}. + * The {@link module:enyo/Model~Model} to proxy. If this is set to an instance of `enyo/Model`, + * the [controller]{@link module:enyo/ModelController~ModelController} will propagate `enyo/Model` + * [events]{@glossary event} and [notifications]{@link module:enyo/ObserverSupport~ObserverSupport#notify}. * **No bindings should ever bind directly to attributes of this property.** * * Also note that this is a reserved property name and will collide with any @@ -78,52 +78,51 @@ module.exports = kind( * @public */ model: null, - + /** * @private */ proxyObjectKey: 'model', - + /** * @method * @private */ get: kind.inherit(function (sup) { return function (path) { - + if (this.hasOwnProperty(path) || this.isComputed(path)) { return this._getComputed(path); } - + return sup.apply(this, arguments); }; }), - + /** * @method * @private */ set: kind.inherit(function (sup) { return function (path) { - + if (typeof path == 'string') { if (this.hasOwnProperty(path)) { return this.isComputed(path) ? this : utils.setPath.apply(this, arguments); } } - + return sup.apply(this, arguments); }; }), - + /** * @method * @private */ _getComputed: ComputedSupport.get.fn(CoreObject.prototype.get), - + /** - * @type enyo.ObserverSupport~Observer * @private */ modelChanged: function (was, is, path) { @@ -131,7 +130,7 @@ module.exports = kind( if (was) was.off('*', this._modelEvent, this); // register for events on new model if any if (is) is.on('*', this._modelEvent, this); - + // either way we need to update any observers that might be related // to the model var props = this.modelObservedProperties(); @@ -140,10 +139,10 @@ module.exports = kind( this.notify(key, was && was.get(key), is && is.get(key)); } } - + this.emit('model', {was: was, is: is}); }, - + /** * @method * @private @@ -153,7 +152,7 @@ module.exports = kind( // will be this controller but all listeners should expect to use the third parameter as // is the convention for model listeners this.emit(e, props, model); - + switch (e) { case 'change': if (props) for (var key in props) this.notify(key, model.previous[key], props[key]); @@ -163,7 +162,7 @@ module.exports = kind( break; } }, - + /** * @method * @private @@ -171,7 +170,7 @@ module.exports = kind( modelObservedProperties: function () { return this._observedProps || (this._observedProps = {}); }, - + /** * @method * @private @@ -180,24 +179,24 @@ module.exports = kind( return function (path) { var part = path , parts; - + if (path.indexOf('.') > -1) { parts = path.split('.'); part = parts.shift(); } - + if (!this.hasOwnProperty(part) && !this.isComputed(part)) this.modelObservedProperties()[path] = null; return sup.apply(this, arguments); }; }), - + /** * @private */ addObserver: function () { return this.observe.apply(this, arguments); }, - + /** * @private */ @@ -205,13 +204,13 @@ module.exports = kind( return function (props) { // ensure we have our own model property this.model = null; - + // adhere to normal approach to constructor properties hash props && utils.mixin(this, props); sup.apply(this, arguments); }; }), - + /** * @method * @private @@ -222,5 +221,5 @@ module.exports = kind( this.model && this.model.off('*', this._modelEvent, this); }; }) - + }); diff --git a/src/NewAnimator.js b/src/NewAnimator.js index 139cb7e51..ab94de1b3 100644 --- a/src/NewAnimator.js +++ b/src/NewAnimator.js @@ -1,3 +1,10 @@ +/** +* Contains the declaration for the {@link module:enyo/NewAnimator~NewAnimator} kind. +* @wip +* @private +* @module enyo/NewAnimator +*/ + require('enyo'); var @@ -8,7 +15,16 @@ var CoreObject = require('./CoreObject'), Loop = require('./Loop'); -module.exports = kind.singleton({ +/** +* {@link module:enyo/NewAnimator~NewAnimator} is a work-in-progress +* +* @class NewAnimator +* @extends module:enyo/CoreObject~Object +* @wip +* @private +*/ +module.exports = kind.singleton( + /** @lends module:enyo/NewAnimator~NewAnimator.prototype */ { name: 'enyo.NewAnimator', kind: CoreObject, animate: function(fn, duration) { diff --git a/src/NewDataList.js b/src/NewDataList.js index eb44b0767..435934758 100644 --- a/src/NewDataList.js +++ b/src/NewDataList.js @@ -1,3 +1,11 @@ +/** +* Contains the declaration for the {@link module:enyo/NewDataList~NewDataList} kind. +* +* @wip +* @public +* @module enyo/NewDataList +*/ + require('enyo'); var @@ -8,19 +16,135 @@ var Scrollable = require('./Scrollable'), VirtualDataRepeater = require('./VirtualDataRepeater'); -module.exports = kind({ +/** +* {@link module:enyo/NewDataList~NewDataList} is new virtual list implementation. +* +* It is intended to replace the older {@link module:enyo/DataList~DataList}, +* {@link module:enyo/DataGridList~DataGridList} and probably +* {@link module:layout/List~List}, but as of the Enyo 2.7 release is a work in +* progress and currently lacks many of the features of those older implementations. +* +* The most significant difference between `NewDataList` and `DataList` / `DataGridList` +* is that `NewDataList` "virtualizes" items one at a time, not a page at a time. This +* approach performs somewhat better in general and most notably disributes the cost of +* virtualization over time, which produces smoother frame rates. +* +* `NewDataList` supports both linear and grid layouts, in either horizontal or +* vertical direction. +* +* Notes: +* * List items must be styled with `position: absolute` to be laid out properly, +* but `NewDataList` does not currently provide any default style rules for items, +* so you need to specify `position: absolute` in your items' classes or style attributes. +* * `NewDataList` currently supports only explicitly sized items; neither variable-size +* items nor "naturally" sized items are currently supported. +* +* @class NewDataList +* @extends module:enyo/VirtualDataRepeater~VirtualDataRepeater +* @wip +* @ui +* @public +*/ +module.exports = kind( + /** @lends module:enyo/NewDataList~NewDataList.prototype */ { name: 'enyo.NewDataList', kind: VirtualDataRepeater, + /** + * The direction of the layout, which may be either `'vertical'` + * or `'horizontal'`. + * + * @type {String} + * @default 'vertical' + * @public + */ direction: 'vertical', + /** + * The height of each list item, in pixels. + * + * Required for grid layouts and linear vertical layouts; may be + * omitted for linear horizontal layouts. + * + * @type {Number} + * @default 100 + * @public + */ itemHeight: 100, + /** + * The width of each list item, in pixels. + * + * Required for grid layouts and linear horizontal layouts; may be + * omitted for linear vertical layouts. + * + * @type {Number} + * @default 100 + * @public + */ itemWidth: 100, + /** + * The space between list items, in pixels. + * + * @type {Number} + * @default 0 + * @public + */ spacing: 0, + /** + * The number of rows (only applies to horizontally scrolling grid layouts). + * + * To specify a horizontally scrolling grid layout, set `rows` to `2` or more + * and `direction` to `horizontal`. + * + * @type {Number} + * @default 'auto' + * @public + */ rows: 'auto', + /** + * The number of columns (only applies to vertically scrolling grid layouts). + * + * To specify a vertically scrolling grid layout, set `columns` to `2` or more + * and `direction` to `vertical`. + * + * @type {Number} + * @default 'auto' + * @public + */ columns: 'auto', + /** + * This number determines how many "extra" items the list will generate, beyond + * the number required to fill the list's viewport. Higher numbers result in more + * extra items being generated. + * + * You should generally not need to adjust this value. + * + * @type {Number} + * @default 3 + * @public + */ overhang: 3, - // Experimental + /** + * This feature is experimental, and only partly functional. + * + * When `scrollToBoundaries` is set to `true`, the list will come to rest on an + * item boundary, such that the first visible item is fully within the list's + * viewport, not partially outside. + * + * Important limitation: this feature currently only works when scrolling in + * response to wheel events or when scrolling to explicitly provided coordinates; + * it does not work when scrolling in response to touch or mouse events. + * + * @type {Boolean} + * @default false + * @public + */ scrollToBoundaries: false, + /** + * @private + */ mixins: [Scrollable], + /** + * @private + */ observers: [ {method: 'reset', path: [ 'direction', 'columns', 'rows', @@ -151,6 +275,7 @@ module.exports = kind({ // as long as calculateMetrics() is called only by reset(). this.numItems = num; }, + /** * @private */ @@ -194,6 +319,34 @@ module.exports = kind({ } this.positionChildren(); }, + + /** + * @private + */ + refreshThresholds: function () { + var tt = this.threshold; + + if (tt) { + var + v = (this.direction === 'vertical'), + val = v ? this.scrollTop : this.scrollLeft, + delta = this.delta, + cb = this.cachedBounds ? this.cachedBounds : this._getScrollBounds(), + maxVal = v ? cb.maxTop : cb.maxLeft, + d2x = this.dim2extent, + head = Math.floor(this.overhang / 2), + fvg = Math.floor(val / delta), + fg = Math.max(0, fvg - head), + f = d2x * fg, + nPos = fvg * delta; + + tt.max = Math.min(maxVal, nPos + delta), + tt.min = Math.max(0, nPos); + + this.first = f; + } + }, + /** * @private */ @@ -326,6 +479,7 @@ module.exports = kind({ modelsAdded: kind.inherit(function (sup) { return function() { this.calcBoundaries(); + this.refreshThresholds(); sup.apply(this, arguments); }; }), @@ -336,6 +490,7 @@ module.exports = kind({ modelsRemoved: kind.inherit(function (sup) { return function() { this.calcBoundaries(); + this.refreshThresholds(); sup.apply(this, arguments); }; }), diff --git a/src/NewDrawer/NewDrawer.js b/src/NewDrawer/NewDrawer.js index 0b9a1d22c..dbf2bd335 100644 --- a/src/NewDrawer/NewDrawer.js +++ b/src/NewDrawer/NewDrawer.js @@ -1,3 +1,10 @@ +/** +* Contains the declaration for the {@link module:enyo/NewDrawer~NewDrawer} kind. +* @wip +* @private +* @module enyo/NewDrawer +*/ + require('enyo'); var @@ -8,7 +15,22 @@ var Control = require('../Control'), NewAnimator = require('../NewAnimator'); -module.exports = kind({ +/** +* {@link module:enyo/NewDrawer~NewDrawer} is an experimental +* {@link module:enyo/Control~Control}. +* +* It is not a 1:1 replacement for {@link module:enyo/Drawer~Drawer} +* and will likely never be promoted out of its experimental state. +* Use at your own risk. +* +* @class NewDrawer +* @extends module:enyo/Control~Control +* @wip +* @private +* @ui +*/ +module.exports = kind( + /** @lends module:enyo/NewDrawer~NewDrawer.prototype */ { name: 'enyo.NewDrawer', kind: Control, classes: 'enyo-new-drawer', @@ -61,4 +83,4 @@ module.exports = kind({ // this.discoverControlParent(); }; }) -}); +}); \ No newline at end of file diff --git a/src/NewThumb/NewThumb.js b/src/NewThumb/NewThumb.js index 7e1f0c74b..228cfdbd6 100644 --- a/src/NewThumb/NewThumb.js +++ b/src/NewThumb/NewThumb.js @@ -2,6 +2,8 @@ require('enyo'); /** * Contains the declaration for the {@link module:enyo/NewThumb~NewThumb} kind. +* @wip +* @public * @module enyo/NewThumb */ @@ -14,15 +16,37 @@ var Control = require('../Control'); /** -* {@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. +* {@link module:enyo/NewThumb~NewThumb} is a simple scroll thumb designed to be +* used with any scrolling {@link module:enyo/Control~Control} whose scrolling +* behavior is provided by the {@link module:enyo/Scrollable~Scrollable} mixin. +* +* Like all {@link module:enyo/Scrollable~Scrollable}-compatible scroll controls, +* {@link module:enyo/NewThumb~NewThumb} listens to events emitted by the scrolling +* {@link module:enyo/Control~Control} and updates its state (position, visibility, +* etc.) accordingly. * -* `enyo/NewThumb` is not typically created in application code. +* To use {@link module:enyo/NewThumb~NewThumb}, simply include it in the +* {@link module:enyo/Scrollable~Scrollable#scrollControls} block of the scrolling +* control: +* +* ```javascript +* var +* kind = require('enyo/kind'), +* NewThumb = require('enyo/NewThumb'), +* NewDataList = require('enyo/NewDataList'); +* +* var MyList = kind({ +* kind: NewDataList, +* scrollControls: [ +* {kind: NewThumb} +* ] +* }); +* ``` * * @class NewThumb -* @protected +* @extends module:enyo/Control~Control +* @wip +* @public */ module.exports = kind( /** @lends module:enyo/NewThumb~NewThumb.prototype */ { @@ -31,12 +55,12 @@ module.exports = kind( * @private */ name: 'enyo.NewScrollThumb', - + kind: Control, /** * The orientation of the scroll indicator bar; 'v' for vertical or 'h' for horizontal. - * + * * @type {String} * @default 'v' * @public @@ -50,19 +74,19 @@ module.exports = kind( /** * Minimum size of the indicator. * - * @private + * @public */ minSize: ri.scale(4), /** * Size of the indicator's corners. * - * @private + * @public */ cornerSize: ri.scale(6), /** - * @private + * @public */ enabled: false, @@ -112,6 +136,9 @@ module.exports = kind( }; }), + /** + * @private + */ scrollerChanged: function(was) { if (was) { was.off('scrollabilityChanged', this._updateEnablement); @@ -123,6 +150,9 @@ module.exports = kind( } }, + /** + * @private + */ updateEnablement: function() { var s = this.scroller, was = this.enabled, @@ -134,7 +164,7 @@ module.exports = kind( s.on('stateChanged', this._updateVisibility); } } - + if (was && !is) { s.off('metricsChanged', this._update); s.off('stateChanged', this._updateVisibility); @@ -142,6 +172,9 @@ module.exports = kind( } }, + /** + * @private + */ _updateVisibility: function() { var s = this.scroller; @@ -154,6 +187,9 @@ module.exports = kind( } }, + /** + * @private + */ rendered: kind.inherit(function (sup) { return function () { sup.apply(this, arguments); @@ -161,6 +197,9 @@ module.exports = kind( }; }), + /** + * @private + */ calculateMetrics: function () { this.extent = this.parent.getBounds()[this.dimension]; this.minSizeRatio = this.minSize / this.extent; @@ -168,12 +207,7 @@ module.exports = kind( }, /** - * Updates the scroll indicator bar based on the scroll bounds of the strategy, the available - * scroll area, and whether there is overscrolling. If the scroll indicator bar is not - * needed, it will be not be displayed. - * - * @param {module:enyo/ScrollStrategy~ScrollStrategy} strategy - The scroll strategy to update from. - * @public + * @private */ update: function () { var sc = this.scroller, @@ -213,9 +247,13 @@ module.exports = kind( }, /** - * Override `show()` to give fade effect. + * Shows the thumb. + * + * App code should generally not need to manage thumb visibility + * unless the {@link module:enyo/NewThumb~NewThumb#autoHide} property + * has been set to `false`. * - * @private + * @public */ show: function (delay) { if (this.enabled) { @@ -228,27 +266,67 @@ module.exports = kind( }, /** - * Hides the control. + * Hides the thumb. * - * @private + * App code should generally not need to manage thumb visibility + * unless the {@link module:enyo/NewThumb~NewThumb#autoHide} property + * has been set to `false`. + * + * @public */ hide: function () { this.stopJob('hide'); this.addClass('hidden'); }, + /** + * Overriding `handleResize()` to re-calculate ratio and size. + * + * @private + */ + handleResize: function () { + Control.prototype.handleResize.apply(this, arguments); + if (this.getAbsoluteShowing()) { + this.calculateMetrics(); + } + }, + + /** + * Overriding `showingChangedHandler()` to recalculate metrics on show. + * + * @private + */ + showingChangedHandler: function (sender, e) { + Control.prototype.showingChangedHandler.apply(this, arguments); + if (this.getAbsoluteShowing()) { + this.calculateMetrics(); + } + }, + + /** + * @private + */ v2dMatrix: function (p, s) { return '1, 0, 0, ' + (s / this.naturalSize) + ', 0,' + p; }, + /** + * @private + */ v3dMatrix: function(p, s) { return '1, 0, 0, 0, 0,' + (s / this.naturalSize) + ', 0, 0, 0, 0, 1, 0, 0, ' + p + ', 1, 1'; }, + /** + * @private + */ h2dMatrix: function(p, s) { return (s / this.naturalSize) + ', 0, 0, 1, ' + p + ', 0'; }, + /** + * @private + */ h3dMatrix: function(p, s) { return (s / this.naturalSize) + ', 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, ' + p + ', 0, 1, 1'; } diff --git a/src/ObserverSupport.js b/src/ObserverSupport.js index 1df5d353c..decff104f 100644 --- a/src/ObserverSupport.js +++ b/src/ObserverSupport.js @@ -373,7 +373,7 @@ var ObserverSupport = { if (path) { loc[path] = null; } else { - observerTable[euid] = null; + delete observerTable[euid]; } } @@ -509,7 +509,8 @@ var ObserverSupport = { chain; sup.apply(this, arguments); - + + this.removeAllObservers(); if (chains) { for (path in chains) { chain = chains[path]; diff --git a/src/Option.js b/src/Option.js index 02ebdcd25..ab4b64c30 100644 --- a/src/Option.js +++ b/src/Option.js @@ -36,12 +36,13 @@ module.exports = kind( * @private */ published: { + /** @lends module:enyo/Option~Option.prototype */ + /** * Value of the [option]{@link module:enyo/Option~Option}. * * @type {String} * @default '' - * @memberof enyo.Option.prototype * @public */ value: '', @@ -51,7 +52,6 @@ module.exports = kind( * * @type {Boolean} * @default false - * @memberof enyo.Option.prototype * @public */ selected: false diff --git a/src/OptionGroup.js b/src/OptionGroup.js index c4bad93af..88fb00bff 100644 --- a/src/OptionGroup.js +++ b/src/OptionGroup.js @@ -40,12 +40,13 @@ module.exports = kind( * @private */ published: { + /** @lends module:enyo/OptionGroup~OptionGroup.prototype */ + /** * The name for this [option group]{@link module:enyo/OptionGroup~OptionGroup}. * * @type {String} * @default '' - * @memberof enyo.OptionGroup.prototype * @public */ label: '' diff --git a/src/Popup/Popup.js b/src/Popup/Popup.js index 89c7a7e72..f657a6086 100644 --- a/src/Popup/Popup.js +++ b/src/Popup/Popup.js @@ -326,6 +326,7 @@ var Popup = module.exports = kind( destroy: kind.inherit(function (sup) { return function() { this.release(); + if (this.showing) this.showHideScrim(false); sup.apply(this, arguments); }; }), diff --git a/src/PriorityQueue.js b/src/PriorityQueue.js index 752ba4818..6efa5e9a1 100644 --- a/src/PriorityQueue.js +++ b/src/PriorityQueue.js @@ -2,6 +2,7 @@ require('enyo'); /** * Exports Priority options used by {@link module:enyo/BackgroundTaskManager}. +* @wip * @module enyo/PriorityQueue */ @@ -48,6 +49,7 @@ var Priorities = { /** * @class PriorityQueue +* @wip * @public */ module.exports = exports = kind( diff --git a/src/RelationalModel/RelationalCollection.js b/src/RelationalModel/Collection.js similarity index 64% rename from src/RelationalModel/RelationalCollection.js rename to src/RelationalModel/Collection.js index 4989f64b4..3b7e87e3d 100644 --- a/src/RelationalModel/RelationalCollection.js +++ b/src/RelationalModel/Collection.js @@ -1,6 +1,7 @@ /** -* Contains the declaration for the {@link module:enyo/RelationalModel~RelationalCollection} kind. -* @module enyo/RelationalModel +* Contains the declaration for the {@link module:enyo/RelationalModel~Collection} kind. +* @module enyo/RelationalModel/Collection +* @private */ var @@ -8,23 +9,24 @@ var Collection = require('../Collection'); var - RelationalModel = require('./RelationalModel'); + Model = require('./Model'); /** * Private class for a collection with a default model kind of * {@link module:enyo/RelationalModel~RelationalModel} as opposed to {@link module:enyo/Model~Model}. * -* @class RelationalCollection +* @class Collection +* @name module:enyo/RelationalModel~Collection * @extends module:enyo/Collection~Collection * @private */ module.exports = kind( - /** @lends module:enyo/RelationalModel~RelationalCollection.prototype */ { + /** @lends module:enyo/RelationalModel~Collection.prototype */ { /** * @private */ - name: "RelationalCollection", + name: 'Collection', /** * @private @@ -34,5 +36,5 @@ module.exports = kind( /** * @private */ - model: RelationalModel + model: Model }); diff --git a/src/RelationalModel/RelationalModel.js b/src/RelationalModel/Model.js similarity index 91% rename from src/RelationalModel/RelationalModel.js rename to src/RelationalModel/Model.js index 90cd16d08..a8d24d5f5 100644 --- a/src/RelationalModel/RelationalModel.js +++ b/src/RelationalModel/Model.js @@ -1,7 +1,11 @@ /** - * Contains the declaration for the {@link module:enyo/RelationalModel~RelationalModel} kind. - * @module enyo/RelationalModel - */ +* Contains the declaration for the {@link module:enyo/RelationalModel~RelationalModel} kind. +* +* NOTE: Do not require this module for use in applications. Require +* {@link module:enyo/RelationalModel} instead. +* @module enyo/RelationalModel/Model +* @private +*/ var kind = require('../kind'), @@ -10,7 +14,7 @@ var var Model = require('../Model'), Store = require('../Store'), - Relation = require('../Relation'); + Relation = require('./Relation'); var defaultRelationType; @@ -22,6 +26,7 @@ var * individual models to exist separately, but be treated as a single entity. * * @class RelationalModel +* @name module:enyo/RelationalModel~RelationalModel * @extends module:enyo/Model~Model * @public */ @@ -34,12 +39,7 @@ var RelationalModel = module.exports = kind( * @private */ kind: Model, - - /** - * @private - */ - /** * An [array]{@glossary Array} declaring relationships of this * [model]{@link module:enyo/RelationalModel~RelationalModel} to other models. @@ -83,14 +83,14 @@ var RelationalModel = module.exports = kind( * * @param {String} key - The key as defined in the * [relations]{@link module:enyo/RelationalModel~RelationalModel#relations} property. - * @returns {(Relation|undefined)} The correct relation instance, or + * @returns {(module:enyo/RelationalModel~Relation|undefined)} The correct relation instance, or * `undefined` if not found. * @public */ getRelation: function (key) { - return this.relations.find(function (ln) { + return this.relations ? this.relations.find(function (ln) { return ln instanceof Relation && ln.key == key; - }); + }) : undefined; }, /** @@ -98,7 +98,7 @@ var RelationalModel = module.exports = kind( * * @param {String} key - The key as defined in the * [relations]{@link module:enyo/RelationalModel~RelationalModel#relations} property. - * @returns {(Relation|undefined)} The correct relation instance, or + * @returns {(module:enyo/RelationalModel~Relation|undefined)} The correct relation instance, or * `undefined` if not found. * @public */ @@ -107,8 +107,8 @@ var RelationalModel = module.exports = kind( }, /** - * Overloaded version of [get]{@link module:enyo/Model~Model#get} to be able to use a _path_ through - * relations. + * Overloaded version of [get]{@link module:enyo/Model~Model#get} to be able to use a _path_ + * through relations. * * @method * @private @@ -312,6 +312,9 @@ var RelationalModel = module.exports = kind( /** * Defines a named relation type * +* @name module:enyo/RelationalModel~RelationalModel.defineRelationType +* @static +* @method * @param {String} name Name of relation type * @param {Relation} type Relation kind constructor * @param {Boolean} isDefault `true` if this type should be the default for type-less relations @@ -326,8 +329,9 @@ RelationalModel.defineRelationType = function (name, type, isDefault) { /** * Ensures that we concatenate (sanely) the relations for any subkinds. * -* @name enyo.RelationalModel.concat +* @name module:enyo/RelationalModel~RelationalModel.concat * @static +* @method * @private */ RelationalModel.concat = function (ctor, props) { @@ -351,5 +355,3 @@ RelationalModel.concat = function (ctor, props) { // apply our modified relations array to the prototype proto.relations = rels; }; - -Relation.relationDefaults.model = RelationalModel; diff --git a/src/Relation.js b/src/RelationalModel/Relation.js similarity index 71% rename from src/Relation.js rename to src/RelationalModel/Relation.js index 6cccc89c9..b94c8c2a7 100644 --- a/src/Relation.js +++ b/src/RelationalModel/Relation.js @@ -1,35 +1,38 @@ require('enyo'); /** -* Contains the declaration for the {@link module:enyo/Relation~Relation} kind. -* @module enyo/Relation +* Contains the declaration for the {@link module:enyo/RelationalModel~Relation} kind. +* @module enyo/RelationalModel/Relation +* @private */ var - kind = require('./kind'), - utils = require('./utils'); + kind = require('../kind'), + utils = require('../utils'); var - Store = require('./Store'); + Store = require('../Store'); /** * The default options for [relations]{@link module:enyo/RelationalModel~RelationalModel#relations}. * These may vary depending on the individual [kind]{@glossary kind} of relation. * * @typedef {Object} module:enyo/RelationalModel~RelationOptions -* @property {String} type=toOne - The [kind]{@glossary kind} of relation being declared. -* Can be the name of the relation type or a reference to the constructor. -* @property {String} key=null - The [attribute]{@link module:enyo/Model~Model#attributes} name for the -* relation being declared. +* @property {module:enyo/RelationalModel~Relation} type=module:enyo/RelationalModel~toOne - The +* [kind]{@glossary kind} of relation being declared. +* @property {String} key=null - The [attribute]{@link module:enyo/Model~Model#attributes} name for +* the relation being declared. * @property {Boolean} create=false - Whether or not the relation should automatically create * the instance of the related kind. * @property {Boolean} parse=false - Whether or not the relation should call the * [parse()]{@link module:enyo/Model~Model#parse} method on incoming data before -* [setting]{@link module:enyo/Model~Model#set} it on the [model]{@link module:enyo/RelationalModel~RelationalModel}. -* @property {String} model=enyo.RelationalModel - The kind of the +* [setting]{@link module:enyo/Model~Model#set} it on the +* [model]{@link module:enyo/RelationalModel~RelationalModel}. +* @property {String} model=enyo/RelationalModel~RelationModel - The kind of the * reverse of the relation. This will vary depending on the type of relation being declared. * @property {Boolean} fetch=false - Whether or not to automatically call -* [fetch()]{@link module:enyo/Model~Model#fetch} (or {@link module:enyo/Collection~Collection#fetch}) after initialization. +* [fetch()]{@link module:enyo/Model~Model#fetch} (or +* {@link module:enyo/Collection~Collection#fetch}) after initialization. * @property {String} inverseKey=null - The key of the reverse relation. * @property {String} inverseType=null - The type of the reverse relation. * @property {Boolean} isOwner=false - Whether or not this end of the relation owns the @@ -41,11 +44,11 @@ var * be included. */ var relationDefaults = { - type: 'toOne', + type: null, // set ex post facto by the module key: null, create: false, parse: false, - model: null, + model: null, // set ex post facto by the module fetch: false, inverseKey: null, inverseType: null, @@ -55,83 +58,92 @@ var relationDefaults = { /** * @class Relation -* @protected +* @name module:enyo/RelationalModel~Relation +* @private */ var Relation = module.exports = kind( - /** @lends module:enyo/Relation~Relation.prototype */ { - + /** @lends module:enyo/RelationalModel~Relation.prototype */ { + /** * @private */ - name: "Relation", - + name: 'enyo.Relation', + /** * @private */ kind: null, - + /** * @private */ options: {}, - + /** * @private */ constructor: function (instance, props) { - + // apply any of the properties to ourself for reference utils.mixin(this, [relationDefaults, this.options, props]); - + // store a reference to the model we're relating this.instance = instance; - - // ensure we have a constructor for our related model kind - this.model = kind.constructorForKind(this.model); - + this.includeInJSON = props.includeInJSON == null && !this.isOwner ? (this.model.prototype.primaryKey || 'id') : this.includeInJSON; - + // let the subkinds do their thing this.init(); }, - + + /** + * @private + */ + isRelated: function (related) { + return related === this.related; + }, + /** * @private */ getRelated: function () { return this.related; }, - + /** + * Sets the `related` model + * + * @param {module:enyo/Model~Model} related - The related model + * @param {Object} [opts] - Used by subkinds to configure the behavior of the method * @private */ - setRelated: function (related) { + setRelated: function (related, opts) { var inst = this.instance, model = this.model, was = this.related, key = this.key, changed, prev; - - + + if (related) Store.off(model, 'add', this._changed, this); - + this.related = related; - + if (!inst._changing) { - + changed = inst.changed || (inst.changed = {}), prev = inst.previous || (inst.previous = {}); - + changed[key] = related; prev[key] = was; if (was !== related) inst.emit('change', changed); } return this; }, - + /** * @private */ @@ -139,21 +151,17 @@ var Relation = module.exports = kind( var isOwner = this.isOwner, create = this.create, related = this.related; - + if ((isOwner || create) && related && related.destroy && !related.destroyed) { related.destroy(); } - + this.destroyed = true; this.instance = null; this.related = null; } }); -/** -* @private -* @static -*/ Relation.concat = function (ctor, props) { var proto = ctor.prototype; if (props.options) { diff --git a/src/RelationalModel/index.js b/src/RelationalModel/index.js index cb79150ab..f3fee3c82 100644 --- a/src/RelationalModel/index.js +++ b/src/RelationalModel/index.js @@ -1,18 +1,23 @@ require('enyo'); /** -* Contains the declaration for the {@link module:enyo/RelationalModel~RelationalModel} kind. +* Contains the declaration for the {@link module:enyo/RelationalModel~RelationalModel} and +* {@link module:enyo/RelationalModel~Collection} kinds. * @module enyo/RelationalModel */ var - RelationalModel = require('./RelationalModel'), + Model = require('./Model'), + Relation = require('./Relation'), toOne = require('./toOne'), toMany = require('./toMany'), manyToMany = require('./manyToMany'); -RelationalModel.defineRelationType('toOne', toOne, true); -RelationalModel.defineRelationType('toMany', toMany); -RelationalModel.defineRelationType('manyToMany', manyToMany); +Model.defineRelationType('toOne', toOne, true); +Model.defineRelationType('toMany', toMany); +Model.defineRelationType('manyToMany', manyToMany); -module.exports = RelationalModel; +Relation.relationDefaults.model = Model; +Relation.relationDefaults.type = toOne; + +module.exports = Model; \ No newline at end of file diff --git a/src/RelationalModel/manyToMany.js b/src/RelationalModel/manyToMany.js index 221f9f7fa..8003e6d5f 100644 --- a/src/RelationalModel/manyToMany.js +++ b/src/RelationalModel/manyToMany.js @@ -1,6 +1,7 @@ /** -* Contains the declaration for the {@link module:moonstone/RelationalModel~manyToMany} kind. -* @module enyo/RelationalModel +* Contains the declaration for the {@link module:enyo/RelationalModel~manyToMany} kind. +* @module enyo/RelationalModel/manyToMany +* @private */ var @@ -14,35 +15,37 @@ var * models. This is an internally-used class. * * @class manyToMany +* @name module:enyo/RelationalModel~manyToMany * @extends module:enyo/RelationalModel~toMany -* @protected +* @private */ var manyToMany = module.exports = kind( /** @lends module:enyo/RelationalModel~manyToMany.prototype */ { - + /** * @private */ kind: toMany, - + /** * @private */ name: 'enyo.manyToMany', - + /** * The default [options]{@link module:enyo/RelationalModel~RelationOptions} overloaded for this * [kind]{@glossary kind}. * * @see module:enyo/RelationalModel~toMany#options - * @type module:enyo/RelationalModel~RelationOptions - * @property {String} inverseType=enyo.manyToMany - This is the **required** type. + * @type enyo/RelationalModel~RelationOptions + * @property {module:enyo/RelationalModel~Relation} inverseType=module:enyo/RelationalModel~manyToMany - This is + * the **required** type. * @public */ options: { inverseType: null // set after the fact }, - + /** * @private */ @@ -55,10 +58,10 @@ var manyToMany = module.exports = kind( rel = model.getRelation(inverseKey), // id = inst.get(inst.primaryKey), isOwner = this.isOwner; - + if (related && related.has(inst)) { // if (related && (related.has(inst) || related.find(function (model) { return model.attributes[model.primaryKey] == id; }))) { - + // if the relation isn't found it probably wasn't defined and we need // to automatically generate it based on what we know if (!rel) { @@ -72,15 +75,15 @@ var manyToMany = module.exports = kind( related: inst }))); } - + // if (rel.related !== inst) rel.setRelated(inst); // if (!rel.related.has(inst)) rel.related.add(inst); return true; } - + return false; }, - + /** * @private */ @@ -92,12 +95,12 @@ var manyToMany = module.exports = kind( isOwner = this.isOwner, model, i; - + // this is a very tricky scenario that we need to be very careful about to try // and avoid unnecessary work (when possible) and to keep out of an infinite // loop of notifications if (sender === related) { - + // we are attempting to distinguish between the occassions we can encounter // this method here if our related collection emits an add, remove or change // event -- if it is change we know it stemmed from a model already @@ -116,7 +119,7 @@ var manyToMany = module.exports = kind( // in this case we removed a/some model/models that should probably be // updated to know about the removal as well for (i = 0; (model = props.models[i]); ++i) { - + // this event will be caught in the event that the model was destroyed // but should that happen the other collections will also have done // this already (or will do it) but if the model is already destroyed @@ -125,7 +128,7 @@ var manyToMany = module.exports = kind( if (!model.destroyed) model.get(inverseKey).remove(inst); } } - + // manyToMany is a special case that requires us to propagate the changes from // either end as changes to the parent model unlike toMany and toOne that // exclusively rely on the isOwner field and safely assuming uni-directional @@ -142,7 +145,7 @@ var manyToMany = module.exports = kind( // return it to whatever it was originally this.isOwner = isOwner; } - + } else sup.apply(this, arguments); }; }) diff --git a/src/RelationalModel/toMany.js b/src/RelationalModel/toMany.js index e2cdd1add..2c659fc50 100644 --- a/src/RelationalModel/toMany.js +++ b/src/RelationalModel/toMany.js @@ -1,17 +1,18 @@ /** * Contains the declaration for the {@link module:enyo/RelationalModel~toMany} kind. - * @module enyo/RelationalModel + * @module enyo/RelationalModel/toMany + * @private */ var kind = require('../kind'), utils = require('../utils'), Collection = require('../Collection'), - Relation = require('../Relation'), Store = require('../Store'); var - RelationalCollection = require('./RelationalCollection'), + Relation = require('./Relation'), + RelationalCollection = require('./Collection'), toOne = require('./toOne'); /** @@ -19,8 +20,9 @@ var * models. This is an internally-used class. * * @class toMany -* @extends module:enyo/Relation~Relation -* @protected +* @name module:enyo/RelationalModel~toMany +* @extends module:enyo/RelationalModel~Relation +* @private */ var toMany = module.exports = kind( /** @lends module:enyo/RelationalModel~toMany.prototype */ { @@ -36,13 +38,13 @@ var toMany = module.exports = kind( name: 'enyo.toMany', /** - * The default [options]{@link module:enyo/RelationalModel~RelationOptions} overloaded for this - * [kind]{@glossary kind}. + * The default [options]{@link module:enyo/RelationalModel~RelationOptions} overloaded for + * this [kind]{@glossary kind}. * * @type module:enyo/RelationalModel~RelationOptions * @property {Boolean} create=true - By default, the relation should create the * [collection]{@link module:enyo/Collection~Collection} automatically. - * @property {module:enyo/Collection~Collection} collection=RelationalCollection - The + * @property {module:enyo/Collection~Collection} collection=module:enyo/RelationalModel~Collection - The * [kind]{@glossary kind} of collection to use; can be the kind name or a * reference to the constructor. * @property {Object} collectionOptions - An options hash to pass to the @@ -67,9 +69,6 @@ var toMany = module.exports = kind( key = this.key, related = this.related != null ? this.related : inst.attributes[key]; - if (typeof collection == 'string') collection = kind.constructorForKind(collection); - if (typeof model == 'string') model = kind.constructorForKind(model); - // since we allow the model property to be used for the collection constructor // we need to check for and use it if we find it if (model.prototype instanceof Collection) { @@ -130,15 +129,23 @@ var toMany = module.exports = kind( fetchRelated: function () { }, + + /** + * @private + */ + isRelated: function (related) { + return this.related.indexOf(related) >= 0; + }, /** + * @see module:enyo/RelationalModel~Relation#setRelated * @private */ - setRelated: function (data) { + setRelated: function (data, opts) { var related = this.related; // related.add(data, {purge: true, parse: true}); - related.add(data, {purge: true}); + related.add(data, opts || {purge: true}); }, /** diff --git a/src/RelationalModel/toOne.js b/src/RelationalModel/toOne.js index 1dd3f0847..6cd4a7e25 100644 --- a/src/RelationalModel/toOne.js +++ b/src/RelationalModel/toOne.js @@ -1,42 +1,47 @@ /** * Contains the declaration for the {@link module:enyo/RelationalModel~toOne} kind. -* @module enyo/RelationalModel +* @module enyo/RelationalModel/toOne +* @private */ var kind = require('../kind'), utils = require('../utils'), Model = require('../Model'), - Relation = require('../Relation'), Store = require('../Store'); +var + Relation = require('./Relation'); + /** * Represents a relationship of data from one [model]{@link module:enyo/Model~Model} to another * model. This is an internally-used class. * * @class toOne -* @extends module:enyo/Relation~Relation -* @protected +* @name module:enyo/RelationalModel~toOne +* @extends module:enyo/RelationalModel~Relation +* @private */ var toOne = module.exports = kind( /** @lends module:enyo/RelationalModel~toOne.prototype */ { - + /** * @private */ kind: Relation, - + /** * @private */ name: 'enyo.toOne', - + /** * The default [options]{@link module:enyo/RelationalModel~RelationOptions} overloaded for this * [kind]{@glossary kind}. * * @type module:enyo/RelationalModel~RelationOptions - * @property {String} inverseType=enyo.toOne - This can be `'enyo.toOne'` or `'enyo.toMany'`. + * @property {module:enyo/RelationalModel~Relation} inverseType=module:enyo/RelationalModel~toOne - This can be + * {@link module:enyo/RelationalModel~toOne} or {@link module:enyo/RelationalModel~toMany}. * @property {Object} modelOptions - An options hash to pass to the related instance if * `create` is `true`. * @public @@ -45,12 +50,12 @@ var toOne = module.exports = kind( inverseType: null, // set after the fact modelOptions: null }, - + /** * @private */ init: function () { - + var model = this.model, inverseType = this.inverseType, inst = this.instance, @@ -60,44 +65,38 @@ var toOne = module.exports = kind( modelOptions = this.modelOptions, id, found; - - // ensure we have a valid model constructor - if (typeof model == 'string') model = kind.constructorForKind(model); - - // ensure our inverse type constructor is correct - if (typeof inverseType == 'string') inverseType = kind.constructorForKind(inverseType); - + // ensure we've got the correct related if any if (related || related === 0) this.related = related; - + // the instance attribute for the designated key will actually point to this relation inst.attributes[key] = this; this.inverseType = inverseType; - + // if we have a related value now check to see if it is something we should attempt // to resolve if (related != null) { - + // just make sure that no one stuck an instance in here if (!(related instanceof Model)) { - + // try and figure out what the assumed id for the related model is if possible if (typeof related == 'object') id = related[model.prototype.primaryKey]; else if (typeof related == 'string' || typeof related == 'number') id = related; - + if (id != null) found = Store.resolve(model, id); - + // if we found an instance we store it and make sure to update the local // reference here if (found) related = this.related = found; } } - + // if this is the owner side of the relation we may need to create the instance // for our relation if it wasn't found already if (related == null || !(related instanceof Model)) { if (this.create) { - + // if the only information we have about the thing is a string or number // then we facade a data hash so the model has the opportunity to work // as expected @@ -106,41 +105,39 @@ var toOne = module.exports = kind( related = {}; related[model.prototype.primaryKey] = id; } - + // if the parse flag is true then we force a parse operation on model // creation regardless of its own flags if (this.parse) { if (!modelOptions) modelOptions = {}; modelOptions.parse = true; } - /*jshint -W055 */ // we create the empty instance so we can separately deal with the // various ways the related data could be handed to us (could be id or data) found = model = new model(related, null, modelOptions); - /*jshint +W055 */ this.related = model; } } - + if (!found) Store.on(model, 'add', this._changed, this); - + // last but not least we begin to listen for changes on our model instance inst.on('change', this._changed, this); }, - + /** * @private */ setRelated: kind.inherit(function (sup) { return function (related) { var val; - + if (related && related instanceof Model) { return sup.apply(this, arguments); } else if (related != null) { val = related; related = this.getRelated(); - + // the only thing we can do is assume the value is intended to be the primary // key of the model just like we would had it been passed into the constructor if (this.create && related) related.set(related.primaryKey, val); @@ -155,34 +152,34 @@ var toOne = module.exports = kind( if (related && related instanceof Model) return sup.call(this, related); } } - + // @TODO: This ignores cases that it might be set as null or undefined which // would clear the related but most of the code assumes there will always be // a value for related but it seems _possible_ that this behavior may be // necessary so would need to find a way to handle that }; }), - + /** * @private */ fetchRelated: function () { }, - + /** * @private */ findRelated: function () { - + var related = this.related, // inst = this.instance, isOwner = this.isOwner, found, rev; - + if (related && related instanceof this.model) found = related; else { - + // we need to search for the related instance if we can found = Store.findLocal( this.model, @@ -190,20 +187,20 @@ var toOne = module.exports = kind( {all: false, context: this} ); } - + if (found) { - + // if we are the owner end we may have a listener on the store and can // safely remove it Store.off(this.model, 'add', this._changed, this); - + // update our related value this.related = found; - + // if we can/need to we will establish the other end of this relationship if (this.inverseKey) { rev = found.getRelation(this.inverseKey); - + // if there isn't one then we go ahead and create it implicitly if (!rev) { rev = new this.inverseType(found, { @@ -214,27 +211,20 @@ var toOne = module.exports = kind( model: this.instance.ctor, related: this.instance }); - + found.relations.push(rev); } - + // now we ensure that the instances are pointing at eachother - switch (rev.kindName) { - case 'enyo.toOne': - if (rev.related !== this.instance) rev.setRelated(this.instance); - break; - case 'enyo.toMany': - rev.related.add(this.instance, {merge: false}); - break; - } + if (!rev.isRelated(this.instance)) rev.setRelated(this.instance, {merge: false}); } - + if (isOwner) found.on('change', this._changed, this); } - + return found; }, - + /** * @private */ @@ -244,7 +234,7 @@ var toOne = module.exports = kind( id = inst.get(inst.primaryKey), inverseKey = this.inverseKey, pkey = model.get(model.primaryKey); - + return (related && ( model.euid == related || pkey == related || @@ -252,7 +242,7 @@ var toOne = module.exports = kind( ) || (id != null && model.get(inverseKey) == id) ); }, - + /** * @private */ @@ -267,7 +257,7 @@ var toOne = module.exports = kind( } return raw; }, - + /** * @private */ @@ -276,18 +266,18 @@ var toOne = module.exports = kind( inst = this.instance, isOwner = this.isOwner, changed; - + if (sender === this.instance) { if (e == 'change') { if (key in props) this.findRelated(); } } else if (sender === this.related) { if (e == 'change' && isOwner) { - + // if the instance is already changing then we do not need to do anything // because it probably stemmed from there anyway if (inst._changing) return; - + // @todo This must be updated to be more thorough... inst.isDirty = true; changed = inst.changed || (inst.changed = {}); diff --git a/src/Repeater.js b/src/Repeater.js index 9c7116608..930ccae2c 100644 --- a/src/Repeater.js +++ b/src/Repeater.js @@ -15,7 +15,7 @@ var * The extended {@glossary event} [object]{@glossary Object} that is provided * when the [onSetupItem]{@link module:enyo/Repeater~Repeater#onSetupItem} event is fired. * -* @typedef {Object} enyo.Repeater~SetupItemEvent +* @typedef {Object} module:enyo/Repeater~Repeater~SetupItemEvent * @property {Number} index - The item's index. * @property {Object} item - The item control, for decoration. */ @@ -95,7 +95,7 @@ var OwnerProxy = kind( * Repeater = require('enyo/Repeater'); * * {kind: Repeater, count: 2, onSetupItem: 'setImageSource', components: [ -* {kind: 'Image'} +* {kind: Image} * ]} * * setImageSource: function(inSender, inEvent) { diff --git a/src/RepeaterChildSupport.js b/src/RepeaterChildSupport.js index 46d665ba4..b917809a5 100644 --- a/src/RepeaterChildSupport.js +++ b/src/RepeaterChildSupport.js @@ -39,6 +39,15 @@ var RepeaterChildSupport = { */ selected: false, + /** + * The CSS class applied to the child when selected + * + * @name module:enyo/RepeaterChildSupport~RepeaterChildSupport#selectedClass + * @type {String} + * @default null + * @public + */ + /** * 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- @@ -49,7 +58,7 @@ var RepeaterChildSupport = { * @private */ cachePoint: true, - + /* * @method * @private @@ -109,9 +118,9 @@ var RepeaterChildSupport = { } }, /** - * 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 + * 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 @@ -132,7 +141,7 @@ var RepeaterChildSupport = { 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 @@ -151,7 +160,7 @@ var RepeaterChildSupport = { } } } - + if (!event._fromRepeaterChild) { if (!!~utils.indexOf(name, this.repeater.selectionEvents)) { this._selectionHandler(); @@ -177,8 +186,7 @@ var RepeaterChildSupport = { var bnd = this.binding({ from: 'model.' + s, to: 'selected', - oneWay: false/*, - kind: enyo.BooleanBinding*/ + oneWay: false }); this._selectionBindingId = bnd.euid; } diff --git a/src/RichText/RichText.js b/src/RichText/RichText.js index dae00b0de..12f8ff13f 100644 --- a/src/RichText/RichText.js +++ b/src/RichText/RichText.js @@ -19,14 +19,14 @@ var /** * The type of change to apply. Possible values are `'move'` and `'extend'`. * -* @typedef {String} enyo.RichText~ModifyType +* @typedef {String} module:enyo/RichText~RichText~ModifyType */ /** * The direction in which to apply the change. Possible values include: `'forward'`, * `'backward'`, `'left'`, and `'right'`. * -* @typedef {String} enyo.RichText~ModifyDirection +* @typedef {String} module:enyo/RichText~RichText~ModifyDirection */ /** @@ -34,7 +34,7 @@ var * `'sentence'`, `'line'`, `'paragraph'`, `'lineboundary'`, `'sentenceboundary'`, * `'paragraphboundary'`, and `'documentboundary'`. * -* @typedef {String} enyo.RichText~ModifyAmount +* @typedef {String} module:enyo/RichText~RichText~ModifyAmount */ /** @@ -277,6 +277,7 @@ var RichText = module.exports = kind( * @param {module:enyo/RichText~ModifyType} type - The type of change to apply. * @param {module:enyo/RichText~ModifyDirection} dir - The direction in which to apply the change. * @param {module:enyo/RichText~ModifyAmount} amount - The granularity of the change. + * @deprecated since version 2.7 * @public */ modifySelection: function (type, dir, amount) { @@ -291,6 +292,7 @@ var RichText = module.exports = kind( * * @param {module:enyo/RichText~ModifyDirection} dir - The direction in which to apply the change. * @param {module:enyo/RichText~ModifyAmount} amount - The granularity of the change. + * @deprecated since version 2.7 * @public */ moveCursor: function (dir, amount) { @@ -300,6 +302,7 @@ var RichText = module.exports = kind( /** * Moves the cursor to end of text field. * + * @deprecated since version 2.7 * @public */ moveCursorToEnd: function () { @@ -309,6 +312,7 @@ var RichText = module.exports = kind( /** * Moves the cursor to start of text field. * + * @deprecated since version 2.7 * @public */ moveCursorToStart: function () { diff --git a/src/ScrollMath.js b/src/ScrollMath.js index ca759576b..18f2bfb86 100644 --- a/src/ScrollMath.js +++ b/src/ScrollMath.js @@ -19,7 +19,7 @@ var * * @event module:enyo/ScrollMath~ScrollMath#onScrollStart * @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 {module:enyo/Scroller~Scroller~ScrollEvent} event - An [object]{@glossary Object} containing * event information. @@ -31,7 +31,7 @@ var * * @event module:enyo/ScrollMath~ScrollMath#onScroll * @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 {module:enyo/Scroller~Scroller~ScrollEvent} event - An [object]{@glossary Object} containing * event information. @@ -43,7 +43,7 @@ var * * @event module:enyo/ScrollMath~ScrollMath#onScrollStop * @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 {module:enyo/Scroller~Scroller~ScrollEvent} event - An [object]{@glossary Object} containing * event information. @@ -54,7 +54,7 @@ var * {@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 @@ -73,10 +73,10 @@ module.exports = kind( /** * @private */ - published: + published: /** @lends module:enyo/ScrollMath~ScrollMath.prototype */ { - /** + /** * Set to `true` to enable vertical scrolling. * * @type {Boolean} @@ -85,7 +85,7 @@ module.exports = kind( */ vertical: true, - /** + /** * Set to `true` to enable horizontal scrolling. * * @type {Boolean} @@ -106,95 +106,95 @@ module.exports = kind( }, /** - * "Spring" damping returns the scroll position to a value inside the boundaries. Lower + * "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 + /** + * "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. + /** + * 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 + /** + * 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 + + /** + * 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 @@ -267,7 +267,7 @@ module.exports = kind( }, /** - * Boundary damping function. Returns damped `value` based on `coeff` on one side of + * Boundary damping function. Returns damped `value` based on `coeff` on one side of * `origin`. * * @private @@ -289,7 +289,7 @@ module.exports = kind( }, /** - * Dual-boundary damping function. Returns damped `value` based on `coeff` when exceeding + * Dual-boundary damping function. Returns damped `value` based on `coeff` when exceeding * either boundary. * * @private @@ -334,7 +334,7 @@ module.exports = kind( this[ex] = this[ex0] + c * dp; }, - /** + /** * One unit of time for simulation. * * @private @@ -466,7 +466,7 @@ module.exports = kind( }); this.job = animation.requestAnimationFrame(fn); }, - + /** * @private */ @@ -483,7 +483,7 @@ module.exports = kind( stop: function (fire) { var job = this.job; if (job) { - this.job = animation.cancelRequestAnimationFrame(job); + this.job = animation.cancelAnimationFrame(job); } if (fire) { this.doScrollStop(); @@ -533,7 +533,7 @@ module.exports = kind( 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 @@ -643,7 +643,7 @@ module.exports = kind( dX = dY; dY = 0; } - + if (dY && canY) { tY = -(refY + (dY * m)); shouldScroll = true; @@ -670,7 +670,7 @@ module.exports = kind( // 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. * @@ -754,7 +754,7 @@ module.exports = kind( setScrollPosition: function (pos) { this.setScrollY(pos); }, - + canScrollX: function() { return this.horizontal && this.rightBoundary < 0; }, @@ -764,9 +764,9 @@ module.exports = kind( }, - /** + /** * Determines whether or not the [scroller]{@link module:enyo/Scroller~Scroller} is actively moving. - * + * * @return {Boolean} `true` if actively moving; otherwise, `false`. * @private */ @@ -774,9 +774,9 @@ module.exports = kind( 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 */ diff --git a/src/Scrollable/Scrollable.js b/src/Scrollable/Scrollable.js index c2eb8055e..a18aa563d 100644 --- a/src/Scrollable/Scrollable.js +++ b/src/Scrollable/Scrollable.js @@ -1,3 +1,11 @@ +/** +* Exports the {@link module:enyo/Scrollable~Scrollable} mixin. +* +* @wip +* @public +* @module enyo/Scrollable +*/ + var kind = require('../kind'), utils = require('../utils'), @@ -27,18 +35,32 @@ function calcNodeVisibility (nodePos, nodeSize, scrollPos, scrollSize) { } /** -* Doc +* Mix scrolling support into any Control that contains content suitable for scrolling. * -* @module enyo/Scrollable +* @mixin +* @wip * @public */ -module.exports = { +var Scrollable = { /** * @private */ name: 'Scrollable', - + + /** + * An array of control definitions that will be instatiated with the scroller. Each object + * will be passed a `scroller` property that contains a reference to the scroller. The controls + * can register to receive scroll events from the scroller. + * + * @name scrollControls + * @type {Object[]} + * @default undefined + * @see module:enyo/NewThumb~NewThumb + * @public + * @memberof module:enyo/Scrollable~Scrollable + */ + /** * Specifies how to horizontally scroll. Acceptable values are `'scroll'`, `'auto'`, * `'hidden'`, and `'default'`. The precise effect of the setting is determined by the @@ -80,24 +102,19 @@ module.exports = { scrollLeft: 0, /** - * Maximum height of the scroll content. - * - * @type {Number} - * @default null - * @memberof module:enyo/Scroller~Scroller.prototype - * @public + * Vestige of previous implementation -- should be eliminated and + * does not appear to be referenced, but leaving here for the moment + * to avoid accidental breakage. + * + * @private */ 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 + * Vestige of previous implementation -- should be eliminated, but + * currently still referenced. + * + * @private */ touch: true, @@ -127,16 +144,16 @@ module.exports = { block: 'farthest', /** - * Set to `true` to display a scroll thumb in touch [scrollers]{@link module:enyo/Scroller~Scroller}. - * - * @type {Boolean} - * @default true - * @public + * Vestige of previous implementation -- should be eliminated and + * does not appear to be referenced, but leaving here for the moment + * to avoid accidental breakage. + * + * @private */ thumb: true, /** - * If `true`, mouse wheel may be used to move the [scroller]{@link module:enyo/Scroller~Scroller}. + * If `true`, mouse wheel may be used to move the [scrollable]{@link module:enyo/Scrollable~Scrollable} control. * * @type {Boolean} * @default true @@ -145,51 +162,116 @@ module.exports = { useMouseWheel: true, /** - * TODO: Document - * Experimental + * This should ultimately be made public, but not sure it's fully baked so + * holding off for now. * - * @public + * @private */ horizontalSnapIncrement: null, /** - * TODO: Document - * Experimental + * This should ultimately be made public, but not sure it's fully baked so + * holding off for now. * - * @public + * @private */ verticalSnapIncrement: null, /** - * TODO: Document - * Experimental + * This should ultimately be made public, but not sure it's fully baked so + * holding off for now. * - * @public + * @private */ suppressMouseEvents: false, + /** + * By default, {@link module:enyo/Scrollable~Scrollable} creates and + * uses a default instance of {@link module:enyo/ScrollMath~ScrollMath}, which + * is responsible for scroll physics. + * + * If you want to customize scroll physics, you can provide an object literal + * to the `scrollMath` property that will be used to create a + * {@link module:enyo/ScrollMath~ScrollMath} instance to your specifications. + * Make sure to include `kind: ScrollMath` in your object literal, along with + * whatever `ScrollMath` properties you want to set. + * + * @type {Object} + * @public + */ scrollMath: {kind: ScrollMath}, + /** + * This should ultimately be made public, but not sure it's fully baked so + * holding off for now. + * + * @private + */ pageMultiplier: 1, + /** + * @private + */ canScrollX: false, + /** + * @private + */ canScrollY: false, + /** + * @private + */ couldScrollX: false, + /** + * @private + */ couldScrollY: false, + /** + * @private + */ canScrollUp: false, + /** + * @private + */ canScrollDown: false, + /** + * @private + */ canScrollLeft: false, + /** + * @private + */ canScrollRight: false, + /** + * @private + */ velocity: 0, + /** + * @private + */ topOffset: 0, + /** + * @private + */ rightOffset: 0, + /** + * @private + */ bottomOffset: 0, + /** + * @private + */ leftOffset: 0, + /** + * @private + */ mixins: [EventEmitter], + /** + * @private + */ handlers: { ondragstart: 'dragstart', ondragfinish: 'dragfinish', @@ -213,8 +295,14 @@ module.exports = { onShouldDrag: '' }, + /** + * @private + */ classes: 'enyo-scrollable enyo-fill', + /** + * @private + */ create: kind.inherit(function (sup) { return function() { sup.apply(this, arguments); @@ -234,6 +322,9 @@ module.exports = { }; }), + /** + * @private + */ destroy: kind.inherit(function (sup) { return function () { sup.apply(this, arguments); @@ -243,8 +334,21 @@ module.exports = { }; }), + /** + * @private + */ showingChangedHandler: kind.inherit(function (sup) { return function (sender, event) { + // Calculate boundaries when shown, just in case + // anything has happened (like scroller contents changing) + // while we were hidden. We do this unconditionally since + // it's cheap to do it now and we avoid a lot of extra + // complexity by not trying to track whether we need it. + // May need to revisit this decision if related issues + // arise. + if (event.showing) { + this.calcBoundaries(); + } sup.apply(this, arguments); if (!event.showing && this._suppressing) { this._resumeMouseEvents(); @@ -252,6 +356,9 @@ module.exports = { }; }), + /** + * @private + */ rendered: kind.inherit(function (sup) { return function() { sup.apply(this, arguments); @@ -1061,3 +1168,5 @@ module.exports = { return false; } }; + +module.exports = Scrollable; diff --git a/src/Scroller/Scroller.js b/src/Scroller/Scroller.js index 42f99c8f4..05111272d 100644 --- a/src/Scroller/Scroller.js +++ b/src/Scroller/Scroller.js @@ -47,7 +47,7 @@ var */ /** -* The extended {@glossary event} [object]{@glossary Object} that is provided +* The extended {@glossary event} [object]{@glossary Object} that is provided * when a scroll event is fired. * * @typedef {Object} module:enyo/Scroller~Scroller~ScrollEvent @@ -59,7 +59,7 @@ var * * @event module:enyo/Scroller~Scroller#onScrollStart * @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 {module:enyo/Scroller~Scroller~ScrollEvent} event - An [object]{@glossary Object} containing * event information. @@ -71,9 +71,9 @@ var * * @event module:enyo/Scroller~Scroller#onScroll * @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 +* @property {Object} event - An [object]{@glossary Object} containing * event information. * @public */ @@ -83,9 +83,9 @@ var * * @event module:enyo/Scroller~Scroller#onScrollStop * @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 +* @property {Object} event - An [object]{@glossary Object} containing * event information. * @public */ @@ -93,14 +93,14 @@ var /** * {@link module:enyo/Scroller~Scroller} is a scroller suitable for use in both desktop and mobile * 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 * 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'`). -* +* * For more information, see the documentation on * [Scrollers]{@linkplain $dev-guide/building-apps/layout/scrollers.html} in the * Enyo Developer Guide. @@ -110,22 +110,22 @@ var */ var Scroller = module.exports = kind( /** @lends module:enyo/Scroller~Scroller.prototype */ { - + name: 'enyo.Scroller', - + kind: Control, - + /** * @private */ - published: + published: /** @lends module:enyo/Scroller~Scroller.prototype */ { /** * 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 @@ -136,7 +136,7 @@ var Scroller = module.exports = kind( * 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 @@ -145,7 +145,7 @@ var Scroller = module.exports = kind( /** * The vertical scroll position. - * + * * @type {Number} * @default 0 * @public @@ -154,7 +154,7 @@ var Scroller = module.exports = kind( /** * The horizontal scroll position. - * + * * @type {Number} * @default 0 * @public @@ -163,7 +163,7 @@ var Scroller = module.exports = kind( /** * Maximum height of the scroll content. - * + * * @type {Number} * @default null * @memberof enyo/Scroller~Scroller.prototype @@ -172,41 +172,40 @@ var Scroller = module.exports = kind( 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 + * 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: false, /** - * Specifies a type of scrolling. The [scroller]{@link module:enyo/Scroller~Scroller} will attempt to + * Specifies a type of scrolling. The [scroller]{@link module:enyo/Scroller~Scroller} will attempt to * automatically select a strategy compatible with the runtime environment. Alternatively, * you may choose to use a specific strategy: - * - * - [ScrollStrategy]{@link module:enyo/ScrollStrategy~ScrollStrategy} is the default and implements no + * + * - [ScrollStrategy]{@link module:enyo/ScrollStrategy~ScrollStrategy} is the default and implements no * scrolling, relying instead on the environment to scroll properly. - * - [TouchScrollStrategy]{@link module:enyo/TouchScrollStrategy~TouchScrollStrategy} implements a touch scrolling + * - [TouchScrollStrategy]{@link module:enyo/TouchScrollStrategy~TouchScrollStrategy} implements a touch scrolling * mechanism. - * - [TranslateScrollStrategy]{@link module:enyo/TranslateScrollStrategy~TranslateScrollStrategy} implements a touch + * - [TranslateScrollStrategy]{@link module:enyo/TranslateScrollStrategy~TranslateScrollStrategy} implements a touch * scrolling mechanism using translations; it is currently recommended only for Android - * 3 and 4, and Windows Phone 8. - * - [TransitionScrollStrategy]{@link module:enyo/TransitionScrollStrategy~TransitionScrollStrategy} implements a touch - * scrolling mechanism using CSS transitions; it is currently recommended only for iOS - * 5 and later. + * 3+, iOS 5+ and Windows Phone 8. + * - [TransitionScrollStrategy]{@link module:enyo/TransitionScrollStrategy~TransitionScrollStrategy} implements a touch + * scrolling mechanism using CSS transitions; * - * @type {String} - * @default 'ScrollStrategy' + * @type {Object} + * @default module:enyo/ScrollStrategy~ScrollStrategy * @public */ strategyKind: ScrollStrategy, /** * Set to `true` to display a scroll thumb in touch [scrollers]{@link module:enyo/Scroller~Scroller}. - * + * * @type {Boolean} * @default true * @public @@ -215,7 +214,7 @@ var Scroller = module.exports = kind( /** * If `true`, mouse wheel may be used to move the [scroller]{@link module:enyo/Scroller~Scroller}. - * + * * @type {Boolean} * @default true * @public @@ -253,7 +252,7 @@ var Scroller = module.exports = kind( touchOverscroll: true, /** - * If `true`, the [scroller]{@link module:enyo/Scroller~Scroller} will not propagate `dragstart` + * If `true`, the [scroller]{@link module:enyo/Scroller~Scroller} will not propagate `dragstart` * [events]{@glossary event} that cause it to start scrolling. * * @type {Boolean} @@ -263,7 +262,7 @@ var Scroller = module.exports = kind( preventDragPropagation: true, /** - * If `true`, the [scroller]{@link module:enyo/Scroller~Scroller} will not propagate scroll + * If `true`, the [scroller]{@link module:enyo/Scroller~Scroller} will not propagate scroll * [events]{@glossary event}. * * @type {Boolean} @@ -274,7 +273,7 @@ var Scroller = module.exports = kind( /** * Needed to allow global mods to `enyo/Scroller.touchScrolling`. - * + * * @private */ @@ -295,9 +294,13 @@ var Scroller = module.exports = kind( classes: 'enyo-scroller', /** + * @lends module:enyo/Scroller~Scroller * @private */ statics: { + /** + * @private + */ osInfo: [ {os: 'android', version: 3}, {os: 'androidChrome', version: 18}, @@ -308,7 +311,7 @@ var Scroller = module.exports = kind( {os: 'blackberry', version:1e9}, {os: 'tizen', version: 2} ], - //* Returns true if platform should have touch events. + /** Returns true if platform should have touch events. */ hasTouchScrolling: function() { for (var i=0, t; (t=this.osInfo[i]); i++) { if (platform[t.os]) { @@ -321,8 +324,8 @@ var Scroller = module.exports = kind( } }, /** - Returns true if the platform has native div scrollers (desktop - browsers always have them). + * Returns true if the platform has native div scrollers (desktop + * browsers always have them). */ hasNativeScrolling: function() { for (var i=0, t; (t=this.osInfo[i]); i++) { @@ -332,8 +335,11 @@ var Scroller = module.exports = kind( } return true; }, + /** + * @private + */ getTouchStrategy: function() { - return (platform.androidChrome >= 27) || (platform.android >= 3) || (platform.windowsPhone === 8) || (platform.webos >= 4) + return (platform.androidChrome >= 27) || (platform.android >= 3) || (platform.ios >= 5) || (platform.windowsPhone === 8) || (platform.webos >= 4) ? TranslateScrollStrategy : TouchScrollStrategy; } @@ -488,7 +494,7 @@ var Scroller = module.exports = kind( // FIXME: these properties are virtual; property changed methods are fired only if // property value changes, not if getter changes. - + /** * Sets the horizontal scroll position. * @@ -535,7 +541,7 @@ var Scroller = module.exports = kind( /** * Retrieves the scroll boundaries of the [scroller]{@link module:enyo/Scroller~Scroller}. - * + * * @returns {module:enyo/Scroller~BoundaryObject} An [object]{@glossary Object} describing the * scroll boundaries. * @public @@ -554,11 +560,11 @@ var Scroller = module.exports = kind( return bounds; }, - /** + /** * Trigger a remeasurement of the scroller's metrics (specifically, the * size of its viewport, the size of its contents and the difference between * the two, which determines the extent to which the scroller may scroll). - * + * * 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 @@ -584,7 +590,7 @@ var Scroller = module.exports = kind( this.$.strategy.scrollIntoView(ctl, alignWithTop); }, - /** + /** * Scrolls to the specified position. * * @param {Number} x - The `x` position in pixels. @@ -596,10 +602,10 @@ var Scroller = module.exports = kind( }, /** - * Ensures that the given [control]{@link module:enyo/Control~Control} is visible in the - * [scroller's]{@link module:enyo/Scroller~Scroller} viewport. Unlike - * [scrollIntoView()]{@link module:enyo/Scroller~Scroller#scrollIntoView}, which uses DOM's - * [scrollIntoView()]{@glossary scrollIntoView}, this only affects the current + * Ensures that the given [control]{@link module:enyo/Control~Control} is visible in the + * [scroller's]{@link module:enyo/Scroller~Scroller} viewport. Unlike + * [scrollIntoView()]{@link module:enyo/Scroller~Scroller#scrollIntoView}, which uses DOM's + * [scrollIntoView()]{@glossary scrollIntoView}, this only affects the current * scroller. * * @param {module:enyo/Control~Control} ctl - The [control]{@link module:enyo/Control~Control} to make visible in the @@ -612,7 +618,7 @@ var Scroller = module.exports = kind( this.scrollToNode(ctl.hasNode(), alignWithTop); }, - /** + /** * Ensures that the given node is visible in the [scroller's]{@link module:enyo/Scroller~Scroller} viewport. * * @param {Node} node - The node to make visible in the [scroller's]{@link module:enyo/Scroller~Scroller} @@ -625,9 +631,9 @@ var Scroller = module.exports = kind( this.$.strategy.scrollToNode(node, alignWithTop); }, - /** + /** * Stops the scroller if it is currently animating. - * + * * @public */ stop: function() { @@ -636,9 +642,9 @@ var Scroller = module.exports = kind( } }, - /** + /** * Adds current values of `getScrollBounds()` to {@glossary event}. - * + * * @private */ decorateScrollEvent: function (e) { @@ -670,7 +676,7 @@ var Scroller = module.exports = kind( this.scrollTop = bounds.top; }, - /** + /** * Normalizes scroll {@glossary event} to `onScroll`. * * @fires module:enyo/Scroller~Scroller#onScroll @@ -710,7 +716,7 @@ var Scroller = module.exports = kind( return true; }, - /** + /** * Either propagates or stops the current scroll {@glossary event}. * * @private @@ -795,7 +801,7 @@ var Scroller = module.exports = kind( }, /** - * Sends the [useMouseWheel]{@link module:enyo/Scroller~Scroller#useMouseWheel} property to the scroll + * Sends the [useMouseWheel]{@link module:enyo/Scroller~Scroller#useMouseWheel} property to the scroll * strategy. * * @private diff --git a/src/Select.js b/src/Select.js index cdfd106b1..ead5457a2 100644 --- a/src/Select.js +++ b/src/Select.js @@ -7,8 +7,7 @@ require('enyo'); var kind = require('./kind'), - platform = require('./platform'), - dispatcher = require('./dispatcher'); + platform = require('./platform'); var Control = require('./Control'), /*jshint -W079*/ @@ -38,7 +37,7 @@ var * } * } * ``` -* +* * Note: This uses the [<select>]{@glossary select} tag, which isn't implemented for * native webOS applications, although it does work in the webOS Web browser. * @@ -63,12 +62,12 @@ module.exports = kind( /** * @private */ - published: + published: /** @lends module:enyo/Select~Select.prototype */ { - + /** * The index of the selected [option]{@link module:enyo/Option~Option} in the list. - * + * * @type {Number} * @default null * @public @@ -77,7 +76,7 @@ module.exports = kind( /** * The value of the selected [option]{@link module:enyo/Option~Option}. - * + * * @type {Object} * @default null * @public @@ -86,7 +85,7 @@ module.exports = kind( /** * The size of the select box in rows. - * + * * @type {Number} * @default 0 * @public @@ -94,8 +93,8 @@ module.exports = kind( size: 0, /** - * Sets whether the enyo.Select can select multiple options - * + * Sets whether the enyo/Select can select multiple options + * * @type {Boolean} * @default false * @public @@ -103,15 +102,15 @@ module.exports = kind( multiple: false, /** - * Sets whether the enyo.Select is disabled, or not - * + * Sets whether the enyo/Select is disabled, or not + * * @type {Boolean} * @default false * @public */ disabled: false }, - + /** * @private */ @@ -136,10 +135,6 @@ module.exports = kind( rendered: kind.inherit(function (sup) { return function () { sup.apply(this, arguments); - //Trick to force IE8 onchange event bubble - if (platform.ie == 8) { - this.setAttribute('onchange', dispatcher.bubbler); - } // This makes Select.selected a higher priority than Option.selected but ensures that // the former works at create time if (this.selected !== null) { @@ -168,6 +163,25 @@ module.exports = kind( this.set('value', this.getNodeProperty('value', this.value)); }, + /** + * Updates the `selected` option matching the current value. If the option doesn't have a value, + * it will match its content instead. + * + * @private + */ + selectOptionByValue: function () { + var i, controls, l, c; + + for (i = 0, controls = this.getControls(), l = controls.length; i < l; i++) { + c = controls[i]; + // support + if (c.value ? c.value === this.value : c.content === this.value) { + if (i !== this.selected) this.set('selected', i); + break; + } + } + }, + /** * @private */ @@ -180,8 +194,15 @@ module.exports = kind( * @private */ valueChanged: function () { - this.setNodeProperty('value', this.value); - this.updateSelectedFromNode(); + // IE will clear selectedIndex when setting the value directly on the node so instead we + // iterate the children, find the matching value, and update selected if necessary. + if (platform.ie || platform.edge) { + this.selectOptionByValue(); + } + else { + this.setNodeProperty('value', this.value); + this.updateSelectedFromNode(); + } }, /** diff --git a/src/Selection.js b/src/Selection.js index 0f3dfbf86..b588190c8 100644 --- a/src/Selection.js +++ b/src/Selection.js @@ -15,7 +15,7 @@ var * when the [onSelect]{@link module:enyo/Selection~Selection#onSelect} and * [onDeselect]{@link module:enyo/Selection~Selection#onDeselect} events are fired. * -* @typedef {Object} enyo.Selection~SelectionEvent +* @typedef {Object} module:enyo/Selection~Selection~SelectionEvent * @property {Number|String} key The key that was used to register the * [selection]{@link module:enyo/Selection~Selection} (usually a row index). * @property {Object} data - References data registered with the key by the code diff --git a/src/ShowingTransitionSupport.js b/src/ShowingTransitionSupport.js new file mode 100644 index 000000000..a72077972 --- /dev/null +++ b/src/ShowingTransitionSupport.js @@ -0,0 +1,244 @@ +/** +* This module exports the {@link module:enyo/ShowingTransitionSupport~ShowingTransitionSupport} mixin. +* +* @wip +* @module enyo/ShowingTransitionSupport +*/ + +var kind = require('enyo/kind'), + utils = require('enyo/utils'); + +/** +* The {@link module:enyo/ShowingTransitionSupport~ShowingTransitionSupport} [mixin]{@glossary mixin} +* is applicable to any control that should use a transition or animation when it is shown or hidden. +* This mixin adds optional state-classes to the component at its resting or transitioning states. +* The states are as follows: +* +* * 'shown' and 'hidden' - resting, static, not-transitioning, past tense states. +* * 'showing' and 'hiding' - transitioning-to, progressive tense states. +* +* A CSS class may be optionally supplied and applied to the component during that state. It will be +* removed immediately when the component is no longer in that state. The 'hidden', 'hiding', and +* 'showing' CSS classes are already defined as defaults, to account for the most common use case. +* The same classes are allowed to be used on multiple states. +* +* It may be desirable to have a transition on only one of the showing or hiding states; this is +* also possible. +* +* Transitions take time, so be sure to include a `hidingDuration` and/or `showingDuration`. You may +* take advantage of this [mixin]{@glossary mixin}'s state classes without using transitions by +* leaving the duration properties blank (0, null or undefined) and the resting states will simply be +* applied immediately, skipping the transition state classes. +* +* An optional method may be supplied to fire at the end of either of the transitions, using +* `hidingMethod` and/or `showingMethod`. +* +* @mixin +* @wip +* @public +*/ +var ShowingTransitionSupport = { + + /** + * @ignore + * @readonly + * @private + */ + name: 'ShowingTransitionSupport', + + /** + * A read-only property for checking whether we are in the middle of a transition. + * + * @type {Boolean} + * @default false + * @readonly + * @public + */ + showingTransitioning: false, + + /** + * The amount of time the "showing" transition takes to complete in milliseconds. + * + * @type {Number} + * @default undefined + * @public + */ + showingDuration: undefined, + + /** + * The amount of time the "hiding" transition takes to complete in milliseconds. + * + * @type {Number} + * @default undefined + * @public + */ + hidingDuration: undefined, + + /** + * The method to fire at the end of the "showing" transition. This may be name of a method on the + * component, or a function. + * + * @type {String|Function} + * @default undefined + * @public + */ + shownMethod: undefined, + + /** + * The method to fire at the end of the "hiding" transition. This may be name of a method on the + * component, or a function. + * + * @type {String|Function} + * @default undefined + * @public + */ + hiddenMethod: undefined, + + /** + * The classname to apply for the "shown" (component is visible) resting state. + * + * @type {String} + * @default undefined + * @public + */ + shownClass: undefined, + + /** + * The classname to apply for the "hidden" (component is not visible) resting state. + * + * @type {String} + * @default 'hidden' + * @public + */ + hiddenClass: undefined, + + /** + * The classname to apply for the "hiding" (component has started the transition to the hidden + * state) transition state. + * + * @type {String} + * @default 'hiding' + * @public + */ + hidingClass: undefined, + + /** + * The classname to apply for the "showing" (component has started the transition to the shown + * state) transition state. + * + * @type {String} + * @default 'showing' + * @public + */ + showingClass: undefined, + + /** + * Initializes the defaults, and prepares the component with its classes in case of initially + * "showing: false". + * + * @method + * @private + */ + create: kind.inherit(function (sup) { + return function () { + sup.apply(this, arguments); + + this.showingDuration = (this.showingDuration === undefined) ? null : this.showingDuration; + this.hidingDuration = (this.hidingDuration === undefined) ? null : this.hidingDuration; + this.shownMethod = (this.shownMethod === undefined) ? null : this.shownMethod; + this.hiddenMethod = (this.hiddenMethod === undefined) ? null : this.hiddenMethod; + this.shownClass = (this.shownClass === undefined) ? 'shown' : this.shownClass; + this.hiddenClass = (this.hiddenClass === undefined) ? 'hidden' : this.hiddenClass; + this.hidingClass = (this.hidingClass === undefined) ? 'hiding' : this.hidingClass; + this.showingClass = (this.showingClass === undefined) ? 'showing' : this.showingClass; + if (this.shownMethod) this.shownMethodChanged(); + if (this.hiddenMethod) this.hiddenMethodChanged(); + this.showingChanged(); + }; + }), + + /** + * Overrides the showingChanged handler to add support for transitions at the right times and + * places. + * + * @method + * @private + */ + showingChanged: kind.inherit(function (sup) { + return function (sender, ev) { + var args = arguments; + + // Prepare our visual state + this.applyStyle('display', null); + this.applyStyle('visibility', null); + if (this.showing) { + // Reset our state classes, in case we switched mid-stream + this.removeClass(this.hidingClass); + this.removeClass(this.hiddenClass); + sup.apply(this, args); + if (this.showingDuration && this.hasNode()) { + this.set('showingTransitioning', true); + // Start transition: Apply a class and start a timer. + // When timer finishes, run the exit function, + // remove the transitioning class + // and add the final-state class + this.addClass(this.showingClass); + this.startJob('showingTransition', function () { + this.removeClass(this.showingClass); + this.addClass(this.shownClass); + this.set('showingTransitioning', false); + utils.call(this._shownMethodScope, this.shownMethod); // Run the supplied method. + }, this.showingDuration); + } else { + // No transition, just a shown class. + this.stopJob('showingTransition'); + this.addClass(this.shownClass); + utils.call(this._shownMethodScope, this.shownMethod); // Run the supplied method. + } + } else { + // Reset our state classes, in case we switched mid-stream + this.removeClass(this.showingClass); + this.removeClass(this.shownClass); + if (this.hidingDuration && this.hasNode()) { + this.set('showingTransitioning', true); + this.addClass(this.hidingClass); + this.startJob('showingTransition', function () { + this.removeClass(this.hidingClass); + this.addClass(this.hiddenClass); + this.set('showingTransitioning', false); + sup.apply(this, args); + utils.call(this._hiddenMethodScope, this.hiddenMethod); // Run the supplied method. + this.applyStyle('visibility', 'hidden'); + this.applyStyle('display', null); + }, this.hidingDuration); + } else { + // No transition, just a hidden class. + this.stopJob('showingTransition'); + this.addClass(this.hiddenClass); + sup.apply(this, args); + utils.call(this._hiddenMethodScope, this.hiddenMethod); // Run the supplied method. + this.applyStyle('visibility', 'hidden'); + this.applyStyle('display', null); + } + } + }; + }), + showingDurationChanged: function () { + if (!this.showingDuration) this.stopJob('showingTransition'); + }, + hidingDurationChanged: function () { + if (!this.hidingDuration) this.stopJob('showingTransition'); + }, + shownMethodChanged: function () { + // checking if the actual method exists to workaround hasOwnProperty issues due to the + // mechanism we use for assigning mixin defaults + this._shownMethodScope = this[this.shownMethod] ? this : this.getInstanceOwner(); + }, + hiddenMethodChanged: function () { + // checking if the actual method exists to workaround hasOwnProperty issues due to the + // mechanism we use for assigning mixin defaults + this._hiddenMethodScope = this[this.hiddenMethod] ? this : this.getInstanceOwner(); + } +}; + +module.exports = ShowingTransitionSupport; diff --git a/src/SlideViewLayout/SlideViewLayout.js b/src/SlideViewLayout/SlideViewLayout.js new file mode 100644 index 000000000..6e9d21e12 --- /dev/null +++ b/src/SlideViewLayout/SlideViewLayout.js @@ -0,0 +1,176 @@ +/** +* Exports the {@link module:enyo/SlideViewLayout~SlideViewLayout} kind. +* +* @module enyo/SlideViewLayout +* @wip +*/ + +var + dom = require('enyo/dom'), + kind = require('enyo/kind'); + +var + TransitionViewLayout = require('../TransitionViewLayout'); + +/** +* Slides views in from the right or top and out the left or bottom. +* +* Order of operations: +* * `prepareTransition()` +* Applies directional CSS classes (forward or back) to each view based on the animation +* direction. For the becoming-active view, this positions it offscreen. For the becoming-inactive +* view, the directional class is overridden by `active` which keeps it onscreen. Calculates the +* `dragDuration` used as the transition duration when the drag is released. +* * `transition()` +* Sets the transition duration to either its `duration` or a lesser amount to complete the +* animation if the transition happens as a result of a drag operation. If a transform was applied +* during a drag, it is removed. The result is a transition from the currently dragged position +* set by inline style to the final position set by the CSS classes. +* * `completeTransition()` +* Removes the directional classes and resets the `dragDuration` +* +* @class SlideViewLayout +* @extends module:enyo/TransitionViewLayout~TransitionViewLayout +* @wip +* @public +*/ +module.exports = kind( + /** @lends module:enyo/SlideViewLayout~SlideViewLayout.prototype */ { + /** + * @private + */ + kind: TransitionViewLayout, + + /** + * @private + */ + layoutClass: 'enyo-viewlayout enyo-viewlayout-slide', + + /** + * @private + */ + constructor: function () { + TransitionViewLayout.prototype._constructor.apply(this, arguments); + if (this.container.layoutCover) this.container.addClass('cover'); + }, + + /** + * @private + */ + addRemoveDirection: function (view, addRemove, invert) { + var direction = invert ? -this.container.direction : this.container.direction, + className = direction == 1 ? 'forward' : 'back'; + view.addRemoveClass(className, addRemove); + }, + + /** + * @private + */ + transform: function (view, px) { + var v, + isHorizontal = this.container.orientation == 'horizontal'; + + if (px === null) { + v = null; + } else { + v = isHorizontal ? px + 'px, 0, 0' : '0, ' + px + 'px, 0'; + } + dom.transformValue(view, 'translate3d', v); + }, + + /** + * @private + */ + drag: function (event) { + var px, + c = this.container, + bounds = c.dragBounds, + isHorizontal = c.orientation == 'horizontal', + size = isHorizontal ? bounds.width : bounds.height, + delta = event.delta; + + if (event.delta < 0 && event.delta < -size) { + this.overDrag = true; + delta = -size; + } + else if (event.delta > 0 && event.delta > size) { + this.overDrag = true; + delta = size; + } + else { + this.overDrag = false; + } + + TransitionViewLayout.prototype.drag.apply(this, arguments); + this.transform(c.active, delta); + if (c.dragView) { + px = this.container.layoutCover ? 0 : size * event.direction + delta; + this.transform(c.dragView, px); + } + }, + + /** + * Applies directional CSS classes to initially position the views. + * + * @private + */ + prepareTransition: function (was, is) { + var c = this.container; + TransitionViewLayout.prototype.prepareTransition.apply(this, arguments); + if (is) this.addRemoveDirection(is, true); + if (was) this.addRemoveDirection(was, true, true); + + if (this.container.layoutCover) { + this.stationaryView = c.direction == 1 && was + || c.direction == -1 && is; + if (this.stationaryView) this.stationaryView.addClass('stationary'); + } + }, + + /** + * Sets the transition duration, + * + * @private + */ + transition: function (was, is) { + var dir; + + TransitionViewLayout.prototype.transition.apply(this, arguments); + if (was && was != this.dragView) { + this.transform(was, null); + } + if (is) { + this.addRemoveDirection(is, false); + if (is != this.dragView) this.transform(is, null); + } + + // If the user drags the entire view off screen, it won't animate so we won't see the CSS + // transition event. + if (this.overDrag) { + if (was) this.setTransitionComplete('from'); + if (is) this.setTransitionComplete('to'); + } + // when using layoutCover, one view doesn't transition so the ontransitionend doesn't fire + // to account for that, set a timeout of the same duration to manually clean up. The + // exception being when dismissing the ViewManager and there is no becoming-active view. + else if (this.stationaryView) { + dir = this.getTransitionDirection(this.stationaryView); + if (dir) this.setTransitionComplete(dir); + } + }, + + /** + * Removes the directional CSS class from the deactivating panel + * + * @private + */ + completeTransition: function (was, is) { + if (is) this.addRemoveDirection(is, false); + if (was) this.addRemoveDirection(was, false, true); + if (this.stationaryView) { + this.stationaryView.removeClass('stationary'); + this.stationaryView = null; + } + TransitionViewLayout.prototype.completeTransition.apply(this, arguments); + } +}); diff --git a/src/SlideViewLayout/SlideViewLayout.less b/src/SlideViewLayout/SlideViewLayout.less new file mode 100644 index 000000000..70fc689c4 --- /dev/null +++ b/src/SlideViewLayout/SlideViewLayout.less @@ -0,0 +1,48 @@ +.enyo-viewlayout-slide { + .pre(@prop, @val) { + -webkit-@{prop}: @val; + @{prop}: @val; + } + > .enyo-view { + .pre(transform, translate3d(-100%, 0, 0)); + .pre(transition-property, none); + .pre(transition-timing-function, linear); + } + + &:not(.dragging) > .enyo-view.transitioning { + -webkit-transition-property: -webkit-transform; + transition-property: transform; + } + + &.horizontal > .enyo-view { + &.forward { .pre(transform, translate3d(100%, 0, 0)); } + &.back { .pre(transform, translate3d(-100%, 0, 0)); } + &.active { .pre(transform, translate3d(0, 0, 0)); } + } + + &.vertical > .enyo-view { + &.forward { .pre(transform, translateY(0, -100%, 0)); } + &.back { .pre(transform, translateY(0, 100%, 0)); } + &.active { .pre(transform, translate3d(0, 0, 0)); } + } + + &.cover { + // in cover mode, promote all views up a bit + > .enyo-view { + z-index: 5; + // unless it's the stationary view which should always remain behind + &.stationary { + z-index: 0; + .pre(transform, translate3d(0, 0, 0)); + } + } + + // except when dragging where the active view is promoted + &.dragging { + > .enyo-view { + z-index: 0; + &.active { z-index: 10; } + } + } + } +} \ No newline at end of file diff --git a/src/SlideViewLayout/package.json b/src/SlideViewLayout/package.json new file mode 100644 index 000000000..45206a06e --- /dev/null +++ b/src/SlideViewLayout/package.json @@ -0,0 +1,6 @@ +{ + "main": "SlideViewLayout.js", + "styles": [ + "SlideViewLayout.less" + ] +} \ No newline at end of file diff --git a/src/Source.js b/src/Source.js index a01798084..1f41c1b56 100644 --- a/src/Source.js +++ b/src/Source.js @@ -10,13 +10,6 @@ var 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 = {}; /** @@ -29,19 +22,19 @@ var sources = {}; */ 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 @@ -53,11 +46,11 @@ var Source = module.exports = kind( constructor: function (props) { if (props) this.importProps(props); // automatic coersion of name removing prefix - this.name || (this.name = this.kindName.replace(/^(.*)\./, "")); + 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` @@ -72,7 +65,7 @@ var Source = module.exports = kind( 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` @@ -87,12 +80,12 @@ var Source = module.exports = kind( 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 + * instead destroy itself and be removed from [sources]{@link module:enyo/Source~Source.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 @@ -101,7 +94,7 @@ var Source = module.exports = kind( * `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) { @@ -109,7 +102,7 @@ var Source = module.exports = kind( 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 @@ -124,21 +117,21 @@ var Source = module.exports = kind( 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 @@ -152,11 +145,11 @@ var Source = module.exports = kind( * 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 +* `enyo/Source`. The instance will automatically be added to the +* [sources]{@link module:enyo/Source~Source.sources} [object]{@glossary Object} and may be * referenced by its `name`. * -* @name enyo.Source.create +* @name module:enyo/Source~Source.create * @static * @method * @param {Object} props - The properties to pass to the constructor for the requested @@ -166,110 +159,131 @@ var Source = module.exports = kind( */ Source.create = function (props) { var Ctor = (props && props.kind) || this; - + if (typeof Ctor == 'string') Ctor = kind.constructorForKind(Ctor); - + return new Ctor(props); }; /** +* @name module:enyo/Source~Source.concat * @static +* @method * @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; }; /** +* @name module:enyo/Source~Source.execute * @static +* @method * @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; - + msg, + ret; + 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); + + ret = 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; - + var i, + src; + + ret = []; + + for (i = 0; i < source.length; i++) { + nom = source[i]; + 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); + + ret.push(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); + + ret = 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); + + ret = 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); } + + return ret; }; +/** +* All of the known, instanced [sources]{@link module:enyo/Source~Source}, by name. +* @name module:enyo/Source~Source.sources +* @type {Object} +* @public +* @readonly +*/ Source.sources = sources; + diff --git a/src/SpriteAnimation/SpriteAnimation.js b/src/SpriteAnimation/SpriteAnimation.js index 82fea364d..e23bb70a0 100644 --- a/src/SpriteAnimation/SpriteAnimation.js +++ b/src/SpriteAnimation/SpriteAnimation.js @@ -295,7 +295,7 @@ module.exports = kind( */ updateKeyframes: function () { if (this.useCssAnimation) { - this.$.spriteImage.set('stylesheetContent', this._generateKeyframes()); + this.$.spriteImage.set('stylesheetContent', this._generateKeyframes('-webkit-') + this._generateKeyframes('-moz-') + this._generateKeyframes()); } else { this._generatePositionList(); } @@ -349,6 +349,7 @@ module.exports = kind( start: function () { if (this.useCssAnimation) { this.$.spriteImage.applyStyle('-webkit-animation-name', this.get('animationName')); + this.$.spriteImage.applyStyle('-moz-animation-name', this.get('animationName')); this.$.spriteImage.applyStyle('animation-name', this.get('animationName')); } else { this._intervalHandle = scope.setInterval(this.bindSafely(this._nextFrame, this), this.get('frameLength')); @@ -363,6 +364,7 @@ module.exports = kind( */ stop: function () { this.$.spriteImage.applyStyle('-webkit-animation-name', null); + this.$.spriteImage.applyStyle('-moz-animation-name', null); this.$.spriteImage.applyStyle('animation-name', null); scope.clearInterval(this._intervalHandle); this._loopCount = 0; @@ -373,6 +375,7 @@ module.exports = kind( */ stopAtEndChanged: function() { this.$.spriteImage.applyStyle('-webkit-animation-fill-mode', this.get('stopAtEnd') ? 'forwards' : null); + this.$.spriteImage.applyStyle('-moz-animation-fill-mode', this.get('stopAtEnd') ? 'forwards' : null); this.$.spriteImage.applyStyle('animation-fill-mode', this.get('stopAtEnd') ? 'forwards' : null); }, @@ -409,6 +412,7 @@ module.exports = kind( iterations = parseInt(this.get('iterationCount'), 10) || null; // strings like "infinite" will be converted to null this.$.spriteImage.applyStyle('-webkit-transform', 'translate3d('+ x +'px, '+ y +'px, 0)'); + this.$.spriteImage.applyStyle('-moz-transform', 'translate3d('+ x +'px, '+ y +'px, 0)'); this.$.spriteImage.applyStyle('transform', 'translate3d('+ x +'px, '+ y +'px, 0)'); if (fi + 1 >= this._positionList.length - 1) { @@ -448,8 +452,10 @@ module.exports = kind( iterations = (iterations && iterations !== 0) ? iterations : 'infinite'; this.$.spriteImage.applyStyle('-webkit-animation-timing-function', 'steps(' + steps + ', start)'); + this.$.spriteImage.applyStyle('-moz-animation-timing-function', 'steps(' + steps + ', start)'); this.$.spriteImage.applyStyle('animation-timing-function', 'steps(' + steps + ', start)'); this.$.spriteImage.applyStyle('-webkit-animation-iteration-count', iterations); + this.$.spriteImage.applyStyle('-moz-animation-iteration-count', iterations); this.$.spriteImage.applyStyle('animation-iteration-count', iterations); this.durationChanged(); }, @@ -457,7 +463,8 @@ module.exports = kind( /** * @private */ - _generateKeyframes: function () { + _generateKeyframes: function (prefix) { + prefix = prefix || ''; var o, width = this.get('width'), height = this.get('height'), @@ -467,7 +474,7 @@ module.exports = kind( kfStr = '', outer = (horiz ? rows : cols); - kfStr += '@-webkit-keyframes '+ this.get('animationName') +' {\n'; + kfStr += '@'+ prefix +'keyframes '+ this.get('animationName') +' {\n'; for (o = 0; o < outer; o++) { kfStr += this._generateKeyframe( // percent @@ -475,7 +482,8 @@ module.exports = kind( // startX horiz ? width : (-width * o), // startY - horiz ? (-height * o) : height + horiz ? (-height * o) : height, + prefix ); kfStr += this._generateKeyframe( // percent @@ -483,7 +491,8 @@ module.exports = kind( // endX horiz ? ((-width * cols) + width) : (-width * o), // endY - horiz ? (-height * o) : ((-height * rows) + height) + horiz ? (-height * o) : ((-height * rows) + height), + prefix ); } kfStr += '}\n'; @@ -493,8 +502,15 @@ module.exports = kind( /** * @private */ - _generateKeyframe: function (percent, x, y) { - return (Math.ceil(percent*1000000) / 10000) +'% { -webkit-transform: translate3d('+ x +'px, '+ y +'px, 0); transform: translate3d('+ x +'px, '+ y +'px, 0); }\n'; + _generateKeyframe: function (percent, x, y, prefix) { + var outStr = (Math.ceil(percent*1000000) / 10000) +'% { ', + transform = 'transform: translate3d('+ x +'px, '+ y +'px, 0); '; + if (prefix) { + // add the prefix and the standard + outStr+= prefix + transform; + } + outStr+= transform +'}\n'; + return outStr; }, _generatePositionList: function () { diff --git a/src/Store.js b/src/Store.js index a0cd310a4..39ba89c36 100644 --- a/src/Store.js +++ b/src/Store.js @@ -30,7 +30,7 @@ var BaseStore = kind({ * should be included in the filtered set for the [find()]{@link module:enyo/Store~Store#find} * method. * -* @callback enyo.Store~Filter +* @callback module:enyo/Store~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`. @@ -39,7 +39,7 @@ var BaseStore = kind({ /** * The configuration options for the [find()]{@link module:enyo/Store~Store#find} method. * -* @typedef {Object} enyo.Store~FindOptions +* @typedef {Object} module:enyo/Store~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 @@ -304,8 +304,7 @@ var Store = kind( * @see module:enyo/Model~Model * @see module:enyo/Collection~Collection * @see module:enyo/RelationalModel~RelationalModel -* @type enyo.Store -* @memberof enyo +* @type module:enyo/Store~Store * @public */ module.exports = new Store(); diff --git a/src/StylesheetSupport.js b/src/StylesheetSupport.js index 7370fe900..c25bb85a6 100644 --- a/src/StylesheetSupport.js +++ b/src/StylesheetSupport.js @@ -1,5 +1,5 @@ /** -* Exports the {@link module:enyo/StylesheetSupport} mixin. +* Exports the {@link module:enyo/StylesheetSupport~StylesheetSupport} mixin. * @module enyo/StylesheetSupport */ @@ -12,7 +12,7 @@ var Style = require('./Style'); /** -* The {@link module:enyo/StylesheetSupport} {@glossary mixin} is used to add a +* The {@link module:enyo/StylesheetSupport~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). @@ -20,7 +20,7 @@ var * @mixin * @public */ -module.exports = { +var StylesheetSupport = { /** * @private @@ -101,3 +101,5 @@ module.exports = { return this.getId() + this._stylesheet_id_suffix; } }; + +module.exports = StylesheetSupport; diff --git a/src/SystemMonitor.js b/src/SystemMonitor.js index 7e5564f09..f3fb399e3 100644 --- a/src/SystemMonitor.js +++ b/src/SystemMonitor.js @@ -2,6 +2,7 @@ require('enyo'); /** * Returns the SystemMonitor singleton +* @wip * @module enyo/SystemMonitor */ diff --git a/src/Table/Table.js b/src/Table/Table.js index 5770be3ff..847f457e7 100644 --- a/src/Table/Table.js +++ b/src/Table/Table.js @@ -12,10 +12,7 @@ var TableRow = require('../TableRow'); /* -* TODO: Won't work in IE8 because we can't set innerHTML on table elements. We'll need to fall -* back to divs with table display styles applied. -* -* Should also facade certain useful table functionality (specific set TBD). +* TODO: Should facade certain useful table functionality (specific set TBD). */ /** diff --git a/src/TaskManagerSupport.js b/src/TaskManagerSupport.js index f7c8516db..38fd7f3ff 100644 --- a/src/TaskManagerSupport.js +++ b/src/TaskManagerSupport.js @@ -1,5 +1,6 @@ /** * Exports the {@link module:enyo/TaskManagerSupport~TaskManagerSupport} mixin +* @wip * @module enyo/TaskManagerSupport */ @@ -14,7 +15,10 @@ var Priorities = PriorityQueue.Priorities, BackgroundTaskManager = require('./BackgroundTaskManager'); -/** @mixin */ +/** +* @mixin +* @wip +*/ var TaskManagerSupport = { /** diff --git a/src/TransitionScrollStrategy.js b/src/TransitionScrollStrategy.js index 0c05cc2a8..90a7b8e04 100644 --- a/src/TransitionScrollStrategy.js +++ b/src/TransitionScrollStrategy.js @@ -2,6 +2,7 @@ require('enyo'); /** * Contains the declaration for the {@link module:enyo/TransitionScrollStrategy~TransitionScrollStrategy} kind. +* @wip * @module enyo/TransitionScrollStrategy */ @@ -60,6 +61,7 @@ var * * @class TransitionScrollStrategy * @extends module:enyo/TouchScrollStrategy~TouchScrollStrategy +* @wip * @protected */ var TransitionScrollStrategy = module.exports = kind( diff --git a/src/TransitionViewLayout.js b/src/TransitionViewLayout.js new file mode 100644 index 000000000..ee6956cc2 --- /dev/null +++ b/src/TransitionViewLayout.js @@ -0,0 +1,124 @@ +/** +* @module enyo/TransitionViewLayout +* @wip +*/ + +var + kind = require('enyo/kind'); + +var + ViewLayout = require('./ViewLayout'); + + +/** +* Slides views in from the right or top and out the left or bottom. +* +* Order of operations: +* * `prepareTransition()` +* Applies directional CSS classes (forward or back) to each view based on the animation +* direction. For the becoming-active view, this positions it offscreen. For the becoming-inactive +* view, the directional class is overridden by `active` which keeps it onscreen. Calculates the +* `dragDuration` used as the transition duration when the drag is released. +* * `transition()` +* Sets the transition duration to either its `duration` or a lesser amount to complete the +* animation if the transition happens as a result of a drag operation. If a transform was applied +* during a drag, it is removed. The result is a transition from the currently dragged position +* set by inline style to the final position set by the CSS classes. +* * `completeTransition()` +* Removes the directional classes and resets the `dragDuration` +* +* @class TransitionViewLayout +* @extends module:enyo/ViewLayout~ViewLayout +* @public +* @wip +*/ +module.exports = kind( + /** @lends module:enyo/TransitionViewLayout~TransitionViewLayout.prototype */ { + /** + * @private + */ + kind: ViewLayout, + + /** + * Sets the duration of the transition. Imported from the value of `layoutDuration` on the + * container on which the layout is applied. + * + * @type {Number} + * @default 300 + * @public + */ + duration: 300, + + /** + * @private + */ + constructor: function () { + ViewLayout.prototype._constructor.apply(this, arguments); + this.duration = this.container.layoutDuration || this.duration; + }, + + /** + * @private + */ + drag: function (event) { + ViewLayout.prototype.drag.apply(this, arguments); + this.dragDuration = this.duration - Math.round(event.percentDelta * this.duration, 2); + }, + + /** + * Sets the transition duration, + * + * @private + */ + transition: function (was, is) { + ViewLayout.prototype.transition.apply(this, arguments); + if (was) { + was.addClass('transitioning'); + this.applyTransitionDuration(was, this.dragDuration || this.duration); + } + if (is) { + is.addClass('transitioning'); + this.applyTransitionDuration(is, this.dragDuration || this.duration); + } + }, + + /** + * Removes the directional CSS class from the deactivating view + * + * @private + */ + completeTransition: function (was, is) { + if (was) was.removeClass('transitioning'); + if (is) is.removeClass('transitioning'); + this.dragDuration = null; + ViewLayout.prototype.completeTransition.apply(this, arguments); + }, + + /** + * @private + */ + applyTransitionDuration: function (view, duration) { + view.applyStyle('-webkit-transition-duration', duration + 'ms'); + view.applyStyle('transition-duration', duration + 'ms'); + }, + + /** + * @private + */ + handlers: { + ontransitionend: 'handleTransitioned' + }, + + /** + * @private + */ + handleTransitioned: function (sender, event) { + var dir, + view = event.originator; + if (view && view.container == this.container) { + dir = this.getTransitionDirection(view); + if (dir) this.setTransitionComplete(dir); + return true; + } + } +}); diff --git a/src/UiComponent.js b/src/UiComponent.js index 29d9e2c03..807e7376c 100644 --- a/src/UiComponent.js +++ b/src/UiComponent.js @@ -13,6 +13,15 @@ var var Component = require('./Component'); +/** +* The configurable options used by {@link module:enyo/UiComponent~UiComponent} when updating +* components. +* +* @typedef {Object} enyo/UiComponent~UiComponent~UpdateComponentsOptions +* @property {Boolean} [silent] - If `true`, component properties will be updated silently i.e. they +* will be set directly, rather than via the generic `set` method. +*/ + /** * {@link module:enyo/UiComponent~UiComponent} implements a container strategy suitable for presentation layers. * @@ -34,13 +43,15 @@ var UiComponent = module.exports = kind( */ kind: Component, - statics: { + statics: + /** @lends module:enyo/UiComponent~UiComponent */ { /** - * 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. + * 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 module:enyo/UiComponent~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'] @@ -227,36 +238,43 @@ var UiComponent = module.exports = kind( }; }), - /** + /** * 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 + * @param {Object[]} props - An array of kind definitions to be set as the child components of * this component. + * @param {Object} [ext] - Additional properties to be supplied as defaults for components, when + * being created or recreated. These properties have no bearing on the diff computation of the + * child components. + * @param {module:enyo/UiComponent~UpdateComponentsOptions} [opts] - Additional options for how + * the update operation should behave. * @returns {Boolean} - Whether or not the component should be re-rendered. + * @wip * @public */ - updateComponents: function (comps) { + updateComponents: function (props, ext, opts) { var allStatefulKeys = {}, - isChanged = this.computeComponentsDiff(comps, allStatefulKeys), - comp, controls, control, keys, key, idxKey, idxComp, kind; + isChanged = this.computeComponentsDiff(props, allStatefulKeys), + prop, controls, control, keys, key, idxKey, idxProp, kind; if (isChanged) { this.destroyClientControls(); - this.createComponents(comps); + this.createComponents(props, ext); 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 (idxProp = 0; idxProp < props.length; idxProp++) { + prop = props[idxProp]; + control = controls[idxProp]; + kind = prop.kind || this.defaultKind; + keys = allStatefulKeys[idxProp]; 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]); + if (prop[key] != control[key]) { + if (opts && opts.silent) control[key] = prop[key]; + else control.set(key, prop[key]); } } } @@ -690,7 +708,7 @@ var UiComponent = module.exports = kind( ); } }, - + /** * @method * @private diff --git a/src/VerticalDelegate.js b/src/VerticalDelegate.js index d50fd09ce..ace9b3cf1 100644 --- a/src/VerticalDelegate.js +++ b/src/VerticalDelegate.js @@ -194,6 +194,7 @@ module.exports = { view = page.children[i]; view.teardownRender(); view.canGenerate = false; + view.set('model', null); } // update the entire page at once - this removes old nodes and updates // to the correct ones @@ -516,17 +517,12 @@ module.exports = { * @private */ adjustBuffer: function (list) { - var pc = this.pageCount(list), - ds = this.defaultPageSize(list), + var cs = this.childSize(list), + count = list.collection ? list.collection.length : 0, bs = 0, sp = list.psizeProp, ss = list.ssizeProp, - n = list.$.buffer.node || list.$.buffer.hasNode(), p; + n = list.$.buffer.node || list.$.buffer.hasNode(); if (n) { - if (pc !== 0) { - for (var i=0; i .enyo-view, & > & { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + } +} \ No newline at end of file diff --git a/src/ViewLayout/package.json b/src/ViewLayout/package.json new file mode 100644 index 000000000..6927b4e0a --- /dev/null +++ b/src/ViewLayout/package.json @@ -0,0 +1,6 @@ +{ + "main": "ViewLayout.js", + "styles": [ + "ViewLayout.less" + ] +} \ No newline at end of file diff --git a/src/ViewManager/ScrimSupport.js b/src/ViewManager/ScrimSupport.js new file mode 100644 index 000000000..7fe6946b4 --- /dev/null +++ b/src/ViewManager/ScrimSupport.js @@ -0,0 +1,39 @@ +var + kind = require('../kind'), + utils = require('enyo/utils'); + + +/** +* A mixin for use by ViewManager. Applies a user-defined `scrimConfig` if present. +* +* @name module:enyo/ViewManager~ScrimSupport +* @mixin +* @wip +* @protected +*/ +var ScrimSupport = { + /** + * @private + */ + initComponents: kind.inherit(function (sup) { + return function () { + var config = {name: 'scrim', classes: 'enyo-viewmanager-scrim enyo-fit'}; + sup.apply(this, arguments); + + // override defaults with user-provided scrimConfig + if (this.scrimConfig) utils.mixin(config, this.scrimConfig); + this.createComponent(config, {isChrome: true}); + + // monitor the relevant events to hide and show the scrim + // @todo - may need to monitor active manager counts + this.on('manager-manage', function (sender, name, event) { + this.$[config.name].addClass('showing'); + }, this); + this.on('manager-dismiss', function (sender, name, event) { + this.$[config.name].removeClass('showing'); + }, this); + }; + }) +}; + +module.exports = ScrimSupport; diff --git a/src/ViewManager/ViewManager.js b/src/ViewManager/ViewManager.js new file mode 100644 index 000000000..7ca276dfc --- /dev/null +++ b/src/ViewManager/ViewManager.js @@ -0,0 +1,1153 @@ +/** +* ViewManager module +* +* @module enyo/ViewManager +* @wip +*/ + +/** +* Fires after a view has been created and rendered, if necessary, but before it transitions into +* viewport. +* +* @event module:enyo/ViewManager~ViewManager#activate +* @property {module:enyo/Control~Control} view The target view for the event +* @property {Boolean} dragging `true` for `activate` or `deactivate` events that occur as a result +* of dragging +* @property {Number} direction Either 1 representing forward or -1 representing backward +* @public +*/ + +/** +* Fires after a view has been created, rendered, and transitioned into the viewport. +* +* @event module:enyo/ViewManager~ViewManager#activated +* @type {Object} +* @property {module:enyo/Control~Control} view The target view for the event +* @property {Boolean} dragging `true` for `activate` or `deactivate` events that occur as a result +* of dragging +* @property {Number} direction Either 1 representing forward or -1 representing backward +* @public +*/ + +/** +* Fires on the previously active view when a new view has become active but before it has +* transitioned out of the viewport +* +* @event module:enyo/ViewManager~ViewManager#deactivate +* @property {module:enyo/Control~Control} view The target view for the event +* @property {Boolean} dragging `true` for `activate` or `deactivate` events that occur as a result +* of dragging +* @property {Number} direction Either 1 representing forward or -1 representing backward +* @public +*/ + +/** +* Fires on the previously active view when it has transitioned out of the viewport and been torn +* down. +* +* @event module:enyo/ViewManager~ViewManager#deactivated +* @property {module:enyo/Control~Control} view The target view for the event +* @property {Boolean} dragging `true` for `activate` or `deactivate` events that occur as a result +* of dragging +* @property {Number} direction Either 1 representing forward or -1 representing backward +* @public +*/ + +/** +* Fires on the previously active view when it has transitioned out of the viewport and been torn +* down. +* +* @event module:enyo/ViewManager~ViewManager#deactivated +* @property {module:enyo/Control~Control} view The target view for the event +* @property {Boolean} dragging `true` for `activate` or `deactivate` events that occur as a result +* of dragging +* @property {Number} direction Either 1 representing forward or -1 representing backward +* @public +*/ + +/** +* Fires when this ViewManager is [activated]{@link module:enyo/ViewManager~ViewManager#activate} by +* its [manager]{@link module:enyo/ViewManager~ViewManager#manager} and ready to activate its own +* views. +* +* @event module:enyo/ViewManager~ViewManager#manage +* @public +*/ + +/** +* Fires when this ViewManager dimissal is initiated -- either by a call to +* [dismiss()]{@link module:enyo/ViewManager~ViewManager#dismiss} or when a +* [dismissable]{@link module:enyo/ViewManager~ViewManager#dismissable} ViewManager is on its first +* view and is dragged beyond the +* [dragThreshold]{@link module:enyo/ViewManager~ViewManager#dragThreshold}. +* +* @event module:enyo/ViewManager~ViewManager#dismiss +* @property {Boolean} dragging `true` for when dismissing as a result of dragging +* @public +*/ + +/** +* Fires when a ViewManager completes its dismissal. +* +* @event module:enyo/ViewManager~ViewManager#dismissed +* @public +*/ + +var + animation = require('../animation'), + kind = require('../kind'), + utils = require('../utils'), + Control = require('../Control'), + EventEmitter = require('../EventEmitter'), + SlideViewLayout = require('../SlideViewLayout'), + rAF = animation.requestAnimationFrame; + +var + ScrimSupport = require('./ScrimSupport'); + +var viewCount = 0; + +/** +* ViewManager manages views. +* +* ``` +* module.exports = kind({ +* kind: ViewManager, +* classes: 'contacts', +* +* // All the views are declared in the common components block +* components: [ +* {name: 'history', components: [ +* {content: 'History'}, +* {kind: Button, content: 'Next', ontap: 'next'} +* ]}, +* {name: 'dialer', active: true, components: [ +* {content: 'Dialer'}, +* {kind: Button, content: 'Inline Example', ontap: 'activateInline'}, +* {kind: Button, content: 'Previous', ontap: 'previous'}, +* {kind: Button, content: 'Next', ontap: 'next'} +* ]}, +* {name: 'contacts', components: [ +* {content: 'Contacts'}, +* {kind: Button, content: 'Previous', ontap: 'previous'}, +* {kind: Button, content: 'Add A Contact', ontap: 'pushAddContactView'} +* ]} +* ], +* +* // Child ViewManagers are declared within a new managers block +* managers: [ +* {name: 'add', kind: AddContact}, +* {name: 'inline', kind: ViewManager, floating: true, components: [ +* {content: 'view1', style: 'background: #424242; color: #fff;', ontap: 'nextInline'}, +* {content: 'view2', style: 'background: #424242; color: #fff;', ontap: 'nextInline'}, +* {content: 'view3', style: 'background: #424242; color: #fff;', ontap: 'dismissInline'} +* ]} +* ], +* +* create: function () { +* ViewManager.prototype.create.apply(this, arguments); +* +* function log (sender, name, event) { +* console.log('Event', name, +* 'on view', event.view.name, +* event.dragging ? 'while dragging' : ''); +* } +* +* // ViewManager emits events to ease view initialization and teardown without concern for the +* // async nature of animations. +* +* // `view` has been activated but not animated +* this.on('activate', log); +* +* // `view` has been activate and animated +* this.on('activated', log); +* +* // `view` has been deactivated but not animated +* this.on('deactivate', log); +* +* // `view` has been deactivated and animated +* this.on('deactivated', log); +* }, +* +* // Activate and navigate the Inline ViewManager +* activateInline: function () { +* this.activate('inline'); +* }, +* nextInline: function () { +* this.$.inline.next(); +* }, +* dismissInline: function () { +* this.$.inline.dismiss(); +* }, +* +* // Activate the Contacts ViewManager +* pushAddContactView: function () { +* var view = this.activate('add'); +* view.set('model', new Model({ +* first: 'First Name', +* last: 'Last Name' +* })); +* } +* }); +* ``` +* +* @class ViewManager +* @extends module:enyo/Control~Control +* @ui +* @public +* @wip +*/ +var ViewMgr = kind( + /** @lends module:enyo/ViewManager~ViewManager.prototype */ { + + /** + * @private + */ + kind: Control, + + /** + * @private + */ + layoutKind: SlideViewLayout, + + /** + * @private + */ + mixins: [EventEmitter], + + /** + * @private + */ + animate: true, + + /** + * @private + */ + classes: 'enyo-viewmanager', + + // PUBLIC PROPERTIES + + /** + * Determines if and how the default view is activated. The default view is either the first + * view with a truthy `active` member or the first view if none are marked active. + * + * * 'off' - No view is activated by default + * * 'create' - The default view is activated on create and not animated into position + * * 'render' - The default view is activated on render and animated into position + * * 'auto' - For floating ViewManagers, this is equivalent to 'render'. For non-floating + * ViewManagers, this is equivalent to 'create'. + * + * @type {String} + * @default auto + * @public + */ + activateDefault: 'auto', + + /** + * Determines if the view can be dismissed by dragging. The ViewManager can be programmatically + * dismissed via dismiss() regardless of the value of this property. If the ViewManager is the + * root and does not have a `manager`, it cannot be dismissed by dragging or by `dismiss()`. + * + * * `true` - Can be dismissed + * * `false` - Cannot be dismissed + * * 'auto' - Can be dismissed if `floating` is `true` + * + * @type {Boolean|String} + * @default auto + * @public + */ + dismissable: 'auto', + + /** + * When `true`, the views can be dragged or flicked into and out of view. + * When `false`, the views cannot be dragged or flicked. + * When `drag`, the views can *only* be dragged and not flicked. + * When `flick`, the views can *only* be flicked and not dragged. + * + * @type {Boolean|String} + * @default true + * @public + */ + draggable: true, + + /** + * Percent a new view must be dragged into the viewport to be activated on drag release + * + * @type {Number} + * @default 25 + * @public + */ + dragThreshold: 25, + + /** + * If `true`, this ViewManager 'floats' over its parent `manager` + * + * @type {Boolean} + * @default false + * @public + */ + floating: false, + + /** + * If created within another ViewManager, `manager` will maintain a reference to that + * ViewManager which will be notified of activated, deactivated, dismiss, and dismissed events + * from this ViewManager. + * + * @type {module:enyo/ViewManager~ViewManager} + * @default null + * @public + */ + manager: null, + + /** + * @private + */ + managerChanged: function (was, is) { + if (was) this.off('*', was.managerEvent); + if (is) this.on('*', is.managerEvent); + }, + + /** + * When `draggable`, this constrains the drag to this direction. + * + * @type {String} + * @default horizontal + * @public + */ + orientation: 'horizontal', + + /** + * The number of views managed by this ViewManager. This member is observable but should be + * considered read-only. + * + * @type {Number} + * @default 0 + * @readOnly + * @public + */ + viewCount: 0, + + // PRIVATE PROPERTIES + + /** + * Active view + * + * @type {module:enyo/Control~Control} + * @private + */ + active: null, + + /** + * @private + */ + activeChanged: function (was, is) { + if (was) { + if (this.dragging) { + this.set('dragging', false); + this.releaseDraggedView = was.retainNode(); + } + this.emitViewEvent('deactivate', was); + } + }, + + /** + * Indicates the logical direction of a view activation. May be used by ViewLayouts to inform the + * direction of their animation + * + * @type {Number} + * @default 0 + * @private + */ + direction: 0, + + /** + * `true` when this ViewManager has been dismissed + * + * @type {Boolean} + * @default false + * @private + */ + dismissed: false, + + /** + * `true` when a drag gesture is in process + * + * @type {Boolean} + * @default false + * @private + */ + dragging: false, + + /** + * During a drag, contains a reference to the becoming-active view + * + * @private + */ + dragView: null, + + /** + * @type {Boolean} + */ + flicked: false, + + /** + * @private + */ + layoutKindChanged: function (was, is) { + Control.prototype.layoutKindChanged.apply(this, arguments); + if (this.layout && this.layout.on) { + this.layout.on('complete', this.handleLayoutComplete, this); + } + }, + + /** + * @private + */ + handlers: { + onflick: 'handleFlick', + ondown: 'handleDown', + ondragstart: 'handleDragStart', + ondrag: 'handleDrag', + ondragfinish: 'handleDragFinish' + }, + + /** + * @private + */ + create: function () { + // Set layoutCover for floating ViewManagers that haven't explicitly defined it + if (this.floating && this.layoutCover === undefined) this.layoutCover = true; + + this.on('*', this.notifyViews, this); + Control.prototype.create.apply(this, arguments); + + // cache a bound reference to the managerEvent handler + this.managerEvent = this.managerEvent.bind(this); + this.managerChanged(null, this.manager); + + if (this.floating) this.stack = []; + + if (this.activateDefault == 'create' || (this.activateDefault == 'auto' && !this.floating)) { + this.initFirstView(); + } + }, + + /** + * @private + */ + rendered: function () { + Control.prototype.rendered.apply(this, arguments); + if (this.activateDefault == 'render' || (this.activateDefault == 'auto' && this.floating)) { + this.initFirstView(); + } + this.set('dismissed', false); + }, + + /** + * @private + */ + initComponents: function () { + var managersOwner = this.hasOwnProperty('managers') ? this.getInstanceOwner() : this; + + // view configs or instances + this.views = []; + this.viewManagers = {}; + + // map of view name to index + this.viewNames = {}; + + // import kind and user components into this.views + this.importViewConfig(this.kindComponents, this); + this.importViewConfig(this.components, this.getInstanceOwner()); + this.importViewConfig(this.managers, managersOwner, true); + this.viewCount = this.views.length; + + // clean up references + this.components = this.kindComponents = null; + }, + + /** + * If a newly added control doesn't exist in the view or manager array, add it + * + * @private + */ + addControl: function (control, before) { + var viewIndex = this.viewNames[control.name]; + Control.prototype.addControl.apply(this, arguments); + + if (!control.isChrome && !(viewIndex || viewIndex === 0) && !this.viewManagers[control.name]) { + this.addView(control); + this.set('viewCount', this.views.length); + } + }, + + /** + * @private + */ + removeControl: function (control) { + var i, l, + index = this.views.indexOf(control); + + Control.prototype.removeControl.apply(this, arguments); + if (index >= 0) { + this.views.splice(index, 1); + this.viewNames[control.name] = null; + + for (i = index, l = this.views.length; i < l; i++) { + this.viewNames[this.views[i].name] = i; + } + this.set('viewCount', this.viewCount - 1); + } + }, + + /** + * Activates the initial view + * + * @private + */ + initFirstView: function () { + var name, view, + i = 0; + + if (this.views.length === 0) return; + + // find the first declared defaultView + while ((view = this.views[i++]) && !name) { + if (view.defaultView) { + name = view.name; + } + } + + name = name || this.views[0].name; + if (this.generated) { + this.activate(name); + } else { + view = this.getView(name); + this.activateImmediate(view); + } + }, + + /** + * Adds the list of components as views + * + * @param {Object[]|module:enyo/Control~Control[]} components List of components + * @param {module:enyo/Control~Control|null} [owner] Owner of components + * + * @private + */ + importViewConfig: function (components, owner, isManager) { + var c, + i = 0; + + while (components && (c = components[i++])) { + this.addView(c, owner, isManager); + } + }, + + /** + * Adds a new view to the view set + * + * @param {Object|module:enyo/Control~Control} view View config or instance + * @param {module:enyo/Control~Control|null} [owner] Optional owner of view. Defaults to this. + * + * @private + */ + addView: function (view, owner, isManager) { + var index, + isControl = view instanceof Control, + _view = isControl ? view : utils.clone(view), + name = _view.name = _view.name || 'view' + (++viewCount); + + owner = _view.owner || owner || this; + if (isControl) { + _view.set('owner', owner); + isManager = _view instanceof ViewMgr; + } else { + _view.owner = owner; + } + + if (isManager) { + // setting directly because the change handler is called manually during create + _view.manager = this; + this.viewManagers[name] = _view; + } else { + index = this.views.push(_view), + this.viewNames[name] = index - 1; + } + }, + + + /** + * Returns the index of the provided view. For fixed ViewManagers, this reflects the view's ordered + * position. For floating ViewManagers, this reflects the last occurence of the view in the stack. If + * the view isn't found, -1 is returned. + * + * @param {module:enyo/Control~Control} view + * @return {Number} Index of `view` + * @public + */ + indexOf: function (view) { + var name = view && view.name; + if (!name) return -1; + + return this.floating ? this.stack.lastIndexOf(name) : this.views.indexOf(view); + }, + + /** + * Returns the currently active view + * + * @return {module:enyo/Control~Control} + * @public + */ + getActive: function () { + return this.active; + }, + + /** + * Retrieves and creates, if necessary, a view or view manager by name + * + * @param {String} viewName Name of the view or view manager + * @return {module:enyo/Control~Control} View + * @public + */ + getView: function (viewName) { + var view = this.viewManagers[viewName], + index = this.viewNames[viewName]; + + // if it's a manager + if (view) { + // but not created, create it + if (!(view instanceof ViewMgr)) { + view = this.viewManagers[viewName] = this.createComponent(view); + } + } + // otherwise, it's probably a view + else { + view = this.views[index]; + // but it might need to be created too + if (view && !(view instanceof Control)) { + view = this.views[index] = this.createComponent(view); + view.addClass('enyo-view'); + } + } + + return view; + }, + + /** + * Navigates to the next view based on order of definition or creation + * + * @param {Object} [opts] Optional parameters to configure the activation + * @return {module:enyo/Control~Control} Activated view + * @public + */ + next: function (opts) { + var index = this.views.indexOf(this.active) + 1, + view = this.views[index]; + if (view) { + this.direction = 1; + return this.activate(view.name, opts); + } + }, + + /** + * Navigates to the previous view based on order of definition or creation + * + * @param {Object} [opts] Optional parameters to configure the activation + * @return {module:enyo/Control~Control} Activated view + * @public + */ + previous: function (opts) { + var index = this.views.indexOf(this.active) - 1, + view = this.views[index]; + if (view) { + this.direction = -1; + return this.activate(view.name, opts); + } + }, + + /** + * If this is a floating ViewManager, navigates back `count` views from the stack. + * + * @param {Number} [count] Number of views to pop off the stack. Defaults to 1. + * @param {Object} [opts] Optional parameters to configure the deactivation + * @return {module:enyo/Control~Control} Activated view + * @public + */ + back: function (count, opts) { + var name, + depth = this.stack.length; + if (this.floating && depth > 0) { + if (this.dragging) { + name = this.stack[0]; + } else { + count = count > depth ? depth : count || 1; + name = this.stack.splice(0, count).pop(); + } + this.direction = -1; + return this._activate(name, opts); + } + }, + + /** + * Determines if the ViewManager can be dragged in the provided direction + * + * @param {Number} direction -1 or 1 indicating the direction of the drag + * @param {String} [mode] When provided, requires `draggable` be `true` or the provided value + * @private + */ + canDrag: function (direction, mode) { + var index, + check = mode ? this.draggable === true || this.draggable == mode : this.draggable; + if (check) { + if (this.isDimissable() && direction == -1) { + return true; + } + else if (!this.floating) { + index = this.views.indexOf(this.active); + return (index > 0 && direction == -1) || + (index < this.views.length - 1 && direction == 1); + } + } + + return false; + }, + + /** + * @private + */ + determineDirection: function (view) { + var isIndex, wasIndex; + + // for a floating VM, the default direction is always forward + if (this.floating) { + this.direction = 1; + } + // fixed VMs direction is based on each view's ordered position + else { + isIndex = this.indexOf(view); + wasIndex = this.indexOf(this.active); + this.direction = wasIndex < isIndex ? 1 : -1; + } + }, + + /** + * Indicates if the view is dismissable via dragging + * + * @return {Boolean} + * @public + */ + isDimissable: function () { + return this.dismissable === true || (this.dismissable == 'auto' && this.floating); + }, + + /** + * Dismisses a view manager. If this is a root (manager-less) view manager, it cannot be + * dismissed. + * + * @param {Object} [opts] Optional parameters to configure the deactivation + * @public + */ + dismiss: function (opts) { + if (this.manager) { + this.direction = -1; + this.set('activationOptions', opts); + this.set('dismissed', true); + this.emit('dismiss', {dragging: false}); + this.set('active', null); + this.stack = []; + } + }, + + /** + * When any view event (activate, activated, deactivate, deactivated) fires, notify the view of + * its change of state by calling a method matching the event (e.g. activate()), if it exists. + * + * @private + */ + notifyViews: function (sender, name, event) { + var viewEvent = name == 'activate' || name == 'activated' + || name == 'deactivate' || name == 'deactivated'; + + // Any event for a view will have an event payload with a view property indicating the view + // that is changing. + if (event && event.view && viewEvent && utils.isFunction(event.view[name])) { + event.view[name](event); + } + }, + + /** + * @private + */ + emitViewEvent: function (name, view) { + this.emit(name, { + view: view, + dragging: this.dragging, + direction: this.direction + }); + }, + + /** + * @private + */ + managerEvent: function (viewManager, event, view) { + if (event == 'dismissed') this.managerDismissed(viewManager); + this.emit('manager-' + event, { + manager: viewManager + }); + }, + + /** + * Handles dismissal of child view managers + * + * @private + */ + managerDismissed: function (viewManager) { + this.teardownView(viewManager); + }, + + /** + * Activates a new view. + * + * For floating ViewManagers, the view will be added to the stack and can be removed by `back()`. + * + * @param {String} viewName Name of the view to activate + * @param {Object} [opts] Optional parameters to configure the activation + * @public + */ + activate: function (viewName, opts) { + var replace = !!(opts && opts.replace), + view = this._activate(viewName, opts); + if (!replace && view && !this.isManager(view) && this.active && this.floating) { + this.stack.unshift(this.active.name); + } + + return view; + }, + + /** + * Activates a view + * + * @private + */ + _activate: function (viewName, opts) { + var replace = !!(this.activationOptions && this.activationOptions.replace), + view = this.getView(viewName); + if (view) { + if (!this._toBeActivated) { + rAF(function () { + this.activateImmediate(this._toBeActivated, opts); + this._toBeActivated = null; + }.bind(this)); + } + else if (!replace && this.floating && !this.isManager(view)) { + this.stack.unshift(this._toBeActivated.name); + } + this.set('activationOptions', opts); + this._toBeActivated = view; + } + return view; + }, + + /** + * @private + */ + activateImmediate: function (view, opts) { + // render the activated view if not already + if (this.generated && !view.generated) { + view.set('canGenerate', true); + view.render(); + } + if (this.isManager(view)) { + view.emit('manage'); + } + else { + if (!this.direction) this.determineDirection(view); + this.emitViewEvent('activate', view); + if (!this.dragging) this.set('active', view); + } + }, + + /** + * @private + */ + deactivate: function (viewName) { + var view = this.getView(viewName); + if (view) rAF(this.deactivateImmediate.bind(this, view)); + return view; + }, + + /** + * @private + */ + deactivateImmediate: function (view) { + this.teardownView(view); + + if (!this.isManager(view)) this.emitViewEvent('deactivated', view); + if (!this.dragging && this.dismissed) this.emit('dismissed'); + }, + + /** + * Tears down a view or ViewManager if not flagged `persistent` + * + * @private + */ + teardownView: function (view) { + if (view.node && !view.persistent) { + if (!this.releaseDraggedView) { + view.node.remove(); + view.node = null; + } + view.set('canGenerate', false); + view.teardownRender(true); + } + }, + + /** + * @private + */ + isManager: function (view) { + return view && !!this.viewManagers[view.name]; + }, + + // Layout + + /** + * Handles the 'complete' event from its layout indicating a view has completed its layout + * @private + */ + handleLayoutComplete: function (sender, name, event) { + if (event.was) this.deactivateImmediate(event.was); + if (event.is) this.emitViewEvent('activated', event.is); + this.direction = 0; + }, + + // Flick + + /** + * Flicks are handled by the drag system so here we only test if there was a valid flick and rely + * on the ondragfinish handler to actually act on the flick as if it were a completed drag. + * + * @private + */ + handleFlick: function (sender, event) { + var isHorizontal = this.orientation == 'horizontal', + dx = event.xVelocity, + dy = event.yVelocity, + adx = Math.abs(dx), + ady = Math.abs(dy), + direction = 0; + + // Set direction iff the primary flick direction matches the orientation + if (isHorizontal && adx > ady) { + direction = dx < 0 ? 1 : -1; + } + else if (!this.isHorizontal && ady > adx) { + direction = dy < 0 ? 1 : -1; + } + + // If we have a direction, are flickable, and flickable in that direction, indicate it + if (direction && this.canDrag(direction, 'flick')) { + this.flicked = true; + return true; + } + }, + + // Draggable + + /** + * Handles `ondown` events + * + * @private + */ + handleDown: function (sender, event) { + event.configureHoldPulse({endHold: 'onMove'}); + }, + + /** + * Handles `ondragstart` events + * + * @private + */ + handleDragStart: function (sender, event) { + if (!this.draggable || this.dismissed) return; + this.set('dragging', 'start'); + this.direction = 0; + this.dragView = null; + this.dragBounds = this.getBounds(); + + return true; + }, + + /** + * Handles `ondrag` events + * + * @private + */ + handleDrag: function (sender, event) { + if (this.validateDrag(event)) { + // clean up on change of direction + if (this.direction !== event.direction) { + this.direction = event.direction; + if (this.dragView === false) { + this.dragView = null; + } else { + this.resetDragView(); + } + } + + // dragView can be a View, `false`, or `null`. `null` indicates we need to (try to) + // activate the becoming-active view. It should be null when a drag starts or when + // there's a change of direction. `false` indicates that we've tried to activate a view + // but there isn't one in that direction. + if (this.dragView === null) { + if (this.direction == 1) { + this.dragView = this.next(); + } else if (this.floating) { + this.dragView = this.back(); + if (!this.dragView) this.emit('dismiss', {dragging: true}); + } else { + this.dragView = this.previous(); + } + this.dragView = this.dragView || false; + } + this.emit('drag', event); + + return true; + } else { + // Reset the drag state when dragging in an invalid direction + this.resetDragView(); + this.direction = 0; + } + }, + + /** + * Handles `ondragfinish` events + * + * @private + */ + handleDragFinish: function (sender, event) { + if (this.releaseDraggedView) { + this.releaseDraggedView(); + this.releaseDraggedView = null; + } + if (this.validateDrag(event)) { + // if the view has been dragged far enough + if (this.flicked || event.percentDelta * 100 > this.dragThreshold) { + this.set('dragging', false); + // normally, there will be a becoming-active view to activate + if (this.dragView) { + // dragging for floating views can only be a back action so shift it off the stack + if (this.floating) this.stack.shift(); + // stack updates aren't necessary as we updated it above + this.activateImmediate(this.dragView); + } + // unless it's a floating ViewManager that is being dismissed + else if (this.isDimissable() && event.direction == -1) { + this.dismiss(); + } + } + // otherwise the drag was small enough to be cancelled + else { + this.cancelDrag(); + } + this.flicked = false; + event.preventTap(); + + this.dragView = null; + return true; + } else { + this.set('dragging', false); + this.dragView = null; + } + }, + + /** + * Deactivates drag view and resets `dragView` + * + * @private + */ + resetDragView: function () { + if (this.dragView) { + this.emitViewEvent('deactivate', this.dragView); + this.deactivate(this.dragView.name); + this.dragView = null; + } + }, + + /** + * @protected + */ + cancelDrag: function () { + var was = this.dragging; + this.set('dragging', false); + // only emit cancelDrag if a valid drag was encountered (=== true and != 'start') + if (was === true) { + // Since we're restoring the active view, the navigation direction is the opposite of the + // drag direction. + this.direction = -this.direction; + this.emit('cancelDrag'); + } + }, + + /** + * Validates that the drag event should be processed + * + * @private + */ + validateDrag: function (event) { + var dragging = false, + draggable = this.dragging && this.draggable && !this.dismissed; + + if (draggable) { + this.decorateDragEvent(event); + dragging = + // check direction against orientation to ignore drags that don't apply to this. the + // check should only be necessary for the first drag event so it's further guarded + // by the special 'start' value of dragging. + !(this.dragging == 'start' && !event[this.orientation]) && + + // Intentionally ignoring draggable mode here so dragView will reference the + // becoming-active view even if we are only supporting flick and not drag + this.canDrag(event.direction); + + this.set('dragging', dragging); + } + + return draggable && dragging; + }, + + /** + * Calculates and adds a few additional properties to the event to aid in logic in ViewManager + * and ViewLayout + * + * @private + */ + decorateDragEvent: function (event) { + var isHorizontal = this.orientation == 'horizontal', + size = isHorizontal ? this.dragBounds.width : this.dragBounds.height, + delta = isHorizontal ? event.dx : event.dy; + + // 'natural' touch causes us to invert the physical change + event.direction = delta < 0 ? 1 : -1; + + // if we're only flickable, we won't set the deltas to suppress the views moving until a + // flick is encountered. + if (this.canDrag(event.direction, 'drag')) { + event.delta = delta; + event.percentDelta = 1 - (size - Math.abs(event.delta)) / size; + } else { + event.delta = 0; + event.percentDelta = 0; + } + } +}); + +module.exports = ViewMgr; + +/** +* The {@link module:enyo/ViewManager~ScrimSupport} mixin +* +* @private +*/ +module.exports.ScrimSupport = ScrimSupport; diff --git a/src/ViewManager/ViewManager.less b/src/ViewManager/ViewManager.less new file mode 100644 index 000000000..396d83b4d --- /dev/null +++ b/src/ViewManager/ViewManager.less @@ -0,0 +1,19 @@ +.enyo-viewmanager { + // promote any ViewManager children to ensure they are above the active view + & > & { + z-index: 20; + } +} + +.enyo-viewmanager-scrim { + background: #fff; + opacity: 0; + pointer-events: none; + transition: opacity 300ms linear; + z-index: 10; + + &.showing { + opacity: 0.5; + pointer-events: auto; + } +} \ No newline at end of file diff --git a/src/ViewManager/package.json b/src/ViewManager/package.json new file mode 100644 index 000000000..fa1ca3039 --- /dev/null +++ b/src/ViewManager/package.json @@ -0,0 +1,6 @@ +{ + "main": "ViewManager.js", + "styles": [ + "ViewManager.less" + ] +} \ No newline at end of file diff --git a/src/ViewPreloadSupport.js b/src/ViewPreloadSupport.js index 4e0d0059f..b1cf4b78e 100644 --- a/src/ViewPreloadSupport.js +++ b/src/ViewPreloadSupport.js @@ -1,5 +1,6 @@ /** * Exports the {@link module:enyo/ViewPreloadSupport~ViewPreloadSupport} mixin +* @wip * @module enyo/ViewPreloadSupport */ var @@ -14,6 +15,7 @@ var * A {@glossary mixin} used for preloading views. * * @mixin +* @wip * @private */ var ViewPreloadSupport = { @@ -140,13 +142,28 @@ var ViewPreloadSupport = { * @public */ restoreView: function (id) { + var view = this.popView(id); + + if (view) { + this.addControl(view); + } + + return view; + }, + + /** + * Pops the specified view that was previously cached. + * + * @param {String} id - The unique identifier for the cached view that is being popped. + * @return {Object} The popped view. + * @public + */ + popView: function (id) { var cp = this._cachedViews, view = cp[id]; if (view) { this.$.viewCache.removeControl(view); - this.addControl(view); - this._cachedViews[id] = null; } diff --git a/src/VirtualDataRepeater.js b/src/VirtualDataRepeater.js index 1c631602c..4c84186ce 100644 --- a/src/VirtualDataRepeater.js +++ b/src/VirtualDataRepeater.js @@ -2,6 +2,9 @@ require('enyo'); /** * Contains the declaration for the {@link module:enyo/VirtualDataRepeater~VirtualDataRepeater} kind. +* +* @wip +* @public * @module enyo/VirtualDataRepeater */ @@ -12,18 +15,57 @@ var DataRepeater = require('./DataRepeater'); /** +* Like {@link module:enyo/DataRepeater~DataRepeater}, +* {@link module:enyo/VirtualDataRepeater~VirtualDataRepeater} repeats the +* {@link module:enyo/Control~Control} provided in the `components` block over +* the elements of the data set provided in the `collection` property (which +* may be an instance of {@link module:enyo/Collection~Collection} or a plain +* old JavaScript array). +* +* However, unlike {@link module:enyo/DataRepeater~DataRepeater}, +* {@link module:enyo/VirtualDataRepeater~VirtualDataRepeater} only generates +* children for a given range of items in the provided collection. +* +* To specify the range of items to be generated, provide the initial index via +* the `first` property and the maximum number of items to generate via the +* `numItems` property. The repeater will refresh automatically when you change +* either of these properties. +* * @class VirtualDataRepeater * @extends module:enyo/DataRepeater~DataRepeater +* @wip * @public */ -module.exports = kind({ +module.exports = kind( + /** @lends module:enyo/VirtualDataRepeater~VirtualDataRepeater.prototype */ { name: 'enyo.VirtualDataRepeater', kind: DataRepeater, + /** + * The maximum number of items to generate. Fewer items will be + * generated if the provided collection does not contain enough + * elements (that is, if the length of the collection minus + * the `first` index is less than `numItems`). + * + * @type {Number} + * @default 10 + * @public + */ numItems: 10, + /** + * The zero-based index of the provided collection from which + * the repeater should start repeating children. This property will + * be coerced to a legal value if you provide a value that is outside + * the range of the collection. + * + * @type {Number} + * @default 0 + * @public + */ first: 0, // TODO: Decide whether we want to implement node reordering // reorderNodes: false, + // Not including JSDoc info here, as we want the inherited doc to be used. reset: function () { // If we are showing, go ahead and reset... if (this.getAbsoluteShowing()) { @@ -46,6 +88,9 @@ module.exports = kind({ } }, + /** + * @private + */ init: function () { this.orderedChildren = [], this.orderedChildren_alt = [], @@ -57,6 +102,9 @@ module.exports = kind({ this.hasInitialized = true; }, + /** + * @private + */ setExtent: function(first, numItems) { var pf = this.first, pn = this.numItems; @@ -81,6 +129,7 @@ module.exports = kind({ } }, + // Not including JSDoc info here, as we want the inherited doc to be used. refresh: function (immediate) { // If we haven't initialized, we need to reset instead if (!this.hasInitialized) return this.reset(); @@ -153,15 +202,29 @@ module.exports = kind({ }; }), + // Not including JSDoc info here, as we want the inherited doc to be used. childForIndex: function(idx) { return this.childrenByIndex[idx]; }, + /** + * Attempts to return the [control]{@link module:enyo/Control~Control} representation of a particular + * model (which may be an instance of [enyo/Model]{@link module:enyo/Model~Model} or a plain old + * JavaScript object). + * + * @param {Number} idx - The model whose [control]{@link module:enyo/Control~Control} you want to retrieve. + * @returns {module:enyo/Control~Control|undefined} The [control]{@link module:enyo/Control~Control} representing + * the specified model, or `undefined` if there is not currently a control representing the model. + * @public + */ childForModel: function(model) { var idx = this.orderedModels.indexOf(model); return this.orderedChildren[idx]; }, + /** + * @private + */ assignChild: function(model, index, child) { var pMod = child.model, pIdx = child.index, @@ -187,6 +250,9 @@ module.exports = kind({ child.show(); }, + /** + * @private + */ doIt: function() { var dd = this.get('data'), f = this.first, @@ -284,14 +350,21 @@ module.exports = kind({ } }, + /** + * @private + */ fwd: function() { this.set('first', this.first + 1); }, + /** + * @private + */ bak: function() { this.set('first', this.first - 1); }, + // Not including JSDoc info here, as we want the inherited doc to be used. set: kind.inherit(function (sup) { return function (prop, val) { if (prop === 'first') { @@ -306,6 +379,9 @@ module.exports = kind({ }; }), + /** + * @private + */ stabilizeExtent: function() { var f = this.first, n = this.numItems, @@ -320,6 +396,9 @@ module.exports = kind({ } }, + /** + * @private + */ dataChanged: function() { if (this.get('data') && this.hasRendered) { this.reset(); diff --git a/src/WebService.js b/src/WebService.js index 5a3677c21..89cbd02b6 100644 --- a/src/WebService.js +++ b/src/WebService.js @@ -93,12 +93,12 @@ module.exports = kind( * @private */ published: { + /** @lends module:enyo/WebService~WebService.prototype */ /** * Indicates whether or not to use the [JSONP]{@glossary JSONP} protocol (and * {@link module:enyo/Jsonp~JsonpRequest} instead of {@link module:enyo/Ajax~Ajax}). * - * @memberof enyo.WebService.prototype * @type {Boolean} * @default false * @public @@ -111,7 +111,6 @@ module.exports = kind( * parameter. Enyo will create an internal callback function as necessary. * * @see module:enyo/WebService~WebService.jsonp - * @memberof enyo.WebService.prototype * @type {String} * @default 'callback' * @public @@ -123,7 +122,6 @@ module.exports = kind( * interpret the return data. * * @see module:enyo/WebService~WebService.jsonp - * @memberof enyo.WebService.prototype * @type {String} * @default null * @public @@ -135,7 +133,6 @@ module.exports = kind( * [send()]{@link module:enyo/WebService~WebService#send} call before failing with a timeout * error. * - * @memberof enyo.WebService.prototype * @type {Number} * @default 0 * @public diff --git a/src/XhrSource.js b/src/XhrSource.js index 283fdf4d7..0c030a4fe 100644 --- a/src/XhrSource.js +++ b/src/XhrSource.js @@ -166,7 +166,7 @@ var XhrSource = module.exports = kind( }); /** -* @name enyo.XhrSource.concat +* @name module:enyo/XhrSource~XhrSource.concat * @static * @private */ diff --git a/src/animation.js b/src/animation.js index 2aa09be80..746674314 100644 --- a/src/animation.js +++ b/src/animation.js @@ -7,20 +7,24 @@ require('enyo'); var platform = require('./platform'), - utils = require('./utils'); + utils = require('./utils'), + easing = require('./easing'); -var ms = Math.round(1000/60); -var prefix = ['webkit', 'moz', 'ms', 'o', '']; -var r = 'requestAnimationFrame'; -var c = 'cancel' + utils.cap(r); +var ms = Math.round(1000/60), + prefix = ['', 'webkit', 'moz', 'ms', 'o'], + rAF = 'requestAnimationFrame', + cRAF = 'cancelRequestAnimationFrame', + cAF = 'cancelAnimationFrame', + i, pl, p, wcRAF, wrAF, wcAF, + _requestFrame, _cancelFrame, cancelFrame; /* * Fallback on setTimeout * * @private */ -var _requestFrame = function(inCallback) { - return global.setTimeout(inCallback, ms); +_requestFrame = function(callback) { + return global.setTimeout(callback, ms); }; /* @@ -28,23 +32,26 @@ var _requestFrame = function(inCallback) { * * @private */ -var _cancelFrame = function(inId) { - return global.clearTimeout(inId); +_cancelFrame = function(id) { + return global.clearTimeout(id); }; -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) { +for (i = 0, pl = prefix.length; (p = prefix[i]) || i < pl; i++) { + // if we're on ios 6 just use setTimeout, requestAnimationFrame has some kinks + 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; + wrAF = p ? (p + utils.cap(rAF)) : rAF; + wcRAF = p ? (p + utils.cap(cRAF)) : cRAF; + wcAF = p ? (p + utils.cap(cAF)) : cAF; + // Test for cancelRequestAnimationFrame, because some browsers (Firefix 4-10) have a request without a cancel - if (global[wc]) { - _cancelFrame = global[wc]; - _requestFrame = global[wr]; + cancelFrame = global[wcAF] || global[wcRAF]; + if (cancelFrame) { + _cancelFrame = cancelFrame; + _requestFrame = global[wrAF]; if (p == 'webkit') { /* Note: In Chrome, the first return value of webkitRequestAnimationFrame is 0. @@ -76,67 +83,21 @@ exports.requestAnimationFrame = function(callback, node) { /** * Cancels a requested animation callback with the specified id. * +* @param {Number} id - The identifier of an animation request we wish to cancel. +* @deprecated since 2.7.0 * @public */ -exports.cancelRequestAnimationFrame = function(inId) { - return _cancelFrame(inId); +exports.cancelRequestAnimationFrame = function(id) { + return _cancelFrame(id); }; - /** -* 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. +* Cancels a requested animation callback with the specified id. * +* @param {Number} id - The identifier of an animation request we wish to cancel. * @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; - } +exports.cancelAnimationFrame = function(id) { + return _cancelFrame(id); }; /** diff --git a/src/dispatcher.js b/src/dispatcher.js index e1244e2fc..55b387f35 100644 --- a/src/dispatcher.js +++ b/src/dispatcher.js @@ -8,8 +8,7 @@ require('enyo'); var logger = require('./logger'), master = require('./master'), - utils = require('./utils'), - platform = require('./platform'); + utils = require('./utils'); var Dom = require('./dom'); @@ -31,10 +30,6 @@ var * any scroll offset. */ -/** -* @private -*/ - /** * @private */ @@ -47,17 +42,17 @@ var dispatcher = module.exports = dispatcher = { * * @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"], + 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"], + windowEvents: ['resize', 'load', 'unload', 'message', 'hashchange', 'popstate', 'focus', 'blur'], /** * Feature plugins (aka filters) @@ -76,8 +71,8 @@ var dispatcher = module.exports = dispatcher = { } for (i=0; (n=d.windowEvents[i]); i++) { // Chrome Packaged Apps don't like "unload" - if(n === "unload" && - (typeof global.chrome === "object") && + if(n === 'unload' && + (typeof global.chrome === 'object') && global.chrome.app) { continue; } @@ -90,40 +85,14 @@ var dispatcher = module.exports = dispatcher = { * @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); + inListener.addEventListener(inEventName, inHandler || dispatch, false); }, /** * @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); + inListener.removeEventListener(inEventName, inHandler || dispatch, false); }, /** @@ -188,7 +157,7 @@ var dispatcher = module.exports = dispatcher = { */ dispatchBubble: function(e, c) { var type = e.type; - type = e.customEvent ? type : "on" + type; + type = e.customEvent ? type : 'on' + type; return c.bubble(type, e, c); } }; @@ -196,6 +165,9 @@ var dispatcher = module.exports = dispatcher = { /** * Called in the context of an event. * +* @name module:enyo/dispatcher.iePreventDefault +* @static +* @method * @private */ dispatcher.iePreventDefault = function() { @@ -215,29 +187,26 @@ function dispatch (inEvent) { } /** +* @name module:enyo/dispatcher.bubble +* @static +* @method * @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); + if (inEvent) { + dispatcher.dispatch(inEvent); } }; // 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])"; +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 +// We replace online scripting with equivalent means, leaving dispatcher.bubbler // for backward compatibility. (function() { var bubbleUp = function() { @@ -247,13 +216,15 @@ dispatcher.bubbler = "enyo.bubble(arguments[0])"; /** * Makes given events bubble on a specified Enyo control. * + * @name: module:enyo/dispatcher.makeBubble + * @method * @private */ dispatcher.makeBubble = function() { var args = Array.prototype.slice.call(arguments, 0), control = args.shift(); - if((typeof control === "object") && (typeof control.hasNode === "function")) { + if((typeof control === 'object') && (typeof control.hasNode === 'function')) { utils.forEach(args, function(event) { if(this.hasNode()) { dispatcher.listen(this.node, event, bubbleUp); @@ -264,15 +235,17 @@ dispatcher.bubbler = "enyo.bubble(arguments[0])"; /** * Removes the event listening and bubbling initiated by - * [enyo.makeBubble()]{@link enyo.makeBubble} on a specific control. + * [makeBubble()]{@link module:enyo/dispatcher.makeBubble} on a specific control. * + * @name: module:enyo/dispatcher.unmakeBubble + * @method * @private */ dispatcher.unmakeBubble = function() { var args = Array.prototype.slice.call(arguments, 0), control = args.shift(); - if((typeof control === "object") && (typeof control.hasNode === "function")) { + if((typeof control === 'object') && (typeof control.hasNode === 'function')) { utils.forEach(args, function(event) { if(this.hasNode()) { dispatcher.stopListening(this.node, event, bubbleUp); @@ -295,12 +268,12 @@ Dom.requiresWindow(dispatcher.connect); */ dispatcher.features.push( function (e) { - if ("click" === e.type) { + 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.type = 'tap'; cp.preventDefault = utils.nop; dispatcher.dispatch(cp); } @@ -319,19 +292,20 @@ var _xy = {}; dispatcher.features.push( function (e) { if ( - (e.type == "mousemove") || - (e.type == "tap") || - (e.type == "click") || - (e.type == "touchmove") + (e.type == 'mousemove') || + (e.type == 'tap') || + (e.type == 'click') || + (e.type == 'touchmove') ) { - _xy.clientX = e.clientX; - _xy.clientY = e.clientY; + var evt = (e.type == 'touchmove') ? e.touches[0] : e; + _xy.clientX = evt.clientX; + _xy.clientY = evt.clientY; // note only ie8 does not support pageX/pageY - _xy.pageX = e.pageX; - _xy.pageY = e.pageY; + _xy.pageX = evt.pageX; + _xy.pageY = evt.pageY; // note ie8 and opera report these values incorrectly - _xy.screenX = e.screenX; - _xy.screenY = e.screenY; + _xy.screenX = evt.screenX; + _xy.screenY = evt.screenY; } } ); @@ -342,35 +316,27 @@ dispatcher.features.push( * `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. +* device pixels). * * @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; + return utils.clone(_xy); }; /** * 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 +* [registerKeyMap()]{@link module: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({ +* dispatcher.registerKeyMap({ * 415 : 'play', * 413 : 'stop', * 19 : 'pause', @@ -378,7 +344,7 @@ dispatcher.getPosition = function () { * 417 : 'fastforward' * }); * ``` -* +* * @private */ dispatcher.features.push(function(e) { @@ -402,8 +368,8 @@ utils.mixin(dispatcher, { /** * 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}. +* [capture(inControl, inShouldForward)]{@linkcode module:enyo/dispatcher.capture}; +* release events via [release()]{@link module:enyo/dispatcher.release}. * * @private */ @@ -427,7 +393,7 @@ dispatcher.features.push(function(e) { // // NOTE: This object is a plug-in; these methods should -// be called on `enyo.dispatcher`, and not on the plug-in itself. +// be called on `enyo/dispatcher`, and not on the plug-in itself. // utils.mixin(dispatcher, { @@ -441,7 +407,7 @@ utils.mixin(dispatcher, { */ 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 @@ -458,7 +424,7 @@ utils.mixin(dispatcher, { /** * Removes the specified target from the capture list. - * + * * @private */ release: function(inTarget) { @@ -473,7 +439,7 @@ utils.mixin(dispatcher, { /** * Sets the information for a captured {@glossary event}. - * + * * @private */ setCaptureInfo: function(inInfo) { @@ -487,28 +453,23 @@ utils.mixin(dispatcher, { (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`. * + * @todo Revisit how/if we document this * @private */ var fn = 'previewDomEvent'; - var preview = - /** @lends enyo.dispatcher.features */ { + var preview = { - /** - * @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, @@ -521,12 +482,10 @@ utils.mixin(dispatcher, { } }, - /** + /* * 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 = [], diff --git a/src/dom.js b/src/dom.js index baf213a66..1d5e46f46 100644 --- a/src/dom.js +++ b/src/dom.js @@ -7,7 +7,8 @@ require('enyo'); var roots = require('./roots'), utils = require('./utils'), - platform = require('./platform'); + platform = require('./platform'), + transform = require('./transform'); var dom = module.exports = { @@ -70,41 +71,11 @@ var dom = module.exports = { } }, - /** - * 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); - } + return global.getComputedStyle && node && global.getComputedStyle(node, null); }, /** @@ -118,9 +89,9 @@ var dom = module.exports = { if (nIE) { var oConversion = { - 'thin' : (nIE > 8 ? 2 : 1) + 'px', - 'medium' : (nIE > 8 ? 4 : 3) + 'px', - 'thick' : (nIE > 8 ? 6 : 5) + 'px', + 'thin' : '2px', + 'medium' : '4px', + 'thick' : '6px', 'none' : '0' }; if (typeof oConversion[s] != 'undefined') { @@ -216,64 +187,11 @@ var dom = module.exports = { /** * @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)) { + if (s) { 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; }, @@ -361,8 +279,7 @@ var dom = module.exports = { 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) { + if (relativeToNode && relativeToNode.compareDocumentPosition(node.offsetParent) & Node.DOCUMENT_POSITION_CONTAINS) { offsetAdjustLeft = relativeToNode.offsetLeft; offsetAdjustTop = relativeToNode.offsetTop; } @@ -377,11 +294,7 @@ var dom = module.exports = { } // 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) { + if (global.getComputedStyle) { borderLeft = parseInt(global.getComputedStyle(node, '').getPropertyValue('border-left-width'), 10); borderTop = parseInt(global.getComputedStyle(node, '').getPropertyValue('border-top-width'), 10); } else { @@ -397,8 +310,7 @@ var dom = module.exports = { } } // 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)); + } while ((node = node.offsetParent) && (!relativeToNode || relativeToNode.compareDocumentPosition(node) & Node.DOCUMENT_POSITION_CONTAINED_BY)); } return { 'top': top, @@ -417,8 +329,7 @@ var dom = module.exports = { * @public */ removeNode: function (node) { - if (node.remove) node.remove(); - else if (node.parentNode) node.parentNode.removeChild(node); + if (node.parentNode) node.parentNode.removeChild(node); }, /** @@ -528,6 +439,68 @@ var dom = module.exports = { } }, + /** + * Get DOM node animation properties. + * @public + * @param {HTMLElement} node DOM node + * @param {Object} props Properties to fetch from DOM. + * @param {Object} initial Default properties to be applied. + * @return {Object} Object with various animation properties. + */ + getAnimatedProperty: function (node, props, initial) { + if(!node) return; + + var eP = {}, + sP = initial ? utils.mixin({}, initial) : {}, + tP = {}, + dP = {}, + m, k, v, + s = initial ? undefined : this.getComputedStyle(node); + + for (k in props) { + v = sP[k]; + if (!utils.isTransform(k)) { + v = v || utils.getStyleValue(s || this.getComputedStyle(node), k); + sP[k] = utils.formatCSSValues(v, k); + eP[k] = utils.formatCSSValues(props[k], k, sP[k] ? sP[k].length : sP[k]); + } else { + v = utils.formatTransformValues(props[k], k); + if (k.match(/rotate/)) { + v = transform.Quaternion.toQuant(v); + tP.rotate = tP.rotate ? transform.Quaternion.multiplication(tP.rotate, v) : v; + } else { + t = k.replace(/[XYZ]$/,''); + tP[t] = tP[t] ? tP[t].map(function (num, id) { + return num + v[id]; + }) : v; + } + if (k.match(/[XYZ]$/)) { + t = k.replace(/[XYZ]$/,''); + props[t] = tP[t].join(); + delete props[k]; + } + } + } + + if (initial) { + dP.translate = initial.translate; + dP.rotate = initial.rotate.length < 4 ? transform.Quaternion.toQuant(initial.rotate) : initial.rotate; + dP.scale = initial.scale; + dP.skew = initial.skew; + dP.perspective = initial.perspective; + } else { + m = utils.getStyleValue(s || this.getComputedStyle(node), this.getCssTransformProp()); + m = utils.formatTransformValues(m, 'matrix'); + transform.Matrix.decompose(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}; + }, + /** * @private */ @@ -576,7 +549,7 @@ var dom = module.exports = { }; // override setInnerHtml for Windows 8 HTML applications -if (typeof global.MSApp !== 'undefined') { +if (typeof global.MSApp !== 'undefined' && typeof global.MSApp.execUnsafeLocalFunction !== 'undefined') { dom.setInnerHtml = function(node, html) { global.MSApp.execUnsafeLocalFunction(function() { node.innerHTML = html; @@ -633,6 +606,7 @@ dom.calcCanAccelerate = function() { } return false; }; + /** * @private */ @@ -660,6 +634,19 @@ dom.getStyleTransformProp = function() { } }; +/** +* @private +*/ +dom.toTransformValue = function(matrix, ret) { + var mat = transform.Matrix.toString(matrix); + + ret = ret || {}; + for (var i = 0, p; (p = styleTransformProps[i]); i++) { + ret[p] = mat; + } + return ret; +}; + /** * @private */ @@ -792,8 +779,7 @@ dom.transformValue = function(control, transform, 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. +* @param {(String|Number)} [value] - An optional value to apply to the acceleration transform property. * @public */ dom.accelerate = function(control, value) { @@ -801,7 +787,6 @@ dom.accelerate = function(control, value) { this.transformValue(control, 'translateZ', v ? 0 : null); }; - /** * The CSS `transition` property name for the current browser/platform, e.g.: * diff --git a/src/easing.js b/src/easing.js new file mode 100644 index 000000000..e6dd104cb --- /dev/null +++ b/src/easing.js @@ -0,0 +1,408 @@ +/** +* Contains set of interpolation functions for animations, similar in function to CSS3 transitions. +* @module enyo/easing +*/ + +var easing = module.exports = { + /** + * linear + * + * @public + */ + linear: function(n) { + return n; + }, + /** + * 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; + }, + + /** + * EaseInQuad + * @public + * @param {number} t - current time + * @return {number} - calculated time + */ + easeInQuad: function(t) { + return t * t; + }, + + /** + * EaseOutQuad + * @public + * @param {number} t - current time + * @return {number} calculated time + */ + easeOutQuad: function(t) { + return -1 * t * (t - 2); + }, + + /** + * EaseInOutQuad + * @public + * @param {number} t - current time + * @return {number} calculated time + */ + easeInOutQuad: function(t) { + if ((t *= 2) < 1) return 0.5 * t * t; + return -0.5 * ((--t) * (t - 2) - 1); + }, + + /** + * EaseInCubic + * @public + * @param {number} t - current time + * @return {number} calculated time + */ + easeInCubic: function(t) { + return t * t * t; + }, + + /** + * EaseOutCubic + * @public + * @param {number} t - current time + * @return {number} calculated time + */ + easeOutCubic: function(t) { + return --t * t * t + 1; + }, + + /** + * EaseInOutCubic + * @public + * @param {number} t - current time + * @return {number} calculated time + */ + easeInOutCubic: function(t) { + if ((t *= 2) < 1) return 0.5 * t * t * t; + return 0.5 * ((t -= 2) * t * t + 2); + }, + + /** + * EaseInQuart + * @public + * @param {number} t - current time + * @return {number} calculated time + */ + easeInQuart: function(t) { + return t * t * t * t; + }, + + /** + * EaseOutQuart + * @public + * @param {number} t - current time + * @return {number} calculated time + */ + easeOutQuart: function(t) { + return -1 * (--t * t * t * t - 1); + }, + + /** + * EaseInOutQuart + * @public + * @param {number} t - current time + * @return {number} calculated time + */ + easeInOutQuart: function(t) { + if ((t *= 2) < 1) return 0.5 * t * t * t * t; + return -0.5 * ((t -= 2) * t * t * t - 2); + }, + + /** + * EaseInQuint + * @public + * @param {number} t - current time + * @return {number} calculated time + */ + easeInQuint: function(t) { + return t * t * t * t * t; + }, + /** + * EaseOutQuint + * @public + * @param {number} t - current time + * @return {number} calculated time + */ + easeOutQuint: function(t) { + return --t * t * t * t * t + 1; + }, + + /** + * EaseInOutQuint + * @public + * @param {number} t - current time + * @return {number} calculated time + */ + easeInOutQuint: function(t, d) { + if ((t *= 2) < 1) return 0.5 * t * t * t * t * t; + return 0.5 * ((t -= 2) * t * t * t * t + 2); + }, + + /** + * EaseInSine + * @public + * @param {number} t - current time + * @return {number} calculated time + */ + easeInSine: function(t) { + return -1 * Math.cos(t * (Math.PI / 2)) + 1; + }, + + /** + * EaseOutSine + * @public + * @param {number} t - current time + * @return {number} calculated time + */ + easeOutSine: function(t) { + return Math.sin(t * (Math.PI / 2)); + }, + + /** + * EaseInOutSine + * @public + * @param {number} t - current time + * @return {number} calculated time + */ + easeInOutSine: function(t) { + return -0.5 * (Math.cos(Math.PI * t) - 1); + }, + + /** + * EaseInExpo + * @public + * @param {number} t - current time + * @return {number} calculated time + */ + easeInExpo: function(t) { + return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)); + }, + + /** + * EaseOutExpo + * @public + * @param {number} t - current time + * @return {number} calculated time + */ + easeOutExpo: function(t) { + return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1; + }, + + /** + * EaseInOutExpo + * @public + * @param {number} t - current time + * @return {number} calculated time + */ + easeInOutExpo: function(t) { + if (t === 0) return 0; + if (t === 1) return 1; + if ((t *= 2) < 1) return 0.5 * Math.pow(2, 10 * (t - 1)); + return 0.5 * (-Math.pow(2, -10 * --t) + 2); + }, + + /** + * EaseInCirc + * @public + * @param {number} t - current time + * @return {number} calculated time + */ + easeInCirc: function(t) { + return -1 * (Math.sqrt(1 - t * t) - 1); + }, + + /** + * EaseOutCirc + * @public + * @param {number} t - current time + * @return {number} calculated time + */ + easeOutCirc: function(t) { + return Math.sqrt(1 - --t * t); + }, + + /** + * EaseInOutCirc + * @public + * @param {number} t - current time + * @return {number} calculated time + */ + easeInOutCirc: function(t) { + if ((t *= 2) < 1) return -0.5 * (Math.sqrt(1 - t * t) - 1); + return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1); + }, + + /** + * EaseInElastic + * @public + * @param {number} t - current time + * @param {number} d - duration + * @return {number} calculated time + */ + easeInElastic: function(t, d) { + var a = 1, + p = 0, + s = 1.70158; + if (t === 0) return 0; + if (t === 1) return 1; + if (!p) p = d * 0.3; + if (a < 1) { + a = 1; + s = p / 4; + } else s = p / (2 * Math.PI) * Math.asin(1 / a); + return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)); + }, + + /** + * EaseInBounce + * @public + * @param {number} t - current time + * @return {number} calculated time + */ + easeInBounce: function(t) { + return 1 - easing.easeOutBounce(1 - t); + }, + + /** + * EaseOutBounce + * @public + * @param {number} t - current time + * @return {number} calculated time + */ + easeOutBounce: function(t) { + if (t < 0.363636) { + return 7.5625 * t * t; + } else if (t < 0.727272) { + return 7.5625 * (t -= 0.545454) * t + 0.75; + } else if (t < (2.5 / 2.75)) { + return 7.5625 * (t -= 0.818182) * t + 0.9375; + } else { + return 7.5625 * (t -= 0.954545) * t + 0.984375; + } + }, + + /** + * EaseInOutBounce + * @public + * @param {number} t - current time + * @return {number} calculated time + */ + easeInOutBounce: function(t) { + if (t < 0.5) return easing.easeInBounce(t * 2) * 0.5; + return easing.easeOutBounce(t * 2 - 1) * 0.5 + 0.5; + }, + + /** + * EaseOutElastic + * @public + * @param {number} t - current time + * @param {number} d - duration + * @return {number} calculated time + */ + easeOutElastic: function(t, d) { + var a = 1, + p = 0, + s = 1.70158; + if (t === 0) return 0; + if (t === 1) return 1; + if (!p) p = d * 0.3; + if (a < 1) { + a = 1; + s = p / 4; + } else s = p / (2 * Math.PI) * Math.asin(1 / a); + return a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + 1; + }, + + /** + * EaseInOutElastic + * @public + * @param {number} t - current time + * @param {number} d - duration + * @return {number} calculated time + */ + easeInOutElastic: function(t, d) { + var a = 1, + p = 0, + s = 1.70158; + if (t === 0) return 0; + if ((t *= 2) === 2) return 1; + if (!p) p = d * (0.3 * 1.5); + if (a < 1) { + a = 1; + s = p / 4; + } else s = p / (2 * Math.PI) * Math.asin(1 / a); + if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)); + return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * 0.5 + 1; + }, + + /** + * EaseInBack + * @public + * @param {number} t - current time + * @param {number} d - duration + * @return {number} calculated time + */ + easeInBack: function(t, d, s) { + if (!s) s = 1.70158; + return t * t * ((s + 1) * t - s); + }, + + /** + * EaseOutBack + * @public + * @param {number} t - current time + * @param {number} d - duration + * @return {number} calculated time + */ + easeOutBack: function(t, d, s) { + if (!s) s = 1.70158; + return --t * t * ((s + 1) * t + s) + 1; + }, + + /** + * EaseInOutBack + * @public + * @param {number} t - current time + * @param {number} d - duration + * @return {number} calculated time + */ + easeInOutBack: function(t, d, s) { + if (!s) s = 1.70158; + if ((t *= 2) < 1) return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s)); + return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); + } +}; \ No newline at end of file diff --git a/src/gesture/drag.js b/src/gesture/drag.js index 8de40002d..31cc721e3 100644 --- a/src/gesture/drag.js +++ b/src/gesture/drag.js @@ -1,6 +1,5 @@ var dispatcher = require('../dispatcher'), - platform = require('../platform'), utils = require('../utils'); var @@ -307,11 +306,6 @@ module.exports = { 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; @@ -586,7 +580,7 @@ module.exports = { } this._pulsing = true; dispatcher.dispatch(e); - n = this._next = this._unsent.shift(); + n = this._next = this._unsent && this._unsent.shift(); } }, diff --git a/src/gesture/gesture.js b/src/gesture/gesture.js index 4eaf02179..8759fc611 100644 --- a/src/gesture/gesture.js +++ b/src/gesture/gesture.js @@ -1,7 +1,6 @@ -require('enyo'); /** - * @module enyo/gesture - */ +* @module enyo/gesture +*/ var @@ -15,7 +14,6 @@ var 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 @@ -85,7 +83,7 @@ var gesture = module.exports = { // 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 + // 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() { @@ -260,7 +258,7 @@ var gesture = module.exports = { /** * @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() @@ -335,13 +333,18 @@ module.exports.events = { dom.requiresWindow(function() { if (document.addEventListener) { document.addEventListener('DOMMouseScroll', function(inEvent) { - var e = utils.clone(inEvent); + var e = utils.clone(inEvent), + isVertical = e.VERTICAL_AXIS == e.axis, + wheelDelta; e.preventDefault = function() { inEvent.preventDefault(); }; e.type = 'mousewheel'; - var p = e.VERTICAL_AXIS == e.axis ? 'wheelDeltaY' : 'wheelDeltaX'; - e[p] = e.detail * -40; + + wheelDelta = e.detail * -40; + e.wheelDeltaY = isVertical ? wheelDelta : 0; + e.wheelDeltaX = isVertical ? 0 : wheelDelta; + dispatcher.dispatch(e); }, false); } diff --git a/src/gesture/touchGestures.js b/src/gesture/touchGestures.js index eed1a4cb0..6010a0534 100644 --- a/src/gesture/touchGestures.js +++ b/src/gesture/touchGestures.js @@ -4,7 +4,7 @@ var /** * The extended {@glossary event} [object]{@glossary Object} that is provided when we -* emulate iOS gesture events on non-iOS devices. +* emulate iOS multitouch 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. diff --git a/src/gesture/util.js b/src/gesture/util.js index e2466335c..0f2d9f8a5 100644 --- a/src/gesture/util.js +++ b/src/gesture/util.js @@ -50,11 +50,6 @@ module.exports = { // 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 diff --git a/src/i18n.js b/src/i18n.js index 861ef5c9d..ef85b5901 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -43,8 +43,6 @@ exports.$L = new utils.Extensible(function (str) { * `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. -* -* This feature is not supported on IE8, which doesn't support `addEventListener()`. * * @private */ diff --git a/src/kind.js b/src/kind.js index a099198fd..9caf55de0 100644 --- a/src/kind.js +++ b/src/kind.js @@ -1,303 +1,301 @@ require('enyo'); var - logger = require('./logger'), - utils = require('./utils'); + logger = require('./logger'), + Scene = require('./scene'), + utils = require('./utils'); var defaultCtor = null; - /** -* Creates a JavaScript {@glossary constructor} function with -* a prototype defined by `props`. **All constructors must have a unique name.** -* -* `kind()` makes it easy to build a constructor-with-prototype (like a -* class) that has advanced features like prototype-chaining -* ({@glossary inheritance}). -* -* A plug-in system is included for extending the abilities of the -* {@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 {@glossary kind} -* @public -*/ + * Creates a JavaScript {@glossary constructor} function with + * a prototype defined by `props`. **All constructors must have a unique name.** + * + * `kind()` makes it easy to build a constructor-with-prototype (like a + * class) that has advanced features like prototype-chaining + * ({@glossary inheritance}). + * + * A plug-in system is included for extending the abilities of the + * {@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 {@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; +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; +exports.setDefaultCtor = function(ctor) { + defaultCtor = ctor; }; -var getDefaultCtor = exports.getDefaultCtor = function () { - return defaultCtor; +var getDefaultCtor = exports.getDefaultCtor = function() { + return defaultCtor; }; /** -* @private -*/ + * @private + */ var concatenated = exports.concatenated = []; /** -* 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 -* var -* kind = require('enyo/kind'), -* Control = require('enyo/Control'); -* -* module.exports = singleton({ -* kind: 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; + * 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 + * var + * kind = require('enyo/kind'), + * Control = require('enyo/Control'); + * + * module.exports = singleton({ + * kind: 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; + * @name module:enyo/kind.makeCtor + * @method + * @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 -*/ + * Feature hooks for the oop system + * + * @name module:enyo/kind.features + * @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 + '()'; - } - } - } + * 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. + * + * @name module:enyo/kind.extendMethods + * @method + * @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); - } + * 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. + * + * @name module:enyo/kind.inherited + * @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; +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); + * 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); + * @private + */ +var isInherited = exports.isInherited = function(fn) { + return fn && (fn instanceof Inherited); }; @@ -305,193 +303,562 @@ var isInherited = exports.isInherited = function (fn) { // '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; - } + // 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; + } +}); + + +kind.features.push(function(ctor, props) { + // install common statics + if (props.scene) { + var fn, + proto = ctor.prototype || ctor, + sctor = Scene.create(proto); + + extend(AnimationSupport, sctor); + fn = props.scene.isScene ? sceneToScene : updateScene; + proto.scene = fn.call(sctor, props.scene); + } }); /** -* @private -*/ + * Adds animation from a scene to other scene + * @memberOf module:enyo/AnimationSupport/Actor + * @public + * @return {Object} An instance of the constructor + */ +function sceneToScene(src) { + if (!src) return; + if (!src.id) extend(AnimationSupport, src); + + var i, l = src.length(), + anim; + + for (i = 0; i < l; i++) { + anim = utils.mixin({}, src.getAnimation(i)); + this.addAnimation(anim.animate, anim.animate.duration); + } + + var span = src.span + this.span; + src.rolePlays.push({ + actor: this, + span: span, + dur: this.span + }); + return this; +} + +/** + * Add animations within a scene + * @memberOf module:enyo/AnimationSupport/Actor + * @public + * @return {Object} An instance of the constructor + */ +function updateScene(props) { + if (!props) return; + + var anims = utils.isArray(props) ? props : [props]; + for (var i = 0; i < anims.length; i++) { + this.addAnimation(anims[i], anims[i].duration || 0); + } + return this; +} + +/** + * @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); - } + /** + * 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. + * + * @name module:enyo/kind.subclass + * @method + * @param {Function} ctor - The [constructor]{@glossary constructor} of the + * [kind]{@glossary kind} being subclassed. + * @param {Object} props - The properties of the kind being subclassed. + * @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.features} against the constructor or instance. + * + * @name module:enyo/kind.extend + * @method + * @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. + * @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. + * + * @name module:enyo/kind.kind + * @method + * @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 + * @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; - } + * @method + * @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 = {}; +var kindCtors = + /** + * Factory for [kinds]{@glossary kind} identified by [strings]{@glossary String}. + * + * @type Object + * @deprecated Since 2.6.0 + * @private + */ + 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; + * @method + * @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 -*/ + * Namespace for current theme (`enyo.Theme.Button` references the Button specialization for the + * current theme). + * + * @deprecated Since 2.6.0 + * @private + */ var Theme = exports.Theme = {}; /** -* @private -*/ -exports.registerTheme = function (ns) { - utils.mixin(Theme, ns); + * @method + * @deprecated Since 2.6.0 + * @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); - } + * @method + * @private + */ +exports.createFromKind = function(nom, param) { + var Ctor = nom && constructorForKind(nom); + if (Ctor) { + return new Ctor(param); + } +}; +/** + * Interface which accepts the animation details and returns a scene object + * @param {Array} proto Actors + * @param {Object} properties Animation Properties + * @param {String} completed Callback function on completion + * @return {Object} A scene constructor object + * @memberOf module:enyo/AnimationSupport/Actor + * @public + */ +kind.animate = function(proto, properties, completed) { + var rolePlays, sctor, parentScene; + parentScene = Scene({ animation: properties }); + if (properties && proto.length > 0) { + for (var i = 0; i <= proto.length; i++) { + sctor = Scene.create(proto[i]); + kind.statics.extend(AnimationSupport, sctor); + parentScene.rolePlays.push({ + actor: updateScene.call(sctor, properties), + span: (sctor.span) * (i + 1), + dur: sctor.span + }); + } + } else if (properties && proto) { + sctor = Scene.create(proto); + kind.statics.extend(AnimationSupport, sctor); + parentScene.rolePlays.push({ + actor: updateScene.call(sctor, properties), + span: sctor.span, + dur: sctor.span + }); + } + kind.statics.extend(AnimationSupport, parentScene); + parentScene.threshold = parentScene.rolePlays.length * parentScene.span; + parentScene.completed = completed; + return parentScene; +}; +/** + * Interface for Sequence animations which accepts the animation details and returns a scene object + * @param {Array} actors Actors + * @param {Object} properties Animation Properties + * @param {String} completed Callback function on completion + * @return {Object} A scene constructor object + * @memberOf module:enyo/AnimationSupport/Actor + * @public + */ +kind.sequenceAnimate = function(actors, properties, completed) { + var actorsList, arrayCheck, rolePlays, sctor, parentScene, sceneLocal; + actorsList = actors; + arrayCheck = utils.isArray(actorsList); + if (arrayCheck === true && actorsList.length !== 0) { + parentScene = Scene.create({ generated: true }); + parentScene.sequencing = true; + kind.statics.extend(AnimationSupport, parentScene); + for (var i = 0; i < actorsList.length; i++) { + sceneLocal = Scene.create(actorsList[i]); + for (var j = 0; j < properties.length; j++) { + sceneLocal.addAnimation(properties[j], properties[j].duration); + } + kind.statics.extend(AnimationSupport, sceneLocal); + parentScene.addAnimation(sceneLocal); + } + parentScene.completed = completed; + return parentScene; + } else { + sceneLocal = kind.animate(actorsList, properties, completed); + return sceneLocal; + } +}; + +var AnimationSupport = { + + + + handleLayers: false, + + /** + * An exposed property to know if know the animating state of this scene. + * 'true' - the scene is asked for animation(doesn't mean animation is happening) + * 'false' - the scene is not active(has completed or its actors are not visible) + * @type {Boolean} + * @memberOf module:enyo/AnimationSupport/Actor + * @public + */ + animating: false, + + /** + * An exposed property to know if the scene is ready with actors performing action. + * 'true' - the scene actors are ready for action + * 'false' - some or all actors are not ready + * @type {Boolean} + * @memberOf module:enyo/AnimationSupport/Actor + * @public + */ + active: this && this.generated, + + + /** + * @private + */ + timeline: 0, + /** + * @private + */ + _cachedValue: 0, + /** + * @private + */ + speed: 0, + /** + * @private + */ + seekInterval: 0, + /** + * @private + */ + repeat: false, + /** + * @private + */ + cache: function(actor) { + actor = actor || this; + if (actor.speed === 0) { + actor.speed = actor._cachedValue; + } + this.animating = true; + }, + + /** + * Starts the animation of the actor given in argument. + * If actor is not provided, animation of all the components linked to the {@link module:enyo/AnimationSupport/Scene} will be started. + * @param [Component]{@link module:enyo/Component~Component} actor The component to be animated + * @public + */ + play: function(actor) { + actor = actor || this; + actor.speed = 1; + if (isNaN(actor.timeline) || !actor.timeline) { + actor.timeline = 0; + } + this.trigger(); + actor._cachedValue = actor.speed; + this.animating = true; + }, + + /** + * Replays the animation of the actor given in argument. + * If actor is not provided, animation of all the components linked to the {@link module:enyo/AnimationSupport/Scene} will be started. + * @param [Component]{@link module:enyo/Component~Component} actor The component to be animated + * @public + */ + replay: function(actor) { + this.stop(); + this.play(); + }, + /** + * Resumes the paused animation of the actor given in argument. + * If actor is not provided, animation of all the components linked to the {@link module:enyo/AnimationSupport/Scene} will be resumed. + * @param [Component]{@link module:enyo/Component~Component} actor The component to be animated + * @public + */ + resume: function(actor) { + this.cache(actor); + actor = actor || this; + actor.speed *= 1; + }, + + /** + * Pauses the animation of the actor given in argument. + * If actor is not provided, animation of all the components linked to the {@link module:enyo/AnimationSupport/Scene} will be paused. + * @param [Component]{@link module:enyo/Component~Component} actor The component to be animated + * @public + */ + pause: function(actor) { + actor = actor || this; + actor._cachedValue = actor.speed; + actor.speed = 0; + }, + + /** + * Reverses the animation of the actor given in argument. + * If actor is not provided, animation of all the components linked to the {@link module:enyo/AnimationSupport/Scene} will be reversed. + * @param [Component]{@link module:enyo/Component~Component} actor The component to be animated + * @public + */ + reverse: function(actor) { + this.cache(actor); + actor = actor || this; + actor._cachedValue = actor.speed; + actor.speed *= -1; + }, + + /** + * fast description goes here + * @param {Number} mul description goes here + * @param [Component]{@link module:enyo/Component~Component} actor description goes here + * @public + */ + fast: function(mul, actor) { + this.cache(actor); + actor = actor || this; + actor.speed *= mul; + }, + + /** + * slow description goes here + * @param {Number} mul description goes here + * @param [Component]{@link module:enyo/Component~Component} actor description goes here + * @public + */ + slow: function(mul, actor) { + this.cache(actor); + actor = actor || this; + actor.speed *= mul; + }, + + /** + * Changes the speed of the animation.
+ * Speed of the animation changed based on the factor.
+ * To slow down the speed use values between 0 and 1. For Example 0.5 to reduce the speed by 50%.
+ * To increase the speed use values above 1. For Example 2 to increase the speed by 200%.
+ * Animation will be paused if factor is 0. To pause the animation use {@link enyo/AnimationSupport/Editor.pause pause} API.
+ * Speed will not be affected incase of negative multiplication factor. + * @param {Number} factor Multiplication factor which changes the speed + * @param [Component {@link module:enyo/Component~Component}] actor The component whose animating speed should be changed + * @public + */ + // speed: function(mul, actor) { + // if (mul < 0) return; + // this.cache(actor); + // actor = actor || this; + // actor.speed *= mul; + // }, + + /** + * Stops the animation of the actor given in argument. + * If actor is not provided, animation of all the components linked to the {@link module:enyo/AnimationSupport/Scene} will be stopped. + * @param [Component]{@link module:enyo/Component~Component} actor The component to be animated + * @public + */ + stop: function(actor) { + actor = actor || this; + actor._cachedValue = 1; + actor.speed = 0; + actor.timeline = 0; + // this.animating = false; + // this.cancel(); + }, + + /** + * Seeks the animation of the actor to the position provided in seek + * The value of seek should be between 0 to duration of the animation. + * @param {Number} seek Value in seek where the animation has to be seeked + * @param [Component]{@link module:enyo/Component~Component} actor The component to be animated + * @public + */ + seek: function(seek, actor) { + actor = actor || this; + actor.timeline = seek; + }, + + /** + * Seeks actor with animation to the position provided in seek + * The value of seek should be between 0 to duration of the animation. + * @param {Number} seek Value in seek where the animation has to be seeked + * @param [Component]{@link module:enyo/Component~Component} actor The component to be animated + * @public + */ + seekAnimate: function(seek, actor) { + actor = actor || this; + if (seek >= 0) { + if (!this.animating) + this.play(actor); + actor.speed = 1; + } else { + actor.speed = -1; + } + actor.seekInterval = actor.timeline + seek; + if (actor.seekInterval < 0) { + actor.speed = 0; + actor.seekInterval = 0; + } + }, + + //TODO: Move these events to Event Delegator + /** + * Event to identify when the scene has done animating. + * @memberOf module:enyo/AnimationSupport/Actor + * @public + */ + completed: function() { + this.animating = false; + }, + + /** + * Event to identify when the scene has done a step(rAF updatation of time) in the animation. + * @memberOf module:enyo/AnimationSupport/Actor + * @public + */ + step: function() {} }; diff --git a/src/logger.js b/src/logger.js index 83440115f..1dccd4dee 100644 --- a/src/logger.js +++ b/src/logger.js @@ -12,22 +12,22 @@ var * * ios * * webos * -* @private +* @ignore */ var dumbConsole = Boolean(platform.android || platform.ios || platform.webos); /** * Internally used methods and properties associated with logging. * -* @module enyo/logging +* @module enyo/logger * @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} + * this value be set using the [setLogLevel()]{@link module:enyo/logger#setLogLevel} * method. * * @type {Number} @@ -35,14 +35,14 @@ exports = module.exports = { * @public */ level: 99, - + /** * The known levels. * * @private */ levels: {log: 20, warn: 10, error: 0}, - + /** * @private */ @@ -50,7 +50,7 @@ exports = module.exports = { var ll = parseInt(this.levels[fn], 0); return (ll <= this.level); }, - + /** * @private */ @@ -66,7 +66,7 @@ exports = module.exports = { } } }, - + /** * @private */ @@ -99,7 +99,7 @@ exports = module.exports = { console.log(a$.join(' ')); } }, - + /** * This is exposed elsewhere. * @@ -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/logging#log}. +* Without the 'see': {@link module:enyo/logger#log}. * -* @see module:enyo/logging#level -* @see module:enyo/logging#log -* @see module:enyo/logging#warn -* @see module:enyo/logging#error +* @see module:enyo/logger#level +* @see module:enyo/logger#log +* @see module:enyo/logger#warn +* @see module:enyo/logger#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/logger#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/logger#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/logger#level} allows it. [Object]{@glossary Object} * parameters will be serialized via [JSON.stringify()]{@glossary JSON.stringify} * automatically. * diff --git a/src/pageVisibility.js b/src/pageVisibility.js index c493e7ff9..6f45652f7 100644 --- a/src/pageVisibility.js +++ b/src/pageVisibility.js @@ -22,7 +22,7 @@ var * module.exports = kind({ * name: 'App', * components: [ -* {kind: 'Signals', onvisibilitychange: 'visibilitychanged'} +* {kind: Signals, onvisibilitychange: 'visibilitychanged'} * ], * visibilitychanged: function() { * if(enyo.hidden){ diff --git a/src/platform.js b/src/platform.js index 831564c56..804c4a3ac 100644 --- a/src/platform.js +++ b/src/platform.js @@ -10,6 +10,7 @@ var utils = require('./utils'); * * androidChrome (Chrome on Android, standard starting in 4.1) * * androidFirefox * * ie +* * edge * * ios * * webos * * windowsPhone @@ -35,24 +36,37 @@ var utils = require('./utils'); * * @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) +exports = module.exports = { + /** + * `true` if the platform has native single-finger [events]{@glossary event}. + * @public + */ + touch: Boolean(('ontouchstart' in window) || window.navigator.msMaxTouchPoints || (window.navigator.msManipulationViewsEnabled && window.navigator.maxTouchPoints)), + /** + * `true` if the platform has native double-finger [events]{@glossary event}. + * @public + */ + gesture: Boolean(('ongesturestart' in window) || ('onmsgesturestart' in window && (window.navigator.msMaxTouchPoints > 1 || window.navigator.maxTouchPoints > 1))) + + /** + * The name of the platform that was detected or `undefined` if the platform + * was unrecognized. This value is the key name for the major version of the + * platform on the exported object. + * @member {String} platformName + * @public + */ + }; -/** -* @private -*/ var ua = navigator.userAgent; var ep = exports; var platforms = [ + // Windows Phone 7 - 10 + {platform: 'windowsPhone', regex: /Windows Phone (?:OS )?(\d+)[.\d]+/}, // Android 4+ using Chrome {platform: 'androidChrome', regex: /Android .* Chrome\/(\d+)[.\d]+/}, // Android 2 - 4 - {platform: 'android', regex: /Android (\d+)/}, + {platform: 'android', regex: /Android(?:\s|\/)(\d+)/}, // Kindle Fire // Force version to 2, (desktop mode does not list android version) {platform: 'android', regex: /Silk\/1./, forceVersion: 2, extra: {silk: 1}}, @@ -60,12 +74,12 @@ var platforms = [ // 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+)/}, + // Edge + {platform: 'edge', regex: /Edge\/(\d+)/}, // iOS 3 - 5 // Apple likes to make this complicated {platform: 'ios', regex: /iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/}, diff --git a/src/ready.js b/src/ready.js index c9937bb8e..d77a36c98 100644 --- a/src/ready.js +++ b/src/ready.js @@ -56,7 +56,7 @@ init = function (event) { flush(); } } - // for an IE8 fallback and legacy WebKit (including webOS 3.x and less) and assurance + // for legacy WebKit (including webOS 3.x and less) and assurance if ((ready = ("complete" === doc.readyState || "loaded" === doc.readyState))) { remove(event.type, init); flush(); @@ -67,18 +67,14 @@ init = function (event) { * @private */ add = function (event, fn) { - var name = doc.addEventListener? "addEventListener": "attachEvent"; - var on = name === "attachEvent"? "on": ""; - doc[name](on + event, fn, false); + doc.addEventListener(event, fn, false); }; /** * @private */ remove = function (event, fn) { - var name = doc.addEventListener? "removeEventListener": "detachEvent"; - var on = name === "detachEvent"? "on": ""; - doc[name](on + event, fn, false); + doc.removeEventListener(event, fn, false); }; /** diff --git a/src/resolution.js b/src/resolution.js index 883bb97bd..c4714cd9d 100644 --- a/src/resolution.js +++ b/src/resolution.js @@ -37,7 +37,9 @@ var ri = module.exports = { * Executing this method also initializes the rest of the resolution-independence code. * * ``` - * ri.defineScreenTypes([ + * var resolution = require('enyo/resolution'); + * + * resolution.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'}, @@ -182,14 +184,12 @@ var ri = module.exports = { }, /** - * The default configurable [options]{@link ri.selectSrc#options}. + * The default configurable [options]{@link module:enyo/resolution.selectSrc#options}. * - * @typedef {Object} ri.selectSrc~src + * @typedef {Object} module:enyo/resolution.selectSrcSrc * @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 */ /** @@ -217,7 +217,7 @@ var ri = module.exports = { * {kind: Image, src: http://lorempixel.com/128/128/city/1/', alt: 'Large'}, * ``` * - * @param {(String|module:enyo/resolution#selectSrc~src)} src - A string containing + * @param {(String|module:enyo/resolution~selectSrcSrc)} 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. diff --git a/src/scene.js b/src/scene.js new file mode 100644 index 000000000..c0cb7713c --- /dev/null +++ b/src/scene.js @@ -0,0 +1,457 @@ +var + tween = require('./tween'), + utils = require('./utils'), + animation = require('./animation'); + +var _ts, _wasts, _framerate = 16.6; + +/** + * This module exports "Scene" which is a class/constructor so that we can create an instance of the same. + * We can define all the animation properties we want in the application in the instance of the "Scene". + * + * @module enyo/AnimationSupport/Scene + */ +var scene = module.exports = function(props) { + var ctx = scene.create(); + + if (props.animation) { + var anims = utils.isArray(props.animation) ? props.animation : [props.animation]; + for (var i = 0; i < anims.length; i++) { + ctx.addAnimation(anims[i], anims[i].duration || 0); + } + } + + utils.mixin(ctx, SceneAction); + ctx.rolePlays = []; + utils.mixin(ctx, props); + return ctx; +}; + +var SceneAction = { + + isScene: true, + + /** + * The boundary of scene within which the scene actors will be animating. + * @memberOf module:enyo/AnimationSupport/Scene + * @public + * @type {number} + */ + threshold: 0, + + /** + * The actors which are added to this scene. Each actor is responsible for its own role + * within a scene. + * @memberOf module:enyo/AnimationSupport/Scene + * @public + * @type {Array} + */ + rolePlays: [], + /** + * An Array of ids of all the actors participating in animation for a scene. + * @memberOf module:enyo/AnimationSupport/Scene + * @public + * @type {Array} + */ + actorsIds: [], + /** + * An array of ids of all the animation completed actors. + * @memberOf module:enyo/AnimationSupport/Scene + * @public + * @type {Array} + */ + completedActors: [], + /** + * This function compares two arrays to know whether they are same or not. + * @memberOf module:enyo/AnimationSupport/Scene + * @private + * @param {Array} array1 First Array + * @param {Array} array2 Second Array + * @return {Boolean} True => Both arrays are same; False => Arrays are not same. + */ + compareArrays: function(array1, array2) { + var array1 = array1; + var array2 = array2; + var is_same = array1.length == array2.length && array1.every(function(element, index) { + return element === array2[index]; + }); + return is_same; + }, + /** + * This function initiates action on the animation + * from the list of animations for a given scene in sequence of actors. + * @param {number} ts - timespan + * @param {Object} pose - pose from the animation list + * @return {Object} - pose + * @memberOf module:enyo/AnimationSupport/Scene + * @private + */ + sceneConstAction: function(ts, pose) { + var past, index, tm, + dur = this.span; + + if (_actor && _actor.generated) { + tm = rolePlay(ts, this); + if (isNaN(tm) || tm < 0) return pose; + else if (tm <= dur) { + if (SceneAction.actorsIds.indexOf(this.id) === -1) { + SceneAction.actorsIds.push(this.id); + } + index = animateAtTime(_poses, tm); + pose = this.getAnimation(index); + past = index ? this.getAnimation(index - 1).span : 0; + if (pose.animate instanceof this.constructor === true) { + pose.animate.speed = this.speed; + pose.animate.action(ts); + } else { + _update(pose, _actor, tm - past, pose.span - past); + } + this.step && this.step(_actor); + } else { + this.timeline = this.repeat ? 0 : this.span; + if (!this.repeat) this.cut(); + } + } + return pose; + }, + /** + * This function initiates action on the animation + * from the list of animations for a given scene. + * @param {number} ts - timespan + * @param {Object} pose - pose from the animation list + * @return {Object} - pose + * @memberOf module:enyo/AnimationSupport/Scene + * @private + */ + action: function(ts, pose) { + + var i, role, actor, rolePlays, + s, e, + sts = 0, + tm = this.timeline, + th = this.threshold || this.span; + rolePlays = this.rolePlays; + + if (this.sequencing && this.sequencing === true) { + sceneConstAction(ts, pose); + } else { + if (rolePlays && rolePlays.length > 0 && this.animating === true) { + if (this.actorsIds.length !== 0 && this.completedActors.length !== 0 && this.compareArrays(this.actorsIds, this.completedActors) === true) { + this.animating = false; + this.completedAction = true; + } + s = animateAtTime(rolePlays, tm); + e = animateAtTime(rolePlays, tm + th); + e += e == s ? 1 : 0; + + for (i = 0; + (role = rolePlays[i]); i++) { + actor = role.actor; + if (i < s) { + actor.active = true; + actor.timeline = role.dur; + pose = actor.action(0, pose); + actor.cut(); + } else if (i >= s && i < e) { + actor.active = true; + sts += ts; + actor.speed = this.speed; + actor.repeat = this.repeat; + pose = actor.action(ts, pose); + } else { + if (actor.active) { + pose = actor.action(0, pose); + actor.cut(); + } + } + } + tm = rolePlay(sts); + } + } + return pose; + } +}; + +/** + * Connects an actor/s to a scene. + * All the actors should be added before initiating animation otherwise actors will animate for remaining time span + * @memberOf module:enyo/AnimationSupport/Scene + * @public + * @param {Object} actors - The elements which needs to be animated + * @param {Object} scene - The instance of the Scene we've created in the application + */ +scene.link = function(actors, scene) { + if (!scene && !actors) return; + + var actorScene, span = 0, + acts = utils.isArray(actors) ? actors : [actors]; + + for (var act, i = 0; + (act = acts[i]); i++) { + if (scene.isScene) { + act.scene = act.scene || scene.create(scene, act); + actorScene = act.scene.isScene ? act.scene : scene.create(act.scene, act); + span += actorScene.span; + scene.rolePlays.push({ + actor: actorScene, + span: span, + dur: actorScene.span + }); + scene.span = span; + } else { + actorScene = scene.create(scene, act); + acts[i].scene = actorScene; + } + } +}; + +/** + * Creates a empty instance of scene. + * Can be used for runtime creation of animations + * @memberOf module:enyo/AnimationSupport/Actor + * @public + * @return {Object} An instance of the constructor + */ +scene.create = function(ctx) { + return new sceneConstructor(ctx); +}; + + +function sceneConstructor(actor) { + var + _req, + _prevDur = 0, + _poses = [], + _actor = actor, + _update = function(pose, actor, since, dur) { + var t; + if (!pose._startAnim) tween.init(actor, pose); + + if (since < 0) since = 0; + if (since <= dur && dur !== 0) { + t = since / dur; + // tween.step(actor, pose, t, dur); + tween.step(actor, pose, (t > 0.98) ? 1 : t, dur); + } else { + tween.step(actor, pose, 1, dur); + } + }, + /** + * Function used to loop in all the animations in a scene + * @memberOf module:enyo/AnimationSupport/Actor + * @private + */ + _loop = function() { + if (this.animating) { + _ts = utils.perfNow(); + _ts = _ts - (_wasts !== undefined ? _wasts : _ts); + _ts = (_ts > _framerate) ? _framerate : _ts; + this.action(_ts); + _wasts = _ts; + this.trigger(true); + } else { + _wasts = undefined; + this.cancel(); + this.completed && this.completed(_actor); + } + }; + + this.span = 0; + this.id = utils.uid("@"); + + /** + * This function initiates action on the animation + * from the list of animations for a given scene. + * @param {number} ts - timespan + * @param {Object} pose - pose from the animation list + * @return {Object} - pose + * @memberOf module:enyo/AnimationSupport/Actor + * @private + */ + this.action = function(ts, pose) { + var past, index, tm, + dur = this.span; + + if (_actor && _actor.generated) { + tm = rolePlay(ts, this); + if (isNaN(tm) || tm < 0) return pose; + else if (tm <= dur) { + if (SceneAction.actorsIds.indexOf(this.id) === -1) { + SceneAction.actorsIds.push(this.id); + } + index = animateAtTime(_poses, tm); + pose = this.getAnimation(index); + past = index ? this.getAnimation(index - 1).span : 0; + if (pose.animate instanceof this.constructor === true) { + pose.animate.speed = this.speed; + pose.animate.action(ts); + } else { + _update(pose, _actor, tm - past, pose.span - past); + } + this.step && this.step(_actor); + } else { + if (typeof this.repeat === "boolean") + this.repeat = this.repeat ? Infinity : 1; + this.timeline = --this.repeat ? 0 : this.span; + if (this.timeline === this.span) this.cut(); + } + } + return pose; + }; + + this.cut = function() { + if (this.handleLayers) { + this.speed = 0; + if (this.active) { + this.active = false; + tween.halt(actor); + } + } + this.animating = false; + if (SceneAction.completedActors.indexOf(this.id) === -1) { + SceneAction.completedActors.push(this.id); + } + this.completed && this.completed(_actor); + + }; + + /** + * Cancel the animation + * @memberOf module:enyo/AnimationSupport/Actor + * @public + */ + this.cancel = function() { + animation.cancelRequestAnimationFrame(_req); + }; + + /** + * Triggers the Request Animation Frame + * @param {boolean} force - A boolean value for letting the rAF start. + * @memberOf module:enyo/AnimationSupport/Actor + * @public + */ + this.trigger = function(force) { + if (force || !this.animating) { + _req = animation.requestAnimationFrame(utils.bindSafely(this, _loop)); + } + }; + + /** + * Returns animation pose based on index from the list of + * animations added to this scene. + * @param {number} index - animation's index from the list of animations + * @return {Object} pose of the animation based on the index in the list + * @memberOf module:enyo/AnimationSupport/Actor + * @public + */ + this.getAnimation = function(index) { + return index < 0 || _poses[index]; + }; + + /** + * Adds new animation on already existing animation for this character. + * @memberOf module:enyo/AnimationSupport/Actor + * @public + */ + this.addAnimation = function(newProp, span) { + span = span ? span : newProp.span; + var delay = newProp.delay; + if (delay) { + this.span += delay; + _poses.push({ + animate: { + duration: delay + }, + span: this.span + }); + } + _prevDur = span || _prevDur; + this.span += _prevDur; + _poses.push({ + animate: newProp, + span: this.span + }); + }; + /** + * Adds new animation on the actors in a parent scene. + * @param {Object} newProp New animation properties + * @param {Number} span duration of animation + */ + this.extend = function(newProp, span) { + for (var i = 0; i < this.rolePlays.length; i++) { + this.rolePlays[i].actor.addAnimation(newProp, span); + } + }; + /** + * Clears/removes the animation + * @memberOf module:enyo/AnimationSupport/Actor + * @public + */ + this.clearAnimation = function() { + for (var i = 0; i < _poses.length; i++) { + _poses[i]._startAnim = undefined; + } + }; + + /** + * Function which returns the length of the poses. + * @memberOf module:enyo/AnimationSupport/Actor + * @public + * @return {number} - length of the poses + */ + this.length = function() { + return _poses.length; + }; +} + +/** + * rolePlay updated the timeline of the actor which is currently animating. + * @param {Number} t Elapsed time since the animation of this pose has started (ratio in factor of 1) + * @param {@link module:enyo/Component~Component} actor The component which is animating + * @return {Number} Returns the updated timeline of the actor + * @private + */ +function rolePlay(t, actor) { + actor = actor || this; + actor.timeline += (t * actor.speed); + + if (actor.seekInterval !== 0) { + if ((actor.seekInterval - actor.timeline) * actor.speed < 0) { + actor.seekInterval = 0; + actor.speed = 0; + } + } + + if (actor.timeline === undefined || actor.timeline < 0) + actor.timeline = 0; + return actor.timeline; +} + +/** + * Returns animation pose index for a particular + * instance of time from the list of + * animations added to the scene. + * @param {number} span - Time span from the animation timeline + * @return {number} - index of the animation + * @private + */ +function animateAtTime(anims, span) { + var startIndex = 0, + stopIndex = anims.length - 1, + middle = Math.floor((stopIndex + startIndex) / 2); + + if (span === 0) { + return startIndex; + } + + while (anims[middle].span != span && startIndex < stopIndex) { + if (span < anims[middle].span) { + stopIndex = middle; + } else if (span > anims[middle].span) { + startIndex = middle + 1; + } + + middle = Math.floor((stopIndex + startIndex) / 2); + } + return (anims[middle].span != span) ? startIndex : middle; +} diff --git a/src/touch.js b/src/touch.js index d31a64662..99ee7eca2 100644 --- a/src/touch.js +++ b/src/touch.js @@ -1,3 +1,9 @@ +/** +* This module has no exports. It merely extends {@link module:enyo/gesture} to enable touch events. +* +* @private +* @module enyo/touch +*/ require('enyo'); var @@ -31,7 +37,7 @@ Dom.requiresWindow(function() { var oldevents = gesture.events; /** - * @private + * @ignore */ gesture.events.touchstart = function (e) { // for duration of this touch, only handle touch events. Old event @@ -41,16 +47,18 @@ Dom.requiresWindow(function() { }; /** - * @private + * @ignore */ var touchGesture = { /** + * @ignore * @private */ _touchCount: 0, /** + * @ignore * @private */ touchstart: function (e) { @@ -67,6 +75,7 @@ Dom.requiresWindow(function() { }, /** + * @ignore * @private */ touchmove: function (e) { @@ -99,6 +108,7 @@ Dom.requiresWindow(function() { }, /** + * @ignore * @private */ touchend: function (e) { @@ -124,6 +134,7 @@ Dom.requiresWindow(function() { * back to default; this works as long as no one did a `preventDefault()` on * the touch events. * + * @ignore * @private */ mouseup: function () { @@ -134,6 +145,7 @@ Dom.requiresWindow(function() { }, /** + * @ignore * @private */ makeEvent: function (e) { @@ -147,6 +159,7 @@ Dom.requiresWindow(function() { }, /** + * @ignore * @private */ calcNodeOffset: function (node) { @@ -162,6 +175,7 @@ Dom.requiresWindow(function() { }, /** + * @ignore * @private */ findTarget: function (e) { @@ -172,6 +186,7 @@ Dom.requiresWindow(function() { * NOTE: Will find only 1 element under the touch and will fail if an element is * positioned outside the bounding box of its parent. * + * @ignore * @private */ findTargetTraverse: function (node, x, y) { @@ -196,16 +211,13 @@ Dom.requiresWindow(function() { }, /** + * @ignore * @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); - } + // on iOS7 document.ongesturechange is never called + document.addEventListener(e, dispatch, false); }); if (platform.androidChrome <= 18 || platform.silk === 2) { diff --git a/src/transform.js b/src/transform.js new file mode 100644 index 000000000..08b47bc13 --- /dev/null +++ b/src/transform.js @@ -0,0 +1,743 @@ +require('enyo'); + +/** + * To create a Typed_array + * @param {Number} size The size of the buffer required + * @return {Number[]} Typed_array + */ +function typedArray (size) { + return new Float32Array(new ArrayBuffer(size)); +} + +/** + * To input the specified indices with value 1 + * @param {Number[]} matrix typedArray sent + * @param {Number[]} numberMat indices where value has to be 1 + */ +function inputValues (matrix, numberMat) { + for (var i = 0; i < numberMat.length; i++) { + matrix[numberMat[i]] = 1; + } +} + +/** + * Transform module for transform related calculation + * + * @module enyo/AnimationSupport/Transform + */ + + +/** + * To create Identity Matrix3d as array. + * @public + * @return {Number[]} Identity Matrix3d + */ +var identity = exports.identity = function() { + var identityMatrix, modifiedMat; + identityMatrix = typedArray(64); + modifiedMat = inputValues(identityMatrix, new Uint8Array([0, 5, 10, 15])); + return identityMatrix; +}; + +/** + * To create Identity Matrix2d as array. + * @public + * @return {Number[]} Identity Matrix2d as array + */ +exports.identity2D = function() { + var identity2D, modifiedMat; + identity2D = typedArray(36); + modifiedMat = inputValues(identity2D, new Uint8Array([0, 4, 8])); + return identity2D; +}; + +/** + * To translate in any dimension based on co-ordinates. + * @public + * @param {Number} x Translate value in X axis + * @param {Number} y Translate value in Y axis + * @param {Number} z Translate value in Z axis + * @return {Number[]} Matrix3d + */ +exports.translate = function(x, y, z) { + var translateMat, modifiedMat; + translateMat = typedArray(64); + modifiedMat = inputValues(translateMat, new Uint8Array([0, 5, 10, 15])); + translateMat[12] = x; + translateMat[13] = y ? y : 0; + translateMat[14] = z ? z : 0; + return translateMat; +}; + +/** + * To translate in x dimension + * @public + * @param {Number} x Translate value in X axis + * @return {Number[]} Matrix3d + */ +exports.translateX = function(x) { + var translateX, modifiedMat; + translateX = typedArray(64); + modifiedMat = inputValues(translateX, new Uint8Array([0, 5, 10, 15])); + translateX[12] = x ? x : 0; + return translateX; +}; + +/** + * To translate in y dimension + * @public + * @param {Number} y Translate value in Y axis + * @return {Number[]} Matrix3d + */ +exports.translateY = function(y) { + var translateY, modifiedMat; + translateY = typedArray(64); + modifiedMat = inputValues(translateY, new Uint8Array([0, 5, 10, 15])); + translateY[13] = y ? y : 0; + return translateY; +}; + +/** + * To translate in z dimension + * @public + * @param {Number} z Translate value in Z axis + * @return {Number[]} Matrix3d + */ +exports.translateZ = function(z) { + var translateZ, modifiedMat; + translateZ = typedArray(64); + modifiedMat = inputValues(translateZ, new Uint8Array([0, 5, 10, 15])); + translateZ[14] = z ? z : 0; + return translateZ; +}; + +/** + * To scale in any dimension + * @public + * @param {Number} x Scale value in X axis + * @param {Number} y Scale value in Y axis + * @param {Number} z Scale value in Z axis + * @return {Number[]} Matrix3d + */ +exports.scale = function(x, y, z) { + var scaleMat = typedArray(64); + scaleMat[0] = x; + scaleMat[5] = y ? y : 1; + scaleMat[10] = z ? z : 1; + scaleMat[15] = 1; + return scaleMat; +}; + +/** + * To skew in any dimension (skew can only happen in 2d) + * @public + * @param {Number} a Skew value in X axis + * @param {Number} b Skew value in Y axis + * @return {Number[]} Matrix3d + */ +exports.skew =function(a, b) { + var skewMat, modifiedMat; + a = a ? Math.tan(a * Math.PI / 180) : 0; + b = b ? Math.tan(b * Math.PI / 180) : 0; + + skewMat = typedArray(64); + modifiedMat = inputValues(skewMat, new Uint8Array([0, 5, 10, 15])); + skewMat[1] = b; + skewMat[4] = a; + return skewMat; +}; + +/** + * To rotate in x-axis + * @public + * @param {Number} a Rotate value in X axis + * @return {Number[]} Matrix3d + */ +exports.rotateX = function(a) { + var cosa, sina, rotateXMat, modifiedMat; + a = a ? a * Math.PI / 180 : 0; + cosa = Math.cos(a); + sina = Math.sin(a); + + rotateXMat = typedArray(64); + modifiedMat = inputValues(rotateXMat, new Uint8Array([0, 15])); + rotateXMat[5] = cosa; + rotateXMat[6] = -sina; + rotateXMat[9] = sina; + rotateXMat[10] = cosa; + return rotateXMat; +}; + +/** + * To rotate in y-axis + * @public + * @param {Number} b Rotate value in Y axis + * @return {Number[]} Matrix3d + */ +exports.rotateY = function(b) { + var cosb, sinb, rotateYMat, modifiedMat; + b = b ? b * Math.PI / 180 : 0; + cosb = Math.cos(b); + sinb = Math.sin(b); + + rotateYMat = typedArray(64); + modifiedMat = inputValues(rotateYMat, new Uint8Array([5, 15])); + rotateYMat[0] = cosb; + rotateYMat[2] = sinb; + rotateYMat[8] = -sinb; + rotateYMat[10] = cosb; + return rotateYMat; +}; + +/** + * To rotate in z-axis + * @public + * @param {Number} g Rotate value in Z axis + * @return {Number[]} Matrix3d + */ +exports.rotateZ = function(g) { + var cosg, sing, rotateZMat; + g = g ? g * Math.PI / 180 : 0; + cosg = Math.cos(g); + sing = Math.sin(g); + + rotateZMat = typedArray(64); + rotateZMat[0] = cosg; + rotateZMat[1] = -sing; + rotateZMat[4] = sing; + rotateZMat[5] = cosg; + rotateZMat[15] = 1; + return rotateZMat; +}; + +/** + * To rotate in any dimension + * @public + * @param {Number} a Rotate value in X axis + * @param {Number} b Rotate value in Y axis + * @param {Number} g Rotate value in Z axis + * @return {Number[]} Matrix3d + */ +exports.rotate = function(a, b, g) { + var ca, sa, cb, sb, cg, sg, rotateMat; + a = a ? a * Math.PI / 180 : 0; + b = b ? b * Math.PI / 180 : 0; + g = g ? g * Math.PI / 180 : 0; + ca = Math.cos(a); + sa = Math.sin(a); + cb = Math.cos(b); + sb = Math.sin(b); + cg = Math.cos(g); + sg = Math.sin(g); + + rotateMat = typedArray(64); + rotateMat[0] = cb * cg; + rotateMat[1] = ca * sg + sa * sb * cg; + rotateMat[2] = sa * sg - ca * sb * cg; + rotateMat[4] = -cb * sg; + rotateMat[5] = ca * cg - sa * sb * sg; + rotateMat[6] = sa * cg + ca * sb * sg; + rotateMat[8] = sb; + rotateMat[9] = -sa * cb; + rotateMat[10] = ca * cb; + rotateMat[15] = 1; + return rotateMat; +}; + +var matrix = exports.Matrix = { + + /** + * To create Identity Matrix (NXN order). + * @public + * @param {Number} N Order of Identity Matrix + * @return {Number[][]} Identity Matrix of order N + */ + identityMatrix: function(N) { + var i, j, row, result = []; + for (i = 0; i < N; i++) { + row = []; + for (j = 0; j < N; j++) { + if (i === j) { + row.push(1); + } else { + row.push(0); + } + } + result.push(row); + } + return result; + }, + /** + * To multiply 2 Martix3d (4x4 order) + * @public + * @param {Number[]} m1 1st Matrix3d + * @param {Number[]} m2 2nd Matrix3d + * @return {Number[]} Resultant Matrix3d + */ + multiply: function(m1, m2) { + var multiplyMat = typedArray(64); + if (m1.length !== 16 || m2.length !== 16) return; + multiplyMat[0] = m1[0] * m2[0] + m1[4] * m2[1] + m1[8] * m2[2]; + multiplyMat[1] = m1[1] * m2[0] + m1[5] * m2[1] + m1[9] * m2[2]; + multiplyMat[2] = m1[2] * m2[0] + m1[6] * m2[1] + m1[10] * m2[2]; + multiplyMat[4] = m1[0] * m2[4] + m1[4] * m2[5] + m1[8] * m2[6]; + multiplyMat[5] = m1[1] * m2[4] + m1[5] * m2[5] + m1[9] * m2[6]; + multiplyMat[6] = m1[2] * m2[4] + m1[6] * m2[5] + m1[10] * m2[6]; + multiplyMat[8] = m1[0] * m2[8] + m1[4] * m2[9] + m1[8] * m2[10]; + multiplyMat[9] = m1[1] * m2[8] + m1[5] * m2[9] + m1[9] * m2[10]; + multiplyMat[10] = m1[2] * m2[8] + m1[6] * m2[9] + m1[10] * m2[10]; + multiplyMat[12] = m1[0] * m2[12] + m1[4] * m2[13] + m1[8] * m2[14] + m1[12]; + multiplyMat[13] = m1[1] * m2[12] + m1[5] * m2[13] + m1[9] * m2[14] + m1[13]; + multiplyMat[14] = m1[2] * m2[12] + m1[6] * m2[13] + m1[10] * m2[14] + m1[14]; + multiplyMat[15] = 1; + return multiplyMat; + }, + + /** + * To multiply 2 Martix3d (n*n order) + * @param {Number[]} m1 1st Matrix3d + * @param {Number[]} m2 2nd Matrix3d + * @return {[type]} Resultant Matrix3d + */ + 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; + }, + + /** + * To inverse matrix of order N + * @public + * @param {Number[]} matrix Matrix (NxN order) + * @param {Number} n Order of the matrix + * @return {Number[]} Inverted Matrix + */ + inverseN: function(matrix, n) { + var i, j, k, r, t, + result = this.identityMatrix(n); + + for (i = 0; i < n; i++) { + for (j = 0; j < n; j++) { + if (i != j) { + r = matrix[j][i] / matrix[i][i]; + for (k = 0; k < n; k++) { + matrix[j][k] -= r * matrix[i][k]; + result[j][k] -= r * result[i][k]; + } + } + } + } + + for (i = 0; i < n; i++) { + t = matrix[i][i]; + for (j = 0; j < n; j++) { + matrix[i][j] = matrix[i][j] / t; + result[i][j] = result[i][j] / t; + } + } + + return result; + }, + + /** + * Calculate matrix3d of a frame based on transformation vectors. + * @public + * @param {Number[]} trns Translate vector + * @param {Number[]} rot Rotate quaternion vector + * @param {Number[]} sc Scale vector + * @param {Number[]} sq Skew vector + * @param {Number[]} per Perspective vector + * @return {Number[]} Final Matrix3d for particular frame + */ + recompose: function(trns, rot, sc, sq, per) { + var i, + x = rot[0], + y = rot[1], + z = rot[2], + w = rot[3], + m = identity.call(), + sM = identity.call(), + rM = identity.call(); + + + // 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 = this.multiply(m, rM); + + // apply skew + if (sq[2]) { + sM[9] = sq[2]; + m = this.multiply(m, sM); + } + + if (sq[1]) { + sM[9] = 0; + sM[8] = sq[1]; + m = this.multiply(m, sM); + } + + if (sq[0]) { + sM[8] = 0; + sM[4] = sq[0]; + m = this.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; + }, + + /** + * Decompose transformation vectors into various properties out of matrix3d. + * @public + * @param {Number[]} matrix Matrix3d + * @param {Object} ret To store various transformation properties like translate, rotate, scale, skew and perspective. + * @return {Boolean} true, if matrix exists else false. + */ + decompose: 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[i] /= 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.length(row[0]); + row[0] = quaternion.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.length(row[1]); + row[1] = quaternion.normalize(row[1]); + skV[0] /= scV[1]; + + // Compute XZ and YZ shears, orthogonalized 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.length(row[2]); + row[2] = quaternion.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; + }, + + /** + * Decompose transformation matrix2d from matrix3d. + * @public + * @param {Number[]} matrix Matrix3d + * @param {Object} ret To store various transformation properties like translate, angle and matrix. + * @return {Boolean} ret To store various transformation properties like translate, angle and matrix. + */ + decompose2D: function(m, ret) { + var scale = [], + matrix = [], + row0x = m[0], + row0y = m[1], + row1x = m[4], + row1y = m[5], + det, angle, sn, cs, + m11, m12, m21, m22; + + ret = ret || {}; + scale = [ + Math.sqrt(row0x * row0x + row0y * row0y), + Math.sqrt(row1x * row1x + row1y * row1y) + ]; + + // If determinant is negative, one axis was flipped. + det = row0x * row1y - row0y * row1x; + if (det < 0) + // Flip axis with minimum unit vector dot product. + if (row0x < row1y) + scale[0] = -scale[0]; + else + scale[1] = -scale[1]; + + // Renormalize matrix to remove scale. + if (scale[0]) { + row0x *= 1 / scale[0]; + row0y *= 1 / scale[0]; + } + + if (scale[1]) { + row1x *= 1 / scale[1]; + row1y *= 1 / scale[1]; + } + ret.scale = scale; + + + // Compute rotation and renormalize matrix. + angle = Math.atan2(row0y, row0x); + + if (angle) { + sn = -row0y; + cs = row0x; + m11 = row0x; + m12 = row0y; + m21 = row1x; + m22 = row1y; + row0x = cs * m11 + sn * m21; + row0y = cs * m12 + sn * m22; + row1x = -sn * m11 + cs * m21; + row1y = -sn * m12 + cs * m22; + } + + // Rotate(-angle) = [cos(angle), sin(angle), -sin(angle), cos(angle)] + // = [row0x, -row0y, row0y, row0x] + // Thanks to the normalization above. + matrix[0] = row0x; + matrix[1] = row0y; + matrix[2] = row1x; + matrix[3] = row1y; + matrix[4] = m[12]; + matrix[5] = m[13]; + ret.matrix2D = matrix; + + // Convert into degrees because our rotation functions expect it. + ret.angle = angle * 180 / Math.PI; + + return ret; + }, + + /** + * Convert Matrix3d array to Matrix3d String + * @public + * @param {Number[]} m Matrix3d Array + * @return {String} Matrix3d String + */ + toString: function(m) { + var i, ms = m.length > 10 ? 'matrix3d(' : 'matrix('; + for (i = 0; i < m.length - 1; i++) { + ms += (m[i] < 0.000001 && m[i] > -0.000001) ? '0,' : m[i] + ','; + } + ms += m[m.length - 1] + ')'; + return ms; + } +}; + +var vector = exports.Vector = { + /** + * Length of a vector + * @param {Number[]} v - vector + * @return {Number} resultant length + * @public + */ + length: function(v) { + return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); + }, + + /** + * Divides vector with a scalar value. + * @param {Number[]} v - vector + * @param {Number} s - scalar value to divide + * @return {Number[]} resultant vector + * @public + */ + divide: function(v, s) { + var divideVector = new Float32Array([v[0] / s, v[1] / s, v[2] / s]); + return divideVector; + }, + + /** + * Dot product of 3D vectors + * @param {Number[]} v1 - vector + * @param {Number[]} v2 - vector + * @return {Number} resultant dot product + * @public + */ + dot: function(v1, v2) { + return (v1[0] * v2[0]) + (v1[1] * v2[1]) + (v1[2] * v2[2]) + (v1[3] !== undefined && v2[3] !== undefined ? (v1[3] * v2[3]) : 0); + }, + + /** + * Cross product of two vectors + * @param {Number[]} v1 - vector + * @param {Number[]} v2 - vector + * @return {Number[]} resultant cross product + * @public + */ + cross: function(v1, v2) { + var crossProdMat = new Float32Array([v1[1] * v2[2] - v1[2] * v2[1], v1[2] * v2[0] - v1[0] * v2[2], v1[0] * v2[1] - v1[1] * v2[0]]); + return crossProdMat; + }, + + /** + * Combine scalar values with two vectors. + * Required during parsing scaler values matrix. + * @param {Number[]} a - first vector + * @param {Number[]} b - second vector + * @param {Number[]} ascl - first vector scalar + * @param {Number[]} bscl - second vector scalar + * @return {Number[]} resultant vector + * @public + */ + combine: function(a, b, ascl, bscl) { + var combineMat = new Float32Array([(ascl * a[0]) + (bscl * b[0]), (ascl * a[1]) + (bscl * b[1]), (ascl * a[2]) + (bscl * b[2])]); + return combineMat; + + } +}; + +var quaternion = exports.Quaternion = { + /** + * Gives the direction of motion from one vector to other. + * Returns true if moving towards positive direction. + * @param {Number[]} q1 - quant + * @param {Number[]} q2 - quant + * @return {boolean} true if positive, false otherwise. + * @public + */ + direction: function(q1, q2) { + return (q1[0] - q2[0]) < 0 || (q1[1] - q2[1]) < 0 || (q1[2] - q2[2]) < 0; + }, + + /** + * Dot product of 3D quanterion + * @param {Number[]} q1 - quanterion + * @param {Number[]} q2 - quanterion + * @return {Number} resultant dot product + * @public + */ + quantDot: function(q1, q2) { + return (q1[0] * q2[0]) + (q1[1] * q2[1]) + (q1[2] * q2[2]) + (q1[3] * q2[3]); + }, + + /** + * Multiplication of two quanternions + * @param {Number[]} q1 quanternion + * @param {Number[]} q2 quanternion + * @return {Number[]} quanternion + */ + multiplication: function(q1, q2) { + return [q2[0] * q1[0] - q2[1] * q1[1] - q2[2] * q1[2] - q2[3] * q1[3], + q2[0] * q1[1] + q2[1] * q1[0] - q2[2] * q1[3] + q2[3] * q1[2], + q2[0] * q1[2] + q2[1] * q1[3] + q2[2] * q1[0] - q2[3] * q1[1], + q2[0] * q1[3] - q2[1] * q1[2] + q2[2] * q1[1] + q2[3] * q1[0]]; + }, + + /** + * Normalizing a vector is obtaining another unit vector in the same direction. + * To normalize a vector, divide the vector by its magnitude. + * @param {Number[]} q1 - quanterion + * @return {Number[]} resultant quanterion + * @public + */ + normalize: function(q) { + return vector.divide(q, vector.length(q)); + }, + + /** + * Converts a rotation vector to a quaternion vector. + * @param {Number[]} v - vector + * @return {Number[]} resultant quaternion + * @public + */ + toQuant: function(v) { + if (!v) v = []; + var 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; + + q = new Float32Array([ + Math.round((s1 * s2 * c3 + c1 * c2 * s3) * 100000) / 100000, + Math.round((s1 * c2 * c3 + c1 * s2 * s3) * 100000) / 100000, + Math.round((c1 * s2 * c3 - s1 * c2 * s3) * 100000) / 100000, + Math.round((c1 * c2 * c3 - s1 * s2 * s3) * 100000) / 100000 + ]); + + return q; + } + //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/Tween.js b/src/tween.js similarity index 72% rename from src/AnimationSupport/Tween.js rename to src/tween.js index d4319771f..bfbed0aea 100644 --- a/src/AnimationSupport/Tween.js +++ b/src/tween.js @@ -1,20 +1,18 @@ require('enyo'); var - frame = require('./Frame'), - Easings = require('./Easings'), - Vector = require('./Vector'), - Matrix = require('./Matrix'), - utils = require('../utils'); + dom = require('./dom'), + utils = require('./utils'), + transform = require('./transform'); -var fn, state, ease, points, path, oldState, newState, node, matrix, cState = []; +var fn, state, ease, points, path, oldState, newState, node, matrix, cState = [], domCSS = {}; /** * 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 enyo/tween */ module.exports = { @@ -24,7 +22,7 @@ module.exports = { init: function (actor, pose, initial) { if (!(actor && pose && pose.animate)) return; node = actor.hasNode(); - utils.mixin(pose, frame.getComputedProperty(node, pose.animate, initial || actor.currentState)); + utils.mixin(pose, dom.getAnimatedProperty(node, pose.animate, initial || actor.currentState)); actor.currentState = pose.currentState; return pose; }, @@ -35,14 +33,16 @@ module.exports = { * @param {Object} pose - Current behavior in the animation (at a given time) * @param {Number} t - Fraction which represents the animation (between 0 to 1) * @param {Number} d - Duration of the current pose - * @memberOf module:enyo/AnimationSupport/Tween + * @memberOf module:enyo/tween * @private */ step: function(actor, pose, t, d) { if (!(actor && pose && pose.animate)) return; - if (t<0) t=0; - if (t>1) t=1; + t = t < 0 ? 0 : t; + t = t > 1 ? 1 : t; + var k; + domCSS = {}; node = actor.hasNode(); state = actor.currentState = actor.currentState || pose.currentState || {}; points = pose.controlPoints = pose.controlPoints || {}; @@ -51,17 +51,16 @@ module.exports = { if (pose.props) { for (k in pose.props) { - if (!pose._endAnim[k]) { + if (k === 'delay' || k === 'duration' || k === 'ease' || !pose._endAnim[k]) { continue; } cState = utils.clone(state[k] || []); - newState = utils.clone(pose._endAnim[k]); - oldState = utils.clone(pose._startAnim[k]); + newState = utils.cloneArray(pose._endAnim[k]); + oldState = utils.cloneArray(pose._startAnim[k]); if (ease && (typeof ease !== 'function')) { - if (k == 'rotate') { - newState = Vector.toQuant(newState); + if (k === 'rotate') { points[k] = points[k] || this.bezierSPoints(ease, oldState, newState, pose.props[k], points[k]); fn = this.bezierSpline; @@ -72,18 +71,12 @@ module.exports = { } cState = fn.call(this, t, points[k], cState); } else { - if (k == 'rotate') { - newState = Vector.toQuant(newState); - fn = this.slerp; - } else { - fn = this.lerp; - } + fn = (k === 'rotate') ? this.slerp : this.lerp; cState = fn.call(this, oldState, newState, ease(t, d), cState); } - if (!frame.isTransform(k)) { - - frame.setProperty(actor, k, cState); + if (!utils.isTransform(k)) { + domCSS = utils.toPropertyValue(k, cState, domCSS); } state[k] = cState; } @@ -96,16 +89,24 @@ module.exports = { this.traversePath(t, path, state.translate); } - matrix = frame.recomposeMatrix( + matrix = transform.Matrix.recompose( state.translate, state.rotate, state.scale, state.skew, state.perspective ); - frame.accelerate(actor, matrix); state.matrix = matrix; - pose.currentState = state; + actor.currentState = pose.currentState = state; + domCSS = dom.toTransformValue(matrix, domCSS); + + actor.addStyles(domCSS); + }, + + halt: function (actor, pose) { + var matrix = actor.currentState && actor.currentState.matrix; + pose = frame.decompose2DMatrix(matrix, pose); + frame.accelerate(actor, pose.matrix2D); }, /** @@ -157,12 +158,12 @@ module.exports = { var a, b, theta, - dot = Vector.quantDot(qA, qB), + dot = transform.Quaternion.quantDot(qA, qB), l = qA.length; dot = Math.min(Math.max(dot, -1.0), 1.0); if (dot == 1.0) { - qR = frame.copy(qA); + qR = utils.clone(qA); return qR; } theta = Math.acos(dot); @@ -192,7 +193,7 @@ module.exports = { lastIndex = (c - 1), startPoint = points[0], endPoint = points[lastIndex], - values = Easings.getBezierValues(t, lastIndex); + values = this.getBezierValues(t, lastIndex); for (i = 0; i < l; i++) { vR[i] = 0; @@ -231,17 +232,21 @@ module.exports = { for (var key in easeObj) { t = parseFloat(key) / 100; a = parseFloat(easeObj[key]) / 100; - bValues = Easings.getBezierValues(t, order); + bValues = this.getBezierValues(t, order); bValues.shift(); m1.push(a - bValues.pop()); m2.push(bValues); } - m3 = Matrix.inverseN(m2, bValues.length); - m4 = Matrix.multiplyN(m3, m1); + m3 = transform.Matrix.inverseN(m2, bValues.length); + m4 = transform.Matrix.multiplyN(m3, m1); l = m4.length; for (var i = 0; i < l; i++) { - points.push([m4[i], m4[i], m4[i]]); + var pValues = []; + for( var j = 0; j < endPoint.length; j++) { + pValues.push(m4[i]); + } + points.push(pValues); } points.push(endPoint); @@ -264,7 +269,7 @@ module.exports = { c = path.length, l = path[0].length, lastIndex = (c - 1), - values = Easings.getBezierValues(t, lastIndex); + values = this.getBezierValues(t, lastIndex); for (i = 0; i < l; i++) { vR[i] = 0; @@ -290,26 +295,24 @@ module.exports = { var time = [0], quats = [startQuat]; - var t, a, q, n, _a, aI, bN, - eD = frame.parseValue(endPoint); + var a, n, _a, aI, bN, eP, + eD = utils.stringToMatrix(endPoint); splinePoints = splinePoints || {}; - quats.push(startQuat); - time.push(0); if (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); + eP = utils.clone(eD); + a = parseFloat(ease[key]) / 100; + for (var i in eP) { + eP[i] *= a; + } + quats.push(transform.Quaternion.toQuant(eP)); + time.push(parseFloat(key) / 100); } quats.push(endQuat); time.push(1); n = quats.length - 1; - ai = this.slerp(startQuat, endQuat, 0); + aI = this.slerp(startQuat, endQuat, 0); splinePoints[0] = [quats[0], aI, aI, quats[1]]; for (var i = 0, j = 1; i < n; i++, j++) { if (i === 0) { @@ -361,5 +364,67 @@ module.exports = { } else vR = this.slerp(pts[0], pts[1], t); return vR; + }, + + /** + * This function returns the coefficents based on the order and the current position + * @private + * @param {number} n - order + * @param {number} k - current position + * @return {object} - coefficients + */ + 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; + }, + + /** + * Function to get the bezier coeffients based on the time and order + * @public + * @param {number} t - time + * @param {number} n - order + * @return {object} - bezier coefficients + */ + getBezierValues: function(t, n) { + t = parseFloat(t, 10), + n = parseInt(n, 10); + + if (isNaN(t) || isNaN(n)) + return void 0; + if ((t < 0) || (n < 0)) + return void 0; + if (t > 1) + return void 0; + + var c, + values = [], + + x = (1 - t), + y = t; + // + // Binomial theorem to expand (x+y)^n + // + for (var k = 0; k <= n; k++) { + c = this.getCoeff(n, k) * Math.pow(x, (n - k)) * Math.pow(y, k); + values.push(c); + } + + return values; } }; \ No newline at end of file diff --git a/src/utils.js b/src/utils.js index c4204fc9d..2d0a44026 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,5 +1,7 @@ require('enyo'); +var transform = require('./transform'); + /** * A collection of utilities * @module enyo/utils @@ -143,6 +145,30 @@ var exists = exports.exists = function (target) { var uidCounter = 0; +var + BORDER = { + 'border-radius': 1, 'border-image-slice': 1, + 'border-top-left-radius': 1, 'border-top-right-radius': 1, 'border-bottom-left-radius': 1, 'border-bottom-right-radius': 1 + }, + COLOR = { + 'color': 1, 'background-color': 1, 'fill': 1, 'flood-color': 1,'lighting-color': 1, 'stop-color': 1, 'outline-color': 1, + 'border-color': 1, 'border-top-color': 1, 'border-left-color': 1, 'border-right-color': 1, 'border-bottom-color': 1 + }, + INT_UNIT = { + 'z-index': 1 + }, + SHADOW = { + 'box-shadow': 1, 'text-shadow': 1 + }, + OPACITY = { + 'opacity': 1, 'flood-opacity': 1, 'stop-opacity': 1, 'fill-opacity': 1, 'stroke-opacity': 1 + }, + TRANSFORM = { + translate: 1, translateX: 1, translateY: 1, translateZ: 1, rotate: 1, rotateX: 1, rotateY: 1, rotateZ: 1, + skew: 1, skewX: 1, skewY: 1, scale: 1, scaleX: 1, scaleY: 1, scaleZ: 1, perspective: 1 + }; + + /** * Creates a unique identifier (with an optional prefix) and returns the identifier as a string. * @@ -233,8 +259,7 @@ var isArray = exports.isArray = Array.isArray || function (it) { * @public */ exports.isObject = Object.isObject || function (it) { - // explicit null/undefined check for IE8 compatibility - return (it != null) && (toString.call(it) === '[object Object]'); + return toString.call(it) === '[object Object]'; }; /** @@ -369,7 +394,8 @@ exports.asyncMethod = function (scope, method) { * arguments `args` ([Array]{@glossary Array}), if the object and method exist. * * @example -* enyo.call(myWorkObject, 'doWork', [3, 'foo']); +* utils = require('enyo/utils'); +* utils.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. @@ -736,23 +762,6 @@ Object.keys = Object.keys || function (obj) { 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; }; @@ -999,60 +1008,6 @@ exports.values = function (obj) { // 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}. @@ -1074,38 +1029,6 @@ Array.prototype.find = Array.prototype.find || function (fn, ctx) { } }; -/** -* A [polyfill]{@glossary polyfill} for platforms that don't support -* [Array.forEach()]{@glossary Array.forEach}. -*/ -Array.prototype.forEach = Array.prototype.forEach || function (fn, ctx) { - for (var i=0, len=this.length >>> 0; i>> 0; i>> 0; i