diff --git a/src/core/Context.js b/src/core/Context.js
index 7b2ff4cd..f5160dce 100644
--- a/src/core/Context.js
+++ b/src/core/Context.js
@@ -146,6 +146,8 @@ define(function (require, exports, module) {
if (contextParameters.origin) this._nodeContext.origin = contextParameters.origin;
if (contextParameters.align) this._nodeContext.align = contextParameters.align;
if (contextParameters.size) this._nodeContext.size = contextParameters.size;
+ if (contextParameters.size) this._nodeContext.size = contextParameters.size;
+ this._nodeContext.hide = contextParameters.hide;
if (contextParameters.allocator) {
this._nodeContext.allocator = contextParameters.allocator;
} else {
diff --git a/src/core/DOMBuffer.js b/src/core/DOMBuffer.js
new file mode 100644
index 00000000..af106a2b
--- /dev/null
+++ b/src/core/DOMBuffer.js
@@ -0,0 +1,103 @@
+/**
+ * Created by lundfall on 02/06/2017.
+ */
+
+
+define(function (require, exports, module) {
+
+ /**
+ * Singleton class optimized for high performance in DOM updates. All DOM updates that are done through this class will
+ * be cached and can be flushed at the same order the instructions came in.
+ *
+ *
+ * @type {{}}
+ */
+ var DOMBuffer = {};
+ var enqueuedOperations = [];
+
+
+ DOMBuffer.assignProperty = function (object, property, value) {
+ enqueuedOperations.push({ data: [object, property, value], operation: 'assignProperty' });
+ };
+
+ DOMBuffer.setAttribute = function (element, attribute, value) {
+ enqueuedOperations.push({ data: [element, attribute, value], operation: 'setAttribute' });
+ };
+
+ DOMBuffer.addToObject = function (object, value) {
+ enqueuedOperations.push({ data: [object, value], operation: 'addToObject' });
+ };
+
+ DOMBuffer.setAttributeOnDescendants = function (element, attribute, attributeValue) {
+ enqueuedOperations.push({ data: [element, attribute, attributeValue], operation: 'setAttributeOnDescendants' });
+ };
+
+ DOMBuffer.removeFromObject = function (object, attribute) {
+ enqueuedOperations.push({ data: [object, attribute], operation: 'removeFromObject' });
+ };
+
+ DOMBuffer.removeAttribute = function (element, attribute) {
+ enqueuedOperations.push({ data: [element, attribute], operation: 'removeAttribute' });
+ };
+
+ DOMBuffer.removeChild = function (parent, childToRemove) {
+ enqueuedOperations.push({ data: [parent, childToRemove], operation: 'removeChild' });
+ };
+
+ DOMBuffer.appendChild = function (parent, childToAppend) {
+ enqueuedOperations.push({ data: [parent, childToAppend], operation: 'appendChild' });
+ };
+
+ DOMBuffer.insertBefore = function (parent, childBefore, childToInsert) {
+ enqueuedOperations.push({ data: [parent, childBefore, childToInsert], operation: 'insertBefore' });
+ };
+
+ DOMBuffer.flushUpdates = function () {
+ for (var index = 0; index < enqueuedOperations.length; index++) {
+ var enqueuedOperation = enqueuedOperations[index];
+ var operationName = enqueuedOperation.operation;
+ var data = enqueuedOperation.data;
+ switch (operationName) {
+ case 'appendChild':
+ data[0].appendChild(data[1]);
+ break;
+ case 'insertBefore':
+ data[0].insertBefore(data[1], data[2]);
+ break;
+ case 'setAttribute':
+ data[0].setAttribute(data[1], data[2]);
+ break;
+ case 'removeChild':
+ if (data[0].childNodes.length && data[0].contains(data[1])) {
+ data[0].removeChild(data[1]);
+ }
+ break;
+ case 'removeAttribute':
+ data[0].removeAttribute(data[1]);
+ break;
+ case 'addToObject':
+ data[0].add(data[1]);
+ break;
+ case 'removeFromObject':
+ data[0].remove(data[1]);
+ break;
+ case 'assignProperty':
+ data[0][data[1]] = data[2];
+ break;
+ case 'setAttributeOnDescendants':
+ /* Gets all the descendants for element
+ * https://stackoverflow.com/questions/26325278/how-can-i-get-all-descendant-elements-for-parent-container
+ * */
+ var descendants = data[0].querySelectorAll("*");
+ for (var i = 0; i < descendants.length; i++) {
+ descendants[i].setAttribute(data[1], data[2]);
+ }
+ break;
+ }
+ }
+ enqueuedOperations = [];
+ };
+
+ module.exports = DOMBuffer;
+});
+
diff --git a/src/core/DOMEventHandler.js b/src/core/DOMEventHandler.js
new file mode 100644
index 00000000..7e46e680
--- /dev/null
+++ b/src/core/DOMEventHandler.js
@@ -0,0 +1,69 @@
+
+/**
+ * Created by lundfall on 01/06/2017.
+ */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * Owner: mark@famo.us
+ * @license MPL 2.0
+ * @copyright Famous Industries, Inc. 2015
+ */
+
+define(function (require, exports, module) {
+
+ var DOMEventHandler = {};
+ var EventEmitter = require('./EventEmitter.js');
+ var DOMBuffer = require('./DOMBuffer');
+
+ //TODO Add more to complete list
+ var singleElementEvents = [
+ 'submit', 'focus', 'blur', 'load', 'unload', 'change', 'reset', 'scroll'
+ ];
+
+ var initializedListeners = {};
+
+ DOMEventHandler.isNativeEvent = function(eventName) {
+ return typeof document.body["on" + eventName] !== "undefined"
+ ||
+ /* Needed because otherwise not able to use mobile emulation in browser! */
+ ['touchmove', 'touchstart', 'touchend'].includes(eventName);
+ };
+
+ DOMEventHandler.addEventListener = function(id, element, type, callback){
+ if(!DOMEventHandler.isNativeEvent(type)){
+ return;
+ }
+
+ if(singleElementEvents.includes(type)){
+ return element.addEventListener(type, callback);
+ }
+ DOMBuffer.setAttribute(element, 'data-arvaid', id);
+ var eventEmitter = initializedListeners[type];
+ if(!eventEmitter){
+ eventEmitter = initializedListeners[type] = new EventEmitter();
+ window.addEventListener(type, function (event) {
+ var target = event.relatedTarget || event.target;
+ var recievedID = target && target.getAttribute && target.getAttribute('data-arvaid');
+ if(recievedID){
+ eventEmitter.emit(recievedID, event);
+ }
+ });
+ }
+ eventEmitter.on(id, callback);
+
+ };
+
+ DOMEventHandler.removeEventListener = function(element, id, type, callback) {
+ if(singleElementEvents.includes(type)){
+ return element.addEventListener(type, callback);
+ }
+ if(initializedListeners[type]){
+ initializedListeners[type].removeListener(id, callback);
+ }
+ };
+
+ module.exports = DOMEventHandler;
+});
diff --git a/src/core/ElementAllocator.js b/src/core/ElementAllocator.js
index 9a6b6091..8dd373ba 100644
--- a/src/core/ElementAllocator.js
+++ b/src/core/ElementAllocator.js
@@ -9,6 +9,7 @@
define(function (require, exports, module) {
var Context = require('./Context.js');
+ var DOMBuffer = require('./DOMBuffer');
/**
* Internal helper object to Context that handles the process of
@@ -42,11 +43,13 @@ define(function (require, exports, module) {
if (container === oldContainer) return;
if (oldContainer instanceof DocumentFragment) {
- container.appendChild(oldContainer);
+ DOMBuffer.appendChild(container, oldContainer);
}
else {
- while (oldContainer.hasChildNodes()) {
- container.appendChild(oldContainer.firstChild);
+ var children = oldContainer.childNodes || [];
+ //TODO Confirm that this works
+ for(var i = 0;i< children.length; i++){
+ DOMBuffer.appendChild(container, children[i]);
}
}
@@ -59,6 +62,7 @@ define(function (require, exports, module) {
* @private
* @method allocate
*
+ * @param {Object} options
* @param {String} options.type type of element, e.g. 'div'
* @param {Boolean} options.insertFirst Whether it should be allocated from the top instead of the bottom
* or at the end. Defaults to false (at the bottom).
@@ -106,9 +110,9 @@ define(function (require, exports, module) {
ElementAllocator.prototype._allocateNewHtmlOutput = function _allocateNewElementOutput(type, insertFirst) {
var result = document.createElement(type);
if (insertFirst) {
- this.container.insertBefore(result, this.container.firstChild);
+ DOMBuffer.insertBefore(this.container, result, this.container.firstChild);
} else {
- this.container.appendChild(result);
+ DOMBuffer.appendChild(this.container, result);
}
return result;
};
diff --git a/src/core/ElementOutput.js b/src/core/ElementOutput.js
index cdea3e0b..2a837305 100644
--- a/src/core/ElementOutput.js
+++ b/src/core/ElementOutput.js
@@ -11,6 +11,8 @@ define(function(require, exports, module) {
var Entity = require('./Entity');
var EventHandler = require('./EventHandler');
var Transform = require('./Transform');
+ var DOMEventHandler = require('./DOMEventHandler');
+ var DOMBuffer = require('./DOMBuffer');
var usePrefix = !('transform' in document.documentElement.style);
var devicePixelRatio = window.devicePixelRatio || 1;
@@ -60,12 +62,12 @@ define(function(require, exports, module) {
* @return {EventHandler} this
*/
ElementOutput.prototype.on = function on(type, fn) {
- if (this._element) this._element.addEventListener(type, this.eventForwarder);
+ if (this._element) DOMEventHandler.addEventListener(this.id, this._element, type, this.eventForwarder);
this._eventOutput.on(type, fn);
};
ElementOutput.prototype.once = function on(type, fn) {
- if (this._element) this._element.addEventListener(type, this.eventForwarder);
+ if (this._element) DOMEventHandler.addEventListener(this.id, this._element, type, this.eventForwarder);
this._eventOutput.once(type, fn);
};
@@ -142,7 +144,7 @@ define(function(require, exports, module) {
// Calling this enables methods like #on and #pipe.
function _addEventListeners(target) {
for (var i in this._eventOutput.listeners) {
- target.addEventListener(i, this.eventForwarder);
+ DOMEventHandler.addEventListener(this.id, target, i, this.eventForwarder);
}
}
@@ -150,7 +152,7 @@ define(function(require, exports, module) {
// document element. This occurs just before detach from the document.
function _removeEventListeners(target) {
for (var i in this._eventOutput.listeners) {
- target.removeEventListener(i, this.eventForwarder);
+ DOMEventHandler.removeEventListener(target, this.id, i, this.eventForwarder)
}
}
@@ -175,7 +177,6 @@ define(function(require, exports, module) {
result += m[15] + ')';
return result;
}
-
/**
* Directly apply given FamousMatrix to the document element as the
* appropriate webkit CSS style.
@@ -191,12 +192,12 @@ define(function(require, exports, module) {
var _setMatrix;
if (usePrefix) {
_setMatrix = function(element, matrix) {
- element.style.webkitTransform = _formatCSSTransform(matrix);
+ DOMBuffer.assignProperty(element.style, 'webkitTransform', _formatCSSTransform(matrix));
};
}
else {
_setMatrix = function(element, matrix) {
- element.style.transform = _formatCSSTransform(matrix);
+ DOMBuffer.assignProperty(element.style, 'transform', _formatCSSTransform(matrix));
};
}
@@ -208,18 +209,18 @@ define(function(require, exports, module) {
// Directly apply given origin coordinates to the document element as the
// appropriate webkit CSS style.
var _setOrigin = usePrefix ? function(element, origin) {
- element.style.webkitTransformOrigin = _formatCSSOrigin(origin);
+ DOMBuffer.assignProperty(element.style, 'webkitTransform', _formatCSSOrigin(origin));
} : function(element, origin) {
- element.style.transformOrigin = _formatCSSOrigin(origin);
+ DOMBuffer.assignProperty(element.style, 'transformOrigin', _formatCSSOrigin(origin));
};
// Shrink given document element until it is effectively invisible.
var _setInvisible = usePrefix ? function(element) {
- element.style.webkitTransform = 'scale3d(0.0001,0.0001,0.0001)';
- element.style.opacity = 0;
+ DOMBuffer.assignProperty(element.style, 'webkitTransform', 'scale3d(0.0001,0.0001,0.0001)');
+ DOMBuffer.assignProperty(element.style, 'opacity', '0');
} : function(element) {
- element.style.transform = 'scale3d(0.0001,0.0001,0.0001)';
- element.style.opacity = 0;
+ DOMBuffer.assignProperty(element.style, 'transform', 'scale3d(0.0001,0.0001,0.0001)');
+ DOMBuffer.assignProperty(element.style, 'opacity', '0');
};
function _xyNotEquals(a, b) {
@@ -256,12 +257,12 @@ define(function(require, exports, module) {
if (this._invisible) {
this._invisible = false;
- this._element.style.display = '';
+ DOMBuffer.assignProperty(this._element.style, 'display', '');
}
if (this._opacity !== opacity) {
this._opacity = opacity;
- target.style.opacity = (opacity >= 1) ? '0.999999' : opacity;
+ DOMBuffer.assignProperty(target.style, 'opacity', (opacity >= 1) ? '0.999999' : opacity);
}
if (this._transformDirty || this._originDirty || this._sizeDirty) {
@@ -282,9 +283,9 @@ define(function(require, exports, module) {
this._matrix = matrix;
var aaMatrix = this._size ? Transform.thenMove(matrix, [-this._size[0]*origin[0], -this._size[1]*origin[1], 0]) : matrix;
_setMatrix(target, aaMatrix);
- /* Since a lot of browsers are buggy, they need the z-index to be set as well in order to successfully place things
- * on top of each other*/
- target.style.zIndex = aaMatrix[14];
+ /* Since a lot of browsers are buggy, they need the z-index to be set as well besides the 3d transformation
+ * matrix to successfully place things on top of each other*/
+ DOMBuffer.assignProperty(target.style, 'zIndex', Math.round(aaMatrix[14]));
this._transformDirty = false;
}
};
@@ -292,7 +293,7 @@ define(function(require, exports, module) {
ElementOutput.prototype.cleanup = function cleanup() {
if (this._element) {
this._invisible = true;
- this._element.style.display = 'none';
+ DOMBuffer.assignProperty(this._element.style, 'display', 'none');
}
};
@@ -305,7 +306,7 @@ define(function(require, exports, module) {
*/
ElementOutput.prototype.attach = function attach(target) {
this._element = target;
- _addEventListeners.call(this, target);
+ _addEventListeners.call(this, target);
};
/**
@@ -321,7 +322,7 @@ define(function(require, exports, module) {
_removeEventListeners.call(this, target);
if (this._invisible) {
this._invisible = false;
- this._element.style.display = '';
+ DOMBuffer.assignProperty(this._element.style, 'display', '');
}
}
this._element = null;
diff --git a/src/core/Engine.js b/src/core/Engine.js
index 4d3940e7..16d2f5b8 100644
--- a/src/core/Engine.js
+++ b/src/core/Engine.js
@@ -28,6 +28,17 @@ define(function (require, exports, module) {
var ElementAllocator = require('./ElementAllocator');
var EventHandler = require('./EventHandler');
var OptionsManager = require('./OptionsManager');
+ var DOMBuffer = require('./DOMBuffer');
+
+ /* Precise function for comparing time stamps*/
+ var getTime = (typeof window !== 'undefined' && window.performance && window.performance.now) ?
+ function() {
+ return window.performance.now();
+ }
+ : function() {
+ return Date.now();
+ };
+
var Engine = {};
@@ -40,7 +51,9 @@ define(function (require, exports, module) {
var deferQueue = [];
- var lastTime = Date.now();
+ /* The last timestamp of the previous frame */
+ var lastTime = getTime();
+
var frameTime;
var frameTimeLimit;
var loopEnabled = true;
@@ -59,6 +72,13 @@ define(function (require, exports, module) {
/** @const */
var MAX_DEFER_FRAME_TIME = 10;
+
+ Engine.PriorityLevels = {
+ critical: Infinity,
+ normal: 130,
+ generous: 0
+ };
+
/**
* Inside requestAnimationFrame loop, step() is called, which:
* calculates current FPS (throttling loop if it is over limit set in setFPSCap),
@@ -75,13 +95,21 @@ define(function (require, exports, module) {
currentFrame++;
nextTickFrame = currentFrame;
- var currentTime = Date.now();
+ var currentTime = getTime();
this._lastFrameTimeDelta = currentTime - lastTime;
// skip frame if we're over our framerate cap
if (frameTimeLimit && this._lastFrameTimeDelta < frameTimeLimit) return;
- var i = 0;
+ this._priorityLevel = Infinity;
+ var priorityLevels = Object.keys(Engine.PriorityLevels);
+ for (var i = 0; i < priorityLevels.length; i++) {
+ var priority = priorityLevels[i];
+ var priorityLevelCriteria = Engine.PriorityLevels[priority];
+ if (this._lastFrameTimeDelta < priorityLevelCriteria && priorityLevelCriteria <= this._priorityLevel){
+ this._priorityLevel = priorityLevelCriteria;
+ }
+ }
frameTime = currentTime - lastTime;
lastTime = currentTime;
@@ -93,15 +121,47 @@ define(function (require, exports, module) {
while (numFunctions--) (nextTickQueue.shift())(currentFrame);
// limit total execution time for deferrable functions
- while (deferQueue.length && (Date.now() - currentTime) < MAX_DEFER_FRAME_TIME) {
+ while (deferQueue.length && (getTime() - currentTime) < MAX_DEFER_FRAME_TIME) {
deferQueue.shift().call(this);
}
- for (i = 0; i < contexts.length; i++) contexts[i].update();
+ for (var i = 0; i < contexts.length; i++) contexts[i].update();
+
+ DOMBuffer.flushUpdates();
eventHandler.emit('postrender');
+
+
};
+ /**
+ * @example
+ *
+ * Engine.restrictAnimations({
+ * size: Engine.PriorityLevel.critical,
+ * opacity: Engine.PriorityLevel.critical
+ * })
+ *
+ * Instructs the engine to disable the animations for the different properties passed.
+ *
+ * @param options
+ */
+ Engine.restrictAnimations = function disableAnimationsWhen(options) {
+ this._disableAnimationSpec = options;
+ };
+
+ Engine.shouldPropertyAnimate = function shouldPropertyAnimate(propertyName){
+ if(!this._disableAnimationSpec){
+ return true;
+ }
+ var priorityLevel = this._disableAnimationSpec[propertyName];
+ if(priorityLevel === undefined){
+ return true;
+ }
+ return this._priorityLevel < priorityLevel;
+ };
+
+
Engine.getFrameTimeDelta = function getFrameTimeDelta() {
return this._lastFrameTimeDelta;
};
@@ -141,13 +201,23 @@ define(function (require, exports, module) {
handleResize();
}
- Engine.touchMoveEnabled = false;
+ Engine.touchMoveEnabled = true;
- Engine.enableTouchMove = function enableTouchMove() {
- if (!this.touchMoveEnabled) {
- console.log("Warning: Touch move enabled. Outcomes might be unwated");
+ Engine.getPriorityLevel = function () {
+ return this._priorityLevel;
+ };
+ Engine.disableTouchMove = function disableTouchMove() {
+ if (this.touchMoveEnabled) {
+ // prevent scrolling via browser
+ window.addEventListener('touchmove', function (event) {
+ if (event.target.tagName === 'TEXTAREA' || this.touchMoveEnabled) {
+ return true;
+ } else {
+ event.preventDefault();
+ }
+ }.bind(this), { capture: true, passive: false });
+ this.touchMoveEnabled = false;
}
- this.touchMoveEnabled = true;
};
@@ -159,6 +229,7 @@ define(function (require, exports, module) {
* @method initialize
*/
function initialize() {
+
// prevent scrolling via browser
window.addEventListener('touchmove', function (event) {
if (event.target.tagName === 'TEXTAREA' || this.touchMoveEnabled) {
@@ -183,6 +254,15 @@ define(function (require, exports, module) {
document.documentElement.classList.add('famous-root');
}
+ var canvas;
+ Engine.getCachedCanvas = function() {
+ if(!canvas){
+ canvas = document.createElement('canvas');
+ document.createDocumentFragment().appendChild(canvas);
+ }
+ return canvas;
+ };
+
/**
* Add event handler object to set of downstream handlers.
*
@@ -333,6 +413,9 @@ define(function (require, exports, module) {
* @return {Context} new Context within el
*/
Engine.createContext = function createContext(el) {
+
+ this._priorityLevel = Engine.PriorityLevels.critical;
+
if (!initialized && options.appMode) Engine.nextTick(initialize.bind(this));
var needMountContainer = false;
@@ -413,6 +496,8 @@ define(function (require, exports, module) {
nextTickQueue.push(fn);
};
+ Engine.now = getTime;
+
/**
* Queue a function to be executed sometime soon, at a time that is
* unlikely to affect frame rate.
diff --git a/src/core/EventEmitter.js b/src/core/EventEmitter.js
index 828fdd43..ed665961 100644
--- a/src/core/EventEmitter.js
+++ b/src/core/EventEmitter.js
@@ -41,7 +41,6 @@ define(function(require, exports, module) {
}
var handlers = this.listeners[type];
if (handlers) {
- handlers = Array.from(handlers);
for (var i = 0; i < handlers.length; i++) {
handlers[i].apply(this._owner, args);
}
diff --git a/src/core/Group.js b/src/core/Group.js
index 2b37b2d5..4a765780 100644
--- a/src/core/Group.js
+++ b/src/core/Group.js
@@ -147,6 +147,7 @@ define(function (require, exports, module) {
allocator: this._allocator,
transform: Transform.translate(-origin[0] * size[0], -origin[1] * size[1], 0),
origin: origin,
+ hide: context.opacity === 0 || context.hide,
size: size
});
return result;
diff --git a/src/core/SpecParser.js b/src/core/SpecParser.js
index 2aa281ff..7ecd61b5 100644
--- a/src/core/SpecParser.js
+++ b/src/core/SpecParser.js
@@ -99,13 +99,15 @@ define(function(require, exports, module) {
if (typeof spec === 'number') {
id = spec;
- transform = parentContext.transform;
+ var hide = parentContext.hide || parentContext.opacity === 0;
+ transform = hide ? Transform.scale(0, 0, 0) : parentContext.transform;
align = parentContext.align || _zeroZero;
if (parentContext.size && align && (align[0] || align[1])) {
var alignAdjust = [align[0] * parentContext.size[0], align[1] * parentContext.size[1], 0];
transform = Transform.thenMove(transform, _vecInContext(alignAdjust, sizeContext));
}
this.result[id] = {
+ hide: hide,
transform: transform,
opacity: parentContext.opacity,
origin: parentContext.origin || _zeroZero,
@@ -128,10 +130,14 @@ define(function(require, exports, module) {
origin = parentContext.origin;
align = parentContext.align;
size = parentContext.size;
+ /* If parent is hidden, also this element should be hidden */
+ var hide = spec.hide || parentContext.hide || opacity === 0;
var nextSizeContext = sizeContext;
if (spec.opacity !== undefined) opacity = parentContext.opacity * spec.opacity;
- if (spec.transform) transform = Transform.multiply(parentContext.transform, spec.transform);
+ if (hide) transform = Transform.scale(0, 0, 0);
+ else if (spec.transform) transform = Transform.multiply(parentContext.transform, spec.transform);
+
if (spec.origin) {
origin = spec.origin;
nextSizeContext = parentContext.transform;
@@ -167,7 +173,8 @@ define(function(require, exports, module) {
opacity: opacity,
origin: origin,
align: align,
- size: size
+ size: size,
+ hide: hide
}, nextSizeContext);
}
};
diff --git a/src/core/Surface.js b/src/core/Surface.js
index 235ae846..3ed7dbf6 100644
--- a/src/core/Surface.js
+++ b/src/core/Surface.js
@@ -7,539 +7,551 @@
* @copyright Famous Industries, Inc. 2015
*/
-define(function(require, exports, module) {
- var ElementOutput = require('./ElementOutput');
-
- /**
- * A base class for viewable content and event
- * targets inside a Famo.us application, containing a renderable document
- * fragment. Like an HTML div, it can accept internal markup,
- * properties, classes, and handle events.
- *
- * @class Surface
- * @constructor
- *
- * @param {Object} [options] default option overrides
- * @param {Array.Number} [options.size] [width, height] in pixels
- * @param {Array.string} [options.classes] CSS classes to set on target div
- * @param {Array} [options.properties] string dictionary of CSS properties to set on target div
- * @param {Array} [options.attributes] string dictionary of HTML attributes to set on target div
- * @param {string} [options.content] inner (HTML) content of surface
- */
- function Surface(options) {
- ElementOutput.call(this);
-
- this.options = {};
-
- this.properties = {};
- this.attributes = {};
- this.content = '';
- this.classList = [];
- this.size = null;
-
- this._classesDirty = true;
- this._stylesDirty = true;
- this._attributesDirty = true;
- this._sizeDirty = true;
- this._contentDirty = true;
- this._trueSizeCheck = true;
-
- this._dirtyClasses = [];
- this._dirtyAttributes = [];
-
- if (options) this.setOptions(options);
-
- this._currentTarget = null;
+define(function (require, exports, module) {
+ var ElementOutput = require('./ElementOutput');
+ var DOMBuffer = require('./DOMBuffer');
+
+ /**
+ * A base class for viewable content and event
+ * targets inside a Famo.us application, containing a renderable document
+ * fragment. Like an HTML div, it can accept internal markup,
+ * properties, classes, and handle events.
+ *
+ * @class Surface
+ * @constructor
+ *
+ * @param {Object} [options] default option overrides
+ * @param {Array.Number} [options.size] [width, height] in pixels
+ * @param {Array.string} [options.classes] CSS classes to set on target div
+ * @param {Array} [options.properties] string dictionary of CSS properties to set on target div
+ * @param {Array} [options.attributes] string dictionary of HTML attributes to set on target div
+ * @param {string} [options.content] inner (HTML) content of surface
+ */
+ function Surface(options) {
+ ElementOutput.call(this);
+
+ this.options = {};
+
+ this.properties = {};
+ this.attributes = {};
+ this.content = '';
+ this.classList = [];
+ this.size = null;
+
+ this._classesDirty = true;
+ this._stylesDirty = true;
+ this._attributesDirty = true;
+ this._sizeDirty = true;
+ this._contentDirty = true;
+ this._trueSizeCheck = true;
+
+ this._dirtyClasses = [];
+ this._dirtyAttributes = [];
+
+ if (options) this.setOptions(options);
+
+ this._currentTarget = null;
+ }
+
+ Surface.prototype = Object.create(ElementOutput.prototype);
+ Surface.prototype.constructor = Surface;
+ Surface.prototype.elementType = 'div';
+ Surface.prototype.elementClass = 'famous-surface';
+
+ /**
+ * Set HTML attributes on this Surface. Note that this will cause
+ * dirtying and thus re-rendering, even if values do not change.
+ *
+ * @method setAttributes
+ * @param {Object} attributes property dictionary of "key" => "value"
+ */
+ Surface.prototype.setAttributes = function setAttributes(attributes) {
+ for (var n in attributes) {
+ if (n === 'style') throw new Error('Cannot set styles via "setAttributes" as it will break Famo.us. Use "setProperties" instead.');
+ this.attributes[n] = attributes[n];
+ /* Remove the attribute that is about to be removed, if applicable */
+ var attributeToBeRemovedIndex = this._dirtyAttributes.indexOf(n);
+ if (attributeToBeRemovedIndex !== -1) {
+ this._dirtyAttributes.splice(attributeToBeRemovedIndex, 1);
+ }
}
- Surface.prototype = Object.create(ElementOutput.prototype);
- Surface.prototype.constructor = Surface;
- Surface.prototype.elementType = 'div';
- Surface.prototype.elementClass = 'famous-surface';
-
- /**
- * Set HTML attributes on this Surface. Note that this will cause
- * dirtying and thus re-rendering, even if values do not change.
- *
- * @method setAttributes
- * @param {Object} attributes property dictionary of "key" => "value"
- */
- Surface.prototype.setAttributes = function setAttributes(attributes) {
- for (var n in attributes) {
- if (n === 'style') throw new Error('Cannot set styles via "setAttributes" as it will break Famo.us. Use "setProperties" instead.');
- this.attributes[n] = attributes[n];
- /* Remove the attribute that is about to be removed, if applicable */
- var attributeToBeRemovedIndex = this._dirtyAttributes.indexOf(n);
- if(attributeToBeRemovedIndex !== -1){
- this._dirtyAttributes.splice(attributeToBeRemovedIndex, 1);
- }
- }
- this._attributesDirty = true;
- };
-
- /**
- * Get HTML attributes on this Surface.
- *
- * @method getAttributes
- *
- * @return {Object} Dictionary of this Surface's attributes.
- */
- Surface.prototype.getAttributes = function getAttributes() {
- return this.attributes;
- };
-
- /**
- * Removes existing attributes from this Surface (e.g. needed for 'disabled').
- * @method removeAttributes
- * @param {Array} attributes List of attribute names to remove
- */
- Surface.prototype.removeAttributes = function removeAttributes(attributes) {
- for(var index in attributes) {
- var name = attributes[index];
- delete this.attributes[name];
- this._dirtyAttributes.push(name);
- }
- this._attributesDirty = true;
- };
-
- /**
- * Set CSS-style properties on this Surface. Note that this will cause
- * dirtying and thus re-rendering, even if values do not change.
- *
- * @method setProperties
- * @chainable
- * @param {Object} properties property dictionary of "key" => "value"
- */
- Surface.prototype.setProperties = function setProperties(properties) {
- for (var n in properties) {
- this.properties[n] = properties[n];
- }
- this._stylesDirty = true;
- return this;
- };
-
- /**
- * Get CSS-style properties on this Surface.
- *
- * @method getProperties
- *
- * @return {Object} Dictionary of this Surface's properties.
- */
- Surface.prototype.getProperties = function getProperties() {
- return this.properties;
- };
-
- /**
- * Add CSS-style class to the list of classes on this Surface. Note
- * this will map directly to the HTML property of the actual
- * corresponding rendered
.
- *
- * @method addClass
- * @chainable
- * @param {string} className name of class to add
- */
- Surface.prototype.addClass = function addClass(className) {
- if (this.classList.indexOf(className) < 0) {
- this.classList.push(className);
- this._classesDirty = true;
- }
- return this;
- };
-
- /**
- * Remove CSS-style class from the list of classes on this Surface.
- * Note this will map directly to the HTML property of the actual
- * corresponding rendered
.
- *
- * @method removeClass
- * @chainable
- * @param {string} className name of class to remove
- */
- Surface.prototype.removeClass = function removeClass(className) {
- var i = this.classList.indexOf(className);
- if (i >= 0) {
- this._dirtyClasses.push(this.classList.splice(i, 1)[0]);
- this._classesDirty = true;
- }
- return this;
- };
-
- /**
- * Toggle CSS-style class from the list of classes on this Surface.
- * Note this will map directly to the HTML property of the actual
- * corresponding rendered
.
- *
- * @method toggleClass
- * @param {string} className name of class to toggle
- */
- Surface.prototype.toggleClass = function toggleClass(className) {
- var i = this.classList.indexOf(className);
- if (i >= 0) {
- this.removeClass(className);
- } else {
- this.addClass(className);
- }
- return this;
- };
-
- /**
- * Reset class list to provided dictionary.
- * @method setClasses
- * @chainable
- * @param {Array.string} classList
- */
- Surface.prototype.setClasses = function setClasses(classList) {
- var i = 0;
- var removal = [];
- for (i = 0; i < this.classList.length; i++) {
- if (classList.indexOf(this.classList[i]) < 0) removal.push(this.classList[i]);
- }
- for (i = 0; i < removal.length; i++) this.removeClass(removal[i]);
- // duplicates are already checked by addClass()
- for (i = 0; i < classList.length; i++) this.addClass(classList[i]);
- return this;
- };
-
- /**
- * Get array of CSS-style classes attached to this div.
- *
- * @method getClasslist
- * @return {Array.string} array of class names
- */
- Surface.prototype.getClassList = function getClassList() {
- return this.classList;
- };
-
- /**
- * Set or overwrite inner (HTML) content of this surface. Note that this
- * causes a re-rendering if the content has changed.
- *
- * @method setContent
- * @chainable
- * @param {string|Document Fragment} content HTML content
- */
- Surface.prototype.setContent = function setContent(content) {
- if (this.content !== content) {
- this.content = content;
- this._contentDirty = true;
- }
- return this;
- };
-
- /**
- * Return inner (HTML) content of this surface.
- *
- * @method getContent
- *
- * @return {string} inner (HTML) content
- */
- Surface.prototype.getContent = function getContent() {
- return this.content;
- };
-
- /**
- * Set options for this surface
- *
- * @method setOptions
- * @chainable
- * @param {Object} [options] overrides for default options. See constructor.
- */
- Surface.prototype.setOptions = function setOptions(options) {
- if (options.size) this.setSize(options.size);
- if (options.classes) this.setClasses(options.classes);
- if (options.properties) this.setProperties(options.properties);
- if (options.attributes) this.setAttributes(options.attributes);
- if (options.content) this.setContent(options.content);
- return this;
- };
-
- // Apply to document all changes from removeClass() since last setup().
- function _cleanupClasses(target) {
- for (var i = 0; i < this._dirtyClasses.length; i++) target.classList.remove(this._dirtyClasses[i]);
- this._dirtyClasses = [];
+ this._attributesDirty = true;
+ };
+
+ /**
+ * Get HTML attributes on this Surface.
+ *
+ * @method getAttributes
+ *
+ * @return {Object} Dictionary of this Surface's attributes.
+ */
+ Surface.prototype.getAttributes = function getAttributes() {
+ return this.attributes;
+ };
+
+ /**
+ * Removes existing attributes from this Surface (e.g. needed for 'disabled').
+ * @method removeAttributes
+ * @param {Array} attributes List of attribute names to remove
+ */
+ Surface.prototype.removeAttributes = function removeAttributes(attributes) {
+ for (var index in attributes) {
+ var name = attributes[index];
+ delete this.attributes[name];
+ this._dirtyAttributes.push(name);
}
+ this._attributesDirty = true;
+ };
- // Apply values of all Famous-managed styles to the document element.
- // These will be deployed to the document on call to #setup().
- function _applyStyles(target) {
- for (var n in this.properties) {
- target.style[n] = this.properties[n];
- }
+ /**
+ * Set CSS-style properties on this Surface. Note that this will cause
+ * dirtying and thus re-rendering, even if values do not change.
+ *
+ * @method setProperties
+ * @chainable
+ * @param {Object} properties property dictionary of "key" => "value"
+ */
+ Surface.prototype.setProperties = function setProperties(properties) {
+ for (var n in properties) {
+ this.properties[n] = properties[n];
}
+ this._stylesDirty = true;
+ return this;
+ };
- // Clear all Famous-managed styles from the document element.
- // These will be deployed to the document on call to #setup().
- function _cleanupStyles(target) {
- for (var n in this.properties) {
- target.style[n] = '';
- }
+ /**
+ * Get CSS-style properties on this Surface.
+ *
+ * @method getProperties
+ *
+ * @return {Object} Dictionary of this Surface's properties.
+ */
+ Surface.prototype.getProperties = function getProperties() {
+ return this.properties;
+ };
+
+ /**
+ * Add CSS-style class to the list of classes on this Surface. Note
+ * this will map directly to the HTML property of the actual
+ * corresponding rendered
.
+ *
+ * @method addClass
+ * @chainable
+ * @param {string} className name of class to add
+ */
+ Surface.prototype.addClass = function addClass(className) {
+ if (this.classList.indexOf(className) < 0) {
+ this.classList.push(className);
+ this._classesDirty = true;
}
+ return this;
+ };
- // Apply values of all Famous-managed attributes to the document element.
- // These will be deployed to the document on call to #setup().
- function _applyAttributes(target) {
- for (var n in this.attributes) {
- target.setAttribute(n, this.attributes[n]);
- }
- for (var index in this._dirtyAttributes) {
- var name = this._dirtyAttributes[index];
- target.removeAttribute(name);
- this._dirtyAttributes.shift();
- }
+ /**
+ * Remove CSS-style class from the list of classes on this Surface.
+ * Note this will map directly to the HTML property of the actual
+ * corresponding rendered
.
+ *
+ * @method removeClass
+ * @chainable
+ * @param {string} className name of class to remove
+ */
+ Surface.prototype.removeClass = function removeClass(className) {
+ var i = this.classList.indexOf(className);
+ if (i >= 0) {
+ this._dirtyClasses.push(this.classList.splice(i, 1)[0]);
+ this._classesDirty = true;
}
+ return this;
+ };
- // Clear all Famous-managed attributes from the document element.
- // These will be deployed to the document on call to #setup().
- function _cleanupAttributes(target) {
- for (var n in this.attributes) {
- target.removeAttribute(n);
- }
+ /**
+ * Toggle CSS-style class from the list of classes on this Surface.
+ * Note this will map directly to the HTML property of the actual
+ * corresponding rendered
.
+ *
+ * @method toggleClass
+ * @param {string} className name of class to toggle
+ */
+ Surface.prototype.toggleClass = function toggleClass(className) {
+ var i = this.classList.indexOf(className);
+ if (i >= 0) {
+ this.removeClass(className);
+ } else {
+ this.addClass(className);
}
+ return this;
+ };
- function _xyNotEquals(a, b) {
- return (a && b) ? (a[0] !== b[0] || a[1] !== b[1]) : a !== b;
+ /**
+ * Reset class list to provided dictionary.
+ * @method setClasses
+ * @chainable
+ * @param {Array.string} classList
+ */
+ Surface.prototype.setClasses = function setClasses(classList) {
+ var i = 0;
+ var removal = [];
+ for (i = 0; i < this.classList.length; i++) {
+ if (classList.indexOf(this.classList[i]) < 0) removal.push(this.classList[i]);
}
+ for (i = 0; i < removal.length; i++) this.removeClass(removal[i]);
+ // duplicates are already checked by addClass()
+ for (i = 0; i < classList.length; i++) this.addClass(classList[i]);
+ return this;
+ };
- /**
- * One-time setup for an element to be ready for commits to document.
- *
- * @private
- * @method setup
- *
- * @param {ElementAllocator} allocator document element pool for this context
- */
- Surface.prototype.setup = function setup(allocator) {
- var target = this.allocate(allocator);
- if (this.elementClass) {
- if (this.elementClass instanceof Array) {
- for (var i = 0; i < this.elementClass.length; i++) {
- target.classList.add(this.elementClass[i]);
- }
- }
- else {
- target.classList.add(this.elementClass);
- }
+ /**
+ * Get array of CSS-style classes attached to this div.
+ *
+ * @method getClasslist
+ * @return {Array.string} array of class names
+ */
+ Surface.prototype.getClassList = function getClassList() {
+ return this.classList;
+ };
+
+ /**
+ * Set or overwrite inner (HTML) content of this surface. Note that this
+ * causes a re-rendering if the content has changed.
+ *
+ * @method setContent
+ * @chainable
+ * @param {string|Document Fragment} content HTML content
+ */
+ Surface.prototype.setContent = function setContent(content) {
+ if (this.content !== content) {
+ this.content = content;
+ this._contentDirty = true;
+ }
+ return this;
+ };
+
+ /**
+ * Return inner (HTML) content of this surface.
+ *
+ * @method getContent
+ *
+ * @return {string} inner (HTML) content
+ */
+ Surface.prototype.getContent = function getContent() {
+ return this.content;
+ };
+
+ /**
+ * Set options for this surface
+ *
+ * @method setOptions
+ * @chainable
+ * @param {Object} [options] overrides for default options. See constructor.
+ */
+ Surface.prototype.setOptions = function setOptions(options) {
+ if (options.size) this.setSize(options.size);
+ if (options.classes) this.setClasses(options.classes);
+ if (options.properties) this.setProperties(options.properties);
+ if (options.attributes) this.setAttributes(options.attributes);
+ if (options.content) this.setContent(options.content);
+ return this;
+ };
+
+ // Apply to document all changes from removeClass() since last setup().
+ function _cleanupClasses(target) {
+ for (var i = 0; i < this._dirtyClasses.length; i++) DOMBuffer.removeFromObject(target.classList, this._dirtyClasses[i]);
+ this._dirtyClasses = [];
+ }
+
+ // Apply values of all Famous-managed styles to the document element.
+ // These will be deployed to the document on call to #setup().
+ function _applyStyles(target) {
+ for (var n in this.properties) {
+ DOMBuffer.assignProperty(target.style, n, this.properties[n]);
+ }
+ }
+
+ // Clear all Famous-managed styles from the document element.
+ // These will be deployed to the document on call to #setup().
+ function _cleanupStyles(target) {
+ for (var n in this.properties) {
+ DOMBuffer.assignProperty(target.style, n, '');
+ }
+ }
+
+ // Apply values of all Famous-managed attributes to the document element.
+ // These will be deployed to the document on call to #setup().
+ function _applyAttributes(target) {
+ for (var n in this.attributes) {
+ DOMBuffer.setAttribute(target, n, this.attributes[n]);
+ }
+ for (var index in this._dirtyAttributes) {
+ var name = this._dirtyAttributes[index];
+ DOMBuffer.removeAttribute(target, name);
+ this._dirtyAttributes.shift();
+ }
+ }
+
+ // Clear all Famous-managed attributes from the document element.
+ // These will be deployed to the document on call to #setup().
+ function _cleanupAttributes(target) {
+ for (var n in this.attributes) {
+ DOMBuffer.removeAttribute(target, n);
+ }
+ }
+
+ function _xyNotEquals(a, b) {
+ return (a && b) ? (a[0] !== b[0] || a[1] !== b[1]) : a !== b;
+ }
+
+ /**
+ * One-time setup for an element to be ready for commits to document.
+ *
+ * @private
+ * @method setup
+ *
+ * @param {ElementAllocator} allocator document element pool for this context
+ */
+ Surface.prototype.setup = function setup(allocator) {
+ var target = this.allocate(allocator);
+ if (this.elementClass) {
+ if (this.elementClass instanceof Array) {
+ for (var i = 0; i < this.elementClass.length; i++) {
+ DOMBuffer.addToObject(target.classList, this.elementClass[i]);
}
- target.style.display = '';
- this.attach(target);
- this._opacity = null;
- this._currentTarget = target;
- this._stylesDirty = true;
- this._classesDirty = true;
- this._attributesDirty = true;
- this._sizeDirty = true;
- this._contentDirty = true;
- this._originDirty = true;
- this._transformDirty = true;
- };
-
- Surface.prototype.deallocate = function deallocate(allocator, target){
+ }
+ else {
+ DOMBuffer.addToObject(target.classList, this.elementClass);
+ }
+ }
+ DOMBuffer.assignProperty(target.style, 'display', '');
+ this.attach(target);
+ this._opacity = null;
+ this._currentTarget = target;
+ this._stylesDirty = true;
+ this._classesDirty = true;
+ this._attributesDirty = true;
+ this._sizeDirty = true;
+ this._contentDirty = true;
+ this._originDirty = true;
+ this._transformDirty = true;
+ };
+
+ Surface.prototype.deallocate = function deallocate(allocator, target) {
return allocator.deallocate(target);
};
- Surface.prototype.allocate = function allocate(allocator){
- return allocator.allocate({type: this.elementType});
+ Surface.prototype.allocate = function allocate(allocator) {
+ return allocator.allocate({ type: this.elementType });
};
- /**
- * Apply changes from this component to the corresponding document element.
- * This includes changes to classes, styles, size, content, opacity, origin,
- * and matrix transforms.
- *
- * @private
- * @method commit
- * @param {Context} context commit context
- */
- Surface.prototype.commit = function commit(context) {
- if (!this._currentTarget) this.setup(context.allocator);
- var target = this._currentTarget;
- var size = context.size;
-
- if (this._classesDirty) {
- _cleanupClasses.call(this, target);
- var classList = this.getClassList();
- for (var i = 0; i < classList.length; i++) target.classList.add(classList[i]);
- this._classesDirty = false;
- this._trueSizeCheck = true;
- }
+ /**
+ * Apply changes from this component to the corresponding document element.
+ * This includes changes to classes, styles, size, content, opacity, origin,
+ * and matrix transforms.
+ *
+ * @private
+ * @method commit
+ * @param {Context} context commit context
+ */
+ Surface.prototype.commit = function commit(context) {
+ if (!this._currentTarget) this.setup(context.allocator);
+ var target = this._currentTarget;
+ var size = context.size;
+
+ if (this._classesDirty) {
+ _cleanupClasses.call(this, target);
+ var classList = this.getClassList();
+ for (var i = 0; i < classList.length; i++) DOMBuffer.addToObject(target.classList, classList[i]);
+ this._classesDirty = false;
+ this._trueSizeCheck = true;
+ }
- if (this._stylesDirty) {
- _applyStyles.call(this, target);
- this._stylesDirty = false;
- this._trueSizeCheck = true;
- }
+ if (this._stylesDirty) {
+ _applyStyles.call(this, target);
+ this._stylesDirty = false;
+ this._trueSizeCheck = true;
+ }
- if (this._attributesDirty) {
- _applyAttributes.call(this, target);
- this._attributesDirty = false;
- this._trueSizeCheck = true;
- }
+ if (this._attributesDirty) {
+ _applyAttributes.call(this, target);
+ this._attributesDirty = false;
+ this._trueSizeCheck = true;
+ }
- if (this.size) {
- var origSize = context.size;
- size = [this.size[0], this.size[1]];
- if (size[0] === undefined) size[0] = origSize[0];
- if (size[1] === undefined) size[1] = origSize[1];
- if (size[0] === true || size[1] === true) {
- if (size[0] === true){
- if (this._trueSizeCheck || (this._size[0] === 0)) {
- var width = target.offsetWidth;
- if (this._size && this._size[0] !== width) {
- this._size[0] = width;
- this._sizeDirty = true;
- }
- size[0] = width;
- } else {
- if (this._size) size[0] = this._size[0];
- }
- }
- if (size[1] === true){
- if (this._trueSizeCheck || (this._size[1] === 0)) {
- var height = target.offsetHeight;
- if (this._size && this._size[1] !== height) {
- this._size[1] = height;
- this._sizeDirty = true;
- }
- size[1] = height;
- } else {
- if (this._size) size[1] = this._size[1];
- }
- }
- this._trueSizeCheck = false;
+ if (this.size) {
+ var origSize = context.size;
+ size = [this.size[0], this.size[1]];
+ if (size[0] === undefined) size[0] = origSize[0];
+ if (size[1] === undefined) size[1] = origSize[1];
+ if (size[0] === true || size[1] === true) {
+ if (size[0] === true) {
+ if (this._trueSizeCheck) {
+ var width = target.offsetWidth;
+ if (this._size && this._size[0] !== width) {
+ this._size[0] = width;
+ this._sizeDirty = true;
+ }
+ size[0] = width;
+ } else {
+ if (this._size) size[0] = this._size[0];
+ }
+ }
+ if (size[1] === true) {
+ if (this._trueSizeCheck) {
+ var height = target.offsetHeight;
+ if (this._size && this._size[1] !== height) {
+ this._size[1] = height;
+ this._sizeDirty = true;
}
+ size[1] = height;
+ } else {
+ if (this._size) size[1] = this._size[1];
+ }
}
+ this._trueSizeCheck = false;
+ }
+ }
- if (_xyNotEquals(this._size, size)) {
- if (!this._size) this._size = [0, 0];
- this._size[0] = size[0];
- this._size[1] = size[1];
+ if (_xyNotEquals(this._size, size)) {
+ if (!this._size) this._size = [0, 0];
+ this._size[0] = size[0];
+ this._size[1] = size[1];
- this._sizeDirty = true;
- }
+ this._sizeDirty = true;
+ }
- if (this._sizeDirty) {
- if (this._size) {
- target.style.width = this.size && this.size[0] === true || this._size[0] === true ? '' : this._size[0] + 'px';
- target.style.height = this.size && this.size[1] === true || this._size[1] === true ? '' : this._size[1] + 'px';
- }
+ if (this._sizeDirty) {
+ if (this._size) {
+ var resolvedWidth = this.size && this.size[0] === true || this._size[0] === true ? '' : this._size[0] + 'px';
+ var resolvedHeight = this.size && this.size[1] === true || this._size[1] === true ? '' : this._size[1] + 'px';
+ DOMBuffer.assignProperty(target.style, 'width', resolvedWidth);
+ DOMBuffer.assignProperty(target.style, 'height', resolvedHeight);
+ }
- this._eventOutput.emit('resize');
- }
+ this._eventOutput.emit('resize');
+ }
- if (this._contentDirty) {
- this.deploy(target);
- this._eventOutput.emit('deploy');
- this._contentDirty = false;
- this._trueSizeCheck = true;
- }
+ if (this._contentDirty) {
+ this.deploy(target);
+ this._eventOutput.emit('deploy');
+ this._contentDirty = false;
+ this._trueSizeCheck = true;
+ }
- ElementOutput.prototype.commit.call(this, context);
- };
-
- /**
- * Remove all Famous-relevant attributes from a document element.
- * This is called by SurfaceManager's detach().
- * This is in some sense the reverse of .deploy().
- *
- * @private
- * @method cleanup
- * @param {ElementAllocator} allocator
- */
- Surface.prototype.cleanup = function cleanup(allocator) {
- /* If clean-up done twice, return. This happens when a surface is cleaned up from
- * one context (e.g. group) and needs to be removed from another context that used to
- * display this surface. */
- if(!this._currentTarget){
- return;
- }
- var i = 0;
- var target = this._currentTarget;
- this._eventOutput.emit('recall');
- this.recall(target);
- target.style.display = 'none';
- target.style.opacity = '';
- target.style.width = '';
- target.style.height = '';
- _cleanupStyles.call(this, target);
- _cleanupAttributes.call(this, target);
- var classList = this.getClassList();
- _cleanupClasses.call(this, target);
- for (i = 0; i < classList.length; i++) target.classList.remove(classList[i]);
- if (this.elementClass) {
- if (this.elementClass instanceof Array) {
- for (i = 0; i < this.elementClass.length; i++) {
- target.classList.remove(this.elementClass[i]);
- }
- }
- else {
- target.classList.remove(this.elementClass);
- }
+ ElementOutput.prototype.commit.call(this, context);
+ };
+
+ /**
+ * Remove all Famous-relevant attributes from a document element.
+ * This is called by SurfaceManager's detach().
+ * This is in some sense the reverse of .deploy().
+ *
+ * @private
+ * @method cleanup
+ * @param {ElementAllocator} allocator
+ */
+ Surface.prototype.cleanup = function cleanup(allocator) {
+ /* If clean-up done twice, return. This happens when a surface is cleaned up from
+ * one context (e.g. group) and needs to be removed from another context that used to
+ * display this surface. */
+ if (!this._currentTarget) {
+ return;
+ }
+ var i = 0;
+ var target = this._currentTarget;
+ this._eventOutput.emit('recall');
+ this.recall(target);
+ DOMBuffer.assignProperty(target.style, 'display', 'none');
+ DOMBuffer.assignProperty(target.style, 'opacity', '');
+ DOMBuffer.assignProperty(target.style, 'width', '');
+ DOMBuffer.assignProperty(target.style, 'height', '');
+ _cleanupStyles.call(this, target);
+ _cleanupAttributes.call(this, target);
+ var classList = this.getClassList();
+ _cleanupClasses.call(this, target);
+ for (i = 0; i < classList.length; i++) target.classList.remove(classList[i]);
+ if (this.elementClass) {
+ if (this.elementClass instanceof Array) {
+ for (i = 0; i < this.elementClass.length; i++) {
+ DOMBuffer.removeFromObject(target.classList, this.elementClass[i]);
}
- this.detach(target);
- this._currentTarget = null;
- this.deallocate(allocator, target);
- };
-
- /**
- * Place the document element that this component manages into the document.
- *
- * @private
- * @method deploy
- * @param {Node} target document parent of this container
- */
- Surface.prototype.deploy = function deploy(target) {
- var content = this.getContent();
- if (content instanceof Node) {
- while (target.hasChildNodes())
- target.removeChild(target.firstChild);
- target.appendChild(content);
+ }
+ else {
+ DOMBuffer.removeFromObject(target.classList, this.elementClass);
+ }
+ }
+ this.detach(target);
+ this._currentTarget = null;
+ this.deallocate(allocator, target);
+ };
+
+ /**
+ * Place the document element that this component manages into the document.
+ *
+ * @private
+ * @method deploy
+ * @param {Node} target document parent of this container
+ */
+ Surface.prototype.deploy = function deploy(target) {
+ var content = this.getContent();
+
+ if (content instanceof Node) {
+ var children = target.childNodes || [];
+ //TODO Confirm that this works
+ for (var i = 0; i < children.length; i++) {
+ DOMBuffer.removeChild(target, children[i]);
+ }
+ DOMBuffer.appendChild(target, content);
+ } else {
+ /* textContent proved to be faster than innerHTML: https://jsperf.com/innerhtml-vs-textcontent-with-checks/1 */
+ if (content && content.includes && content.includes('<')) {
+ DOMBuffer.assignProperty(target, 'innerHTML', content);
+ DOMBuffer.setAttributeOnDescendants(target, 'data-arvaid', this.id);
} else {
- target.innerHTML = content;
+ DOMBuffer.assignProperty(target, 'textContent', content);
}
- this.content = target.innerHTML;
- };
-
- /**
- * FIX for famous-bug: https://github.com/Famous/famous/issues/673
- *
- * There is a bug in recall which causes the latest setContent()
- * call to be ignored, if the element is removed from the DOM in
- * the next render-cycle.
- */
- Surface.prototype.recall = function recall(target) {
- if (!this._contentDirty) {
- var df = document.createDocumentFragment();
- while (target.hasChildNodes()) {
- df.appendChild(target.firstChild);
- }
- this.setContent(df);
- }
- else {
- this._contentDirty = true;
- }
- };
-
- /**
- * Get the x and y dimensions of the surface.
- *
- * @method getSize
- * @return {Array.Number} [x,y] size of surface
- */
- Surface.prototype.getSize = function getSize() {
- return this._size ? this._size : this.size;
- };
-
- /**
- * Set x and y dimensions of the surface.
- *
- * @method setSize
- * @chainable
- * @param {Array.Number} size as [width, height]
- */
- Surface.prototype.setSize = function setSize(size) {
- this.size = size ? [size[0], size[1]] : null;
- this._sizeDirty = true;
- return this;
- };
-
- module.exports = Surface;
+ }
+
+ };
+
+ /**
+ * FIX for famous-bug: https://github.com/Famous/famous/issues/673
+ *
+ * There is a bug in recall which causes the latest setContent()
+ * call to be ignored, if the element is removed from the DOM in
+ * the next render-cycle.
+ */
+ Surface.prototype.recall = function recall(target) {
+ var df = document.createDocumentFragment();
+ var children = target.childNodes || [];
+ //TODO Confirm that this works
+ for (var i = 0; i < children.length; i++) {
+ DOMBuffer.appendChild(df, children[i]);
+ }
+ this._contentDirty = true;
+
+ };
+
+ /**
+ * Get the x and y dimensions of the surface.
+ *
+ * @method getSize
+ * @return {Array.Number} [x,y] size of surface
+ */
+ Surface.prototype.getSize = function getSize() {
+ return this._size ? this._size : this.size;
+ };
+
+ /**
+ * Set x and y dimensions of the surface.
+ *
+ * @method setSize
+ * @chainable
+ * @param {Array.Number} size as [width, height]
+ */
+ Surface.prototype.setSize = function setSize(size) {
+ this.size = size ? [size[0], size[1]] : null;
+ this._sizeDirty = true;
+ return this;
+ };
+
+ module.exports = Surface;
});
diff --git a/src/surfaces/ContainerSurface.js b/src/surfaces/ContainerSurface.js
index c238c36a..85420b02 100644
--- a/src/surfaces/ContainerSurface.js
+++ b/src/surfaces/ContainerSurface.js
@@ -95,20 +95,13 @@ define(function (require, exports, module) {
* @private
* @method commit
* @param {Context} context commit context
- * @param {Transform} transform unused TODO
- * @param {Number} opacity unused TODO
- * @param {Array.Number} origin unused TODO
- * @param {Array.Number} size unused TODO
* @return {undefined} TODO returns an undefined value
*/
- ContainerSurface.prototype.commit = function commit(context, transform, opacity, origin, size) {
+ ContainerSurface.prototype.commit = function commit(context) {
var previousSize = this._size ? [this._size[0], this._size[1]] : null;
var result = Surface.prototype.commit.apply(this, arguments);
- if (this._shouldRecalculateSize || (previousSize && (this._size[0] !== previousSize[0] || this._size[1] !== previousSize[1]))) {
- this.context.setSize();
- this._shouldRecalculateSize = false;
- }
- this.context.update();
+ this.context.setSize(context.size);
+ this.context.update({hide: context.opacity === 0 || context.hide});
return result;
};
diff --git a/src/surfaces/InputSurface.js b/src/surfaces/InputSurface.js
index 262c248b..60924662 100644
--- a/src/surfaces/InputSurface.js
+++ b/src/surfaces/InputSurface.js
@@ -9,6 +9,7 @@
define(function(require, exports, module) {
var Surface = require('../core/Surface');
+ var DOMBuffer = require('../core/DOMBuffer');
/**
* A Famo.us surface in the form of an HTML input element.
@@ -151,10 +152,11 @@ define(function(require, exports, module) {
* @param {Node} target document parent of this container
*/
InputSurface.prototype.deploy = function deploy(target) {
- if (this._placeholder !== '') target.placeholder = this._placeholder;
- target.value = this._value;
- target.type = this._type;
- target.name = this._name;
+ if (this._placeholder !== '')
+ DOMBuffer.assignProperty(target, 'placeholder', this._placeholder);
+ DOMBuffer.assignProperty(target, 'value', this._value);
+ DOMBuffer.assignProperty(target, 'type', this._type);
+ DOMBuffer.assignProperty(target, 'name', this._name);
};
module.exports = InputSurface;
diff --git a/src/utilities/Timer.js b/src/utilities/Timer.js
index 7ea87f6e..17669e5f 100644
--- a/src/utilities/Timer.js
+++ b/src/utilities/Timer.js
@@ -23,13 +23,7 @@ define(function(require, exports, module) {
var _event = 'prerender';
- var getTime = (typeof window !== 'undefined' && window.performance && window.performance.now) ?
- function() {
- return window.performance.now();
- }
- : function() {
- return Date.now();
- };
+
/**
* Add a function to be run on every prerender
@@ -58,9 +52,9 @@ define(function(require, exports, module) {
* @return {function} function passed in as parameter
*/
function setTimeout(fn, duration) {
- var t = getTime();
+ var t = FamousEngine.now();
var callback = function() {
- var t2 = getTime();
+ var t2 = FamousEngine.now();
if (t2 - t >= duration) {
fn.apply(this, arguments);
FamousEngine.removeListener(_event, callback);
@@ -82,12 +76,12 @@ define(function(require, exports, module) {
* @return {function} function passed in as parameter
*/
function setInterval(fn, duration) {
- var t = getTime();
+ var t = FamousEngine.now();
var callback = function() {
- var t2 = getTime();
+ var t2 = FamousEngine.now();
if (t2 - t >= duration) {
fn.apply(this, arguments);
- t = getTime();
+ t = FamousEngine.now();
}
};
return addTimerFunction(callback);
@@ -171,10 +165,10 @@ define(function(require, exports, module) {
return function() {
ctx = this;
args = arguments;
- timestamp = getTime();
+ timestamp = FamousEngine.now();
var fn = function() {
- var last = getTime - timestamp;
+ var last = FamousEngine.now() - timestamp;
if (last < wait) {
timeout = setTimeout(fn, wait - last);