toloframework
Version:
Javascript/HTML/CSS compiler for Firefox OS or nodewebkit apps using modules in the nodejs style.
345 lines (307 loc) • 8.54 kB
JavaScript
"use strict";
require("polyfill.promise");
var $ = require("dom");
var DB = require("tfw.data-binding");
var delay = window.setTimeout;
var EMPTY_FUNC = function() {};
var ID = 1;
var Fx = function( name ) {
// Because it is possible to call `end()` before the sequence is
// over, we need to know in what session we are. This way, if a
// callback is called after the end, it can know that it must not
// execute the next task.
this._session = {};
// A name can be defined for debug purpose, but it is not used right now.
Object.defineProperty( Fx.prototype, 'name', {
value: name,
writable: false,
configurable: true,
enumerable: true
});
this._name = name;
// Sequence of tasks. A task is a function with two parameters:
// the `next()` function and a boolean `async` which tells you if
// your are allowed to call async functions.
// When a task is called in the `end()` function, it is not
// allowed to call async functions.
this._tasks = [];
// Index of the current task.
this._index = 0;
this._started = false;
this._startTime = 0;
this._onEnd = EMPTY_FUNC;
};
/**
* @member Fx.go
* @param
*/
Fx.prototype.start = function(onEnd) {
if( this._started ) this.end();
if (typeof onEnd !== 'function' ) onEnd = EMPTY_FUNC;
this._onEnd = onEnd;
this._started = true;
this._index = 0;
this._session = { $id: ID++ };
this._startTime = Date.now();
next.call( this, this._session );
};
function next( session ) {
if( session != this._session ) return;
if( this._index >= this._tasks.length ) {
this._index = 0;
this._started = false;
delete this._session;
this._onEnd( this );
return;
}
var that = this;
var tsk = this._tasks[this._index++];
if( this._debug ) {
console.info( "[dom.fx] tsk[" + (this._index - 1) + "]: ", tsk.label,
"(" + (Date.now() - this._startTime) + " ms)", tsk.args, session );
}
tsk(function(){
delay( next.bind( that, session ) );
}, true);
};
Fx.prototype.end = function() {
if( !this._started ) return this;
var that = this;
this._started = false;
delete this._session;
while( this._index < this._tasks.length ) {
var tsk = this._tasks[this._index++];
if( that._debug ) {
console.info( "[dom.fx.end] tsk[" + (this._index - 1) + "]: ", tsk.label, tsk.args );
}
tsk( EMPTY_FUNC, false );
}
this._onEnd( this );
return this;
};
/**
* @member Fx.debug
* @param value
*/
Fx.prototype.debug = function(value) {
this.addTask( function( next ) {
this._debug = value ? true : false;
next();
});
return this;
};
/**
* @return void
*/
Fx.prototype.addTask = function( task, label, args ) {
task.label = label;
task.args = args;
this._tasks.push( task );
return this;
};
/**
* @return void
*/
Fx.prototype.log = function(msg) {
this.addTask(function(next) {
console.log( "[dom.fx]", msg );
next();
}, 'log');
return this;
};
/**
* Stop execution of the animation until end() has been called.
*/
Fx.prototype.pause = function() {
this.addTask( EMPTY_FUNC, 'pause' );
return this;
};
Fx.prototype.exec = function() {
var args = Array.prototype.slice.call( arguments );
this.addTask(function(next, session) {
args.forEach(function (arg) {
try {
if( typeof arg === 'function' ) arg( session );
else console.log( '[dom.fx]', arg );
}
catch( ex ) {
console.error( ex );
}
});
next();
}, 'exec', args );
return this;
};
[
'css', 'addClass', 'removeClass', 'toggleClass', 'detach',
'saveStyle', 'restoreStyle', 'add', 'removeAtt', 'replace'
].forEach(function (methodname) {
var slot = $[methodname];
Fx.prototype[methodname] = function() {
var args = Array.prototype.slice.call(arguments);
this.addTask(function(next) {
slot.apply( $, args );
next();
}, methodname, args );
return this;
};
});
/**
* @member Fx.vanish
* @param elem, ms
*/
Fx.prototype.vanish = function(elem, ms) {
ms = parseInt( ms );
if( isNaN( ms ) ) ms = 300;
return this.css( elem, { transition: "none" } )
.css(elem, { transition: "opacity " + ms + "ms", opacity: 0 } )
.wait( ms );
};
Fx.prototype.vanishAndDetach = function(elem, ms) {
ms = parseInt( ms );
if( isNaN( ms ) ) ms = 300;
return this.vanish(elem, ms).detach(elem);
};
/**
* @member Fx.wait
* @param ms
*/
Fx.prototype.wait = function(msOrElem) {
var that = this;
var args = Array.prototype.slice.call( arguments );
if( typeof msOrElem === 'undefined' ) msOrElem = 0;
if( typeof msOrElem === 'number' ) {
this.addTask(function(next, async) {
if( async ) {
delay(next, msOrElem);
}
}, 'wait', args );
} else {
this.addTask(function(next, async) {
if( async ) {
var e = $(msOrElem);
var slot = function(evt) {
['transitionend', 'oTransitionEnd', 'webkitTransitionEnd'].forEach(function (itm) {
e.removeEventListener( itm, slot );
});
next();
};
['transitionend', 'oTransitionEnd', 'webkitTransitionEnd'].forEach(function (itm) {
e.addEventListener( itm, slot );
});
}
}, 'wait', args );
}
return this;
};
module.exports = function( name ) {
return new Fx( name );
};
/**
* @module dom.fx
*
* @description
* All the animation effects you can do on DOM elements.
*
* @example
* var mod = require('dom.fx');
*/
/**
* @export {function} fullscreen
* @param opts
* * __target__: target DOM element.
*
* @return {object}
* * __value__ {boolean}: If `true` the element goes
fullscreen. Otherwise, it returns to its initial position.
*/
module.exports.Fullscreen = function( opts ) {
if( typeof opts === 'undefined' ) {
throw Error("[dom.fx:fullscreen] Missing argument!");
}
if( typeof opts.target === 'undefined' ) {
throw Error("[dom.fx:fullscreen] Missing `opts.target`!");
}
if (typeof opts.target.element === 'function') opts.target = opts.target.element();
if( typeof opts.target.element !== 'undefined' ) opts.target = opts.target.element;
var voidFunc = function() {};
var tools = {
onBeforeReplace: typeof opts.onBeforeReplace === 'function' ? opts.onBeforeReplace : voidFunc,
onAfterReplace: typeof opts.onAfterReplace === 'function' ? opts.onAfterReplace : voidFunc
};
DB.propBoolean(this, 'value')(function(isFullScreen) {
if (isFullScreen) {
fullscreenOn( opts.target, tools );
} else {
fullscreenOff( opts.target, tools );
}
});
};
function fullscreenOn( target, tools ) {
if (tools.terminate) tools.terminate();
var rect = target.getBoundingClientRect();
console.info("[dom.fx] rect=...", rect);
var substitute = $.div();
$.css(substitute, {
display: 'inline-block',
width: rect.width + "px",
height: rect.height + "px"
});
tools.onBeforeReplace( target );
$.replace( substitute, target );
tools.onAfterReplace( target );
tools.substitute = substitute;
tools.styles = saveStyles( target );
tools.overlay = $.div('dom-fx-fullscreen');
document.body.appendChild( tools.overlay );
tools.overlay.appendChild( target );
$.css(target, {
left: rect.left + 'px',
top: rect.top + 'px',
width: rect.width + 'px',
height: rect.height + 'px'
});
$.addClass(target, 'dom-fx-fullscreen-target');
delay(function() {
var r2 = tools.overlay.getBoundingClientRect();
$.css(target, {
left: '20px',
top: '20px',
width: (r2.width - 40) + 'px',
height: (r2.height - 40) + 'px'
});
});
}
function fullscreenOff( target, tools ) {
var rect = tools.substitute.getBoundingClientRect();
$.css(target, {
left: rect.left + 'px',
top: rect.top + 'px',
width: rect.width + 'px',
height: rect.height + 'px'
});
tools.terminate = function() {
$.detach( tools.overlay );
tools.onBeforeReplace( target );
$.replace( target, tools.substitute );
tools.onAfterReplace( target );
loadStyles( target, tools.styles );
delete tools.terminate;
};
delay(tools.terminate, 200);
}
function saveStyles( element ) {
var styles = {};
var key, val;
for( key in element.style ) {
val = element.style[key];
styles[key] = val;
}
console.info("[dom.fx] styles=...", styles);
return styles;
}
function loadStyles( element, styles ) {
for( var key in styles ) {
element.style[key] = styles[key];
}
}