Skip to content

Commit

Permalink
Merge branch 'dom-flushing' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
Karl Lundfall committed Jun 9, 2017
2 parents a42d8dc + d9d2e13 commit 631dfe6
Show file tree
Hide file tree
Showing 13 changed files with 842 additions and 570 deletions.
2 changes: 2 additions & 0 deletions src/core/Context.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
103 changes: 103 additions & 0 deletions src/core/DOMBuffer.js
Original file line number Diff line number Diff line change
@@ -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;
});

69 changes: 69 additions & 0 deletions src/core/DOMEventHandler.js
Original file line number Diff line number Diff line change
@@ -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: [email protected]
* @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;
});
14 changes: 9 additions & 5 deletions src/core/ElementAllocator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]);
}
}

Expand All @@ -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).
Expand Down Expand Up @@ -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;
};
Expand Down
43 changes: 22 additions & 21 deletions src/core/ElementOutput.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
};

Expand Down Expand Up @@ -142,15 +144,15 @@ 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);
}
}

// Detach Famous event handling from document events emanating from target
// 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)
}
}

Expand All @@ -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.
Expand All @@ -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));
};
}

Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -282,17 +283,17 @@ 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;
}
};

ElementOutput.prototype.cleanup = function cleanup() {
if (this._element) {
this._invisible = true;
this._element.style.display = 'none';
DOMBuffer.assignProperty(this._element.style, 'display', 'none');
}
};

Expand All @@ -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);
};

/**
Expand All @@ -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;
Expand Down
Loading

0 comments on commit 631dfe6

Please sign in to comment.