UNPKG

toloframework

Version:

Javascript/HTML/CSS compiler for Firefox OS or nodewebkit apps using modules in the nodejs style.

362 lines (323 loc) 9.74 kB
"use strict"; require( "polyfill.promise" ); const $ = require( "dom" ), DB = require( "tfw.data-binding" ); const EMPTY_FUNC = function () { // Do nothing. }; // Unique ID. let ID = 1; const delay = window.setTimeout; /** * @class Fx * @param {string} name - Optional name of this animation chain. */ function Fx( 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; } /** * @param {function} _onEnd - Function to call as soon as the animation ends. * @returns {undefined} */ Fx.prototype.start = function ( _onEnd ) { const onEnd = typeof _onEnd !== "undefined" ? _onEnd : EMPTY_FUNC; if ( this._started ) this.end(); this._onEnd = onEnd; this._started = true; this._index = 0; this._session = { $id: ID++ }; this._startTime = Date.now(); next.call( this, this._session ); }; /** * Next step of the animation. * @this Fx * @param {object} session - ``{ $id } * @returns {undefined} */ 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; } const that = this, 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 ]; } }