!(function(win) {
/**
* FastDom
*
* Eliminates layout thrashing
* by batching DOM read/write
* interactions.
*
* @author Wilson Page
* @author Kornel Lesinski
*/
'use strict';
/**
* Mini logger
*
* @return {Function}
*/
var debug = 0 ? console.log.bind(console, '[fastdom]') : function() {};
/**
* Normalized rAF
*
* @type {Function}
*/
var raf = win.requestAnimationFrame
|| win.webkitRequestAnimationFrame
|| win.mozRequestAnimationFrame
|| win.msRequestAnimationFrame
|| function(cb) { return setTimeout(cb, 16); };
/**
* Initialize a `FastDom`.
*
* @constructor
*/
function FastDom() {
var self = this;
self.reads = [];
self.writes = [];
self.raf = raf.bind(win); // test hook
debug('initialized', self);
}
FastDom.prototype = {
constructor: FastDom,
/**
* We run this inside a try catch
* so that if any jobs error, we
* are able to recover and continue
* to flush the batch until it's empty.
*
* @param {Array} tasks
*/
runTasks: function(tasks) {
debug('run tasks');
var task; while (task = tasks.shift()) task();
},
/**
* Adds a job to the read batch and
* schedules a new frame if need be.
*
* @param {Function} fn
* @param {Object} ctx the context to be bound to `fn` (optional).
* @public
*/
measure: function(fn, ctx) {
debug('measure');
var task = !ctx ? fn : fn.bind(ctx);
this.reads.push(task);
scheduleFlush(this);
return task;
},
/**
* Adds a job to the
* write batch and schedules
* a new frame if need be.
*
* @param {Function} fn
* @param {Object} ctx the context to be bound to `fn` (optional).
* @public
*/
mutate: function(fn, ctx) {
debug('mutate');
var task = !ctx ? fn : fn.bind(ctx);
this.writes.push(task);
scheduleFlush(this);
return task;
},
/**
* Clears a scheduled 'read' or 'write' task.
*
* @param {Object} task
* @return {Boolean} success
* @public
*/
clear: function(task) {
debug('clear', task);
return remove(this.reads, task) || remove(this.writes, task);
},
/**
* Extend this FastDom with some
* custom functionality.
*
* Because fastdom must *always* be a
* singleton, we're actually extending
* the fastdom instance. This means tasks
* scheduled by an extension still enter
* fastdom's global task queue.
*
* The 'super' instance can be accessed
* from `this.fastdom`.
*
* @example
*
* var myFastdom = fastdom.extend({
* initialize: function() {
* // runs on creation
* },
*
* // override a method
* measure: function(fn) {
* // do extra stuff ...
*
* // then call the original
* return this.fastdom.measure(fn);
* },
*
* ...
* });
*
* @param {Object} props properties to mixin
* @return {FastDom}
*/
extend: function(props) {
debug('extend', props);
if (typeof props != 'object') throw new Error('expected object');
var child = Object.create(this);
mixin(child, props);
child.fastdom = this;
// run optional creation hook
if (child.initialize) child.initialize();
return child;
},
// override this with a function
// to prevent Errors in console
// when tasks throw
catch: null
};
/**
* Schedules a new read/write
* batch if one isn't pending.
*
* @private
*/
function scheduleFlush(fastdom) {
if (!fastdom.scheduled) {
fastdom.scheduled = true;
fastdom.raf(flush.bind(null, fastdom));
debug('flush scheduled');
}
}
/**
* Runs queued `read` and `write` tasks.
*
* Errors are caught and thrown by default.
* If a `.catch` function has been defined
* it is called instead.
*
* @private
*/
function flush(fastdom) {
debug('flush');
var writes = fastdom.writes;
var reads = fastdom.reads;
var error;
try {
debug('flushing reads', reads.length);
fastdom.runTasks(reads);
debug('flushing writes', writes.length);
fastdom.runTasks(writes);
} catch (e) { error = e; }
fastdom.scheduled = false;
// If the batch errored we may still have tasks queued
if (reads.length || writes.length) scheduleFlush(fastdom);
if (error) {
debug('task errored', error.message);
if (fastdom.catch) fastdom.catch(error);
else throw error;
}
}
/**
* Remove an item from an Array.
*
* @param {Array} array
* @param {*} item
* @return {Boolean}
*/
function remove(array, item) {
var index = array.indexOf(item);
return !!~index && !!array.splice(index, 1);
}
/**
* Mixin own properties of source
* object into the target.
*
* @param {Object} target
* @param {Object} source
*/
function mixin(target, source) {
for (var key in source) {
if (source.hasOwnProperty(key)) target[key] = source[key];
}
}
// There should never be more than
// one instance of `FastDom` in an app
var exports = win.fastdom = (win.fastdom || new FastDom()); // jshint ignore:line
// Expose to CJS & AMD
if ((typeof define) == 'function') define(function() { return exports; });
else if ((typeof module) == 'object') module.exports = exports;
})( typeof window !== 'undefined' ? window : typeof this != 'undefined' ? this : globalThis);