UNPKG

toloframework

Version:

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

557 lines (515 loc) 15.3 kB
/** * @module dom * * @description * Functions which facilitate DOM manipulations. * Included __interact.js__. You can find documentation for it here: * [http://interactjs.io/docs/] * * @example * var mod = require('dom'); */ require("polyfill.classList"); var Theme = require("dom.theme"); var PointerEvents = require("tfw.pointer-events"); var $ = function(dom) { if( dom instanceof Node ) return dom; if ( dom === undefined || dom === null ) { throw Error("`dom` is not a valid element!", dom); } if( dom.$ instanceof Node ) return dom.$; if( dom.element instanceof Node ) return dom.element; if (typeof dom === 'string') { var elem = document.getElementById( dom ); if (!elem) { console.error( "[dom] There is no DOM element with this ID: `" + dom + "`" ); } return elem; } if (typeof dom.element === 'function') return dom.element(); return dom; }; module.exports = $; // Used to store data on the DOM element without colliding with existing attributes. var SYMBOL = '@dom' + Date.now(); var RX_ENTITY = /^&(#[0-9]+|[a-zA-Z0-9]+);$/; $.tagNS = tagNS; $.svgRoot = tagNS.bind( undefined, "http://www.w3.org/2000/svg", "svg", { version: '1.1', 'xmlns:svg': 'http://www.w3.org/2000/svg', xmlns: 'http://www.w3.org/2000/svg', 'xmlns:xlink': 'http://www.w3.org/1999/xlink' }); $.svg = tagNS.bind( undefined, "http://www.w3.org/2000/svg" ); $.tag = tagNS.bind( undefined, "http://www.w3.org/1999/xhtml" ); $.div = tagNS.bind( undefined, "http://www.w3.org/1999/xhtml", "div" ); $.txt = window.document.createTextNode.bind( window.document ); $.textOrHtml = textOrHtml; $.get = get; /** * Add a readonly `element` property to `obj` and return it. */ $.elem = elem; /** * Apply css rules on `element`. * * @return `element`. * * @example * var $ = require('dom'); * $.css( element, { width: '800px'. height: '600px' }); */ $.css = css; $.att = att; $.removeAtt = removeAtt; $.addClass = addClass; $.hasClass = hasClass; $.removeClass = removeClass; $.toggleClass = toggleClass; $.saveStyle = saveStyle; $.restoreStyle = restoreStyle; /** * @param {string} name - Name of this theme. * @param {object} style - Colors. * @param {string} style.bg0 - [optional] Background level 0. * @param {string} style.bg1 - [optional] Background level 1. * @param {string} style.bg2 - [optional] Background level 2. * @param {string} style.bg3 - [optional] Background level 3. * @param {string} style.bgP - [optional] Primary color. * @param {string} style.bgPD - [optional] Primary color (dark version). * @param {string} style.bgPL - [optional] Primary color (light version). * @param {string} style.bgS - [optional] Accent color. * @param {string} style.bgSD - [optional] Accent color (dark version). * @param {string} style.bgSL - [optional] Accent color (light version). * @param {string} style.fg* - [optional] To each background color * (bg) correspond a text color (fg). */ $.registerTheme = Theme.register.bind($); /** * @param {string} name - Name of the heme to apply. */ $.applyTheme = Theme.apply.bind($); /** * @param newElem {Element} - Replacement element. * @param oldElem {Element} - Element to replace. */ $.replace = replace; /** * Remove element from its parent. * @param element {Element} - Element to detach from its parent. * @return The parent element. */ $.detach = detach; /** * Add event handlers to one or many elements. * * @param {object|array} element - list of elements on which apply * events handlers. * * @param {object|function} slots - If a function is given, it is * considered as a slot for the event `tap`. Otherwise, the object is * a map between events' names (the key) and function to handle the * event (the value). * Events' names are: * * __tap__: When the element is pressed and released in less than 900 ms and without too much sliding. * * __doubletap__ * * __dragmove__ * * @param {boolean} capture - If `true` events are captured before they reach the children. * * @example * DOM.on( [screen, button], function() {...} ); * DOM.on( body, null ); // Do nothing, but stop propagation. * DOM.on( element, { tap: function() {...} } ); */ $.on = on; $.off = off; /** * Append all the `children` to `element`. * @param element * @param ...children */ $.add = add; /** * Add the attribute `element` and the following functions to `obj`: * * __css__ * * __addClass__ * * __removeClass__ * * __toggleClass__ */ $.wrap = wrap; /** * Remove all children of the `element`. * @param element {Element} - Element from which remove all the children. */ $.clear = clear; function wrap( obj, element, nomethods ) { Object.defineProperty( obj, 'element', { value: element, writable: false, configurable: false, enumerable: true }); if( nomethods ) return obj; obj.on = on.bind( obj, element ); obj.css = css.bind( obj, element ); obj.add = add.bind( obj, element ); obj.att = att.bind( obj, element ); obj.addClass = addClass.bind( obj, element ); obj.hasClass = hasClass.bind( obj, element ); obj.removeClass = removeClass.bind( obj, element ); obj.toggleClass = toggleClass.bind( obj, element ); return obj; } function replace( newElem, oldElem ) { newElem = $(newElem); oldElem = $(oldElem); oldElem.parentNode.replaceChild( newElem, oldElem ); return newElem; } function css( element, styles ) { element = $(element); var key, val; for( key in styles ) { val = styles[key]; element.style[key] = val; } return element; } function att( element, attribs, value ) { element = $(element); var key, val; if (typeof attribs === 'string') { if( typeof value === 'undefined' ) value = ""; key = attribs; attribs = {}; attribs[key] = value; } for( key in attribs ) { val = attribs[key]; element.setAttribute( key, val ); } return element; } function removeAtt( element, attrib ) { element = $(element); element.removeAttribute( attrib ); return element; } function add( element ) { element = $(element); try { var i, child, isValidArgument; for (i = 1 ; i < arguments.length ; i++) { child = arguments[i]; isValidArgument = addArray( element, child ) || addText( element, child ) || addNode( element, child ); if( !isValidArgument ) { console.error("Argument #" + i + " of dom.add() is invalid!", arguments); } } return element; } catch( ex ) { console.error( "[DOM.add] arguments=", [].slice.call( arguments ) ); throw Error( "[DOM.add] " + ex ); } } function addNode( element, child ) { child = $(child); if( child instanceof Node ) { element.appendChild( child ); return true; } return false; } function addText( element, child ) { if( typeof child === 'number' ) child = '' + child; if( typeof child !== 'string' ) return false; if( child.substr( 0, 6 ).toLowerCase() == '<html>' ) { // Text starting with '<html>' is HTML code to parse. var html = child.substr( 6 ); child = $.tag('span'); child.innerHTML = html; } else if( RX_ENTITY.test( child ) ) { // HTML entity, for instance: &amp;, &lt;, ... var text = child; child = $.tag('span'); child.innerHTML = text; } else { child = document.createTextNode( child ); } element.appendChild( child ); return true; } function addArray( element, array ) { if( !Array.isArray( array ) ) return false; array.forEach(function (child) { add( element, child ); }); return true; } function off( element ) { if( Array.isArray( element ) ) { element.forEach(function ( elem ) { off( elem ); }); return element; } if( typeof element[SYMBOL] === 'undefined' ) return element; var pe = element[SYMBOL].events; if( typeof pe === 'undefined' ) return element; pe.off(); delete element[SYMBOL].events; } var NON_POINTER_EVENTS = [ 'keyup', 'keydown', 'scroll', 'load', 'error' ]; function on( element, slots, extra ) { // If only a function is passed, we consider this is a Tap event. if( typeof slots === 'function' || slots === null ) slots = { tap: slots }; else if( typeof slots === 'string' && typeof extra === 'function' ) { // Syntax: $.on( elem, 'tap', function(evt) {...} ) var obj = {}; obj[slots] = extra; slots = obj; } if( Array.isArray( element ) ) { element.forEach(function ( elem ) { on( elem, slots ); }); return element; } element = $(element); if( typeof element[SYMBOL] === 'undefined' ) element[SYMBOL] = {}; if( typeof element[SYMBOL].events === 'undefined' ) { element[SYMBOL].events = new PointerEvents( element ); } var key, val, preview; for( key in slots ) { val = slots[key]; if (key.charAt(0) == '!') { key = key.substr(1); preview = true; } else { preview = false; } if( NON_POINTER_EVENTS.indexOf(key.toLowerCase()) > -1 ) { element.addEventListener( key, val, preview ); } else { element[SYMBOL].events.on( key, val, preview ); } } return element; } function tagNS( ns, name ) { try { var e = document.createElementNS( ns, name.trim().toLowerCase() ); var i, arg, key, val; for (i = 2 ; i < arguments.length ; i++) { arg = arguments[i]; if( Array.isArray(arg) ) { // Arrays are for children. arg.forEach(function (child) { switch( typeof child ) { case 'string': case 'number': case 'boolean': child = '' + child; if( child.substr( 0, 6 ) == '<html>' ) { var html = child.substr( 6 ); child = $.tag('span'); child.innerHTML = html; } else { child = document.createTextNode( child ); } break; } add( e, child ); }); } else { switch( typeof arg ) { case "string": arg.split( ' ' ).forEach(function ( item ) { if( item.length > 0 ) { addClass(e, item); } }); break; case "object": for( key in arg ) { val = arg[key]; e.setAttribute( key, val ); } break; default: throw Error("[dom.tag] Error creating <" + name + ">: Invalid argument #" + i + "!"); } } } return e; } catch (ex) { console.error("[dom.tagNS] Error with `ns` = ", ns, " and `name` = ", name); console.error(ex); } }; function addClass(elem) { var args = [].slice.call( arguments, 1 ); if( Array.isArray( elem ) ) { // Loop on each element. args.unshift( null ); elem.forEach(function ( child ) { args[0] = child; addClass.apply( undefined, args ); }); return elem; } elem = $( elem ); args.forEach(function (className) { if (typeof className !== 'string') return; className = className.trim(); if( className.length == 0 ) return; try { if( elem.classList ) elem.classList.add( className ); } catch( ex ) { console.error( "[dom.addClass] Invalid class name: ", className ); console.error( ex ); } }); return elem; } function hasClass( elem, className ) { elem = $( elem ); if( !elem.classList ) return false; return elem.classList.contains( className ); } function removeClass(elem) { var args = [].slice.call( arguments, 1 ); if( Array.isArray( elem ) ) { // Loop on each element. args.unshift( null ); elem.forEach(function ( child ) { args[0] = child; removeClass.apply( undefined, args ); }); return elem; } elem = $( elem ); args.forEach(function (className) { if (typeof className !== 'string') return; try { if( elem.classList ) elem.classList.remove( className ); } catch( ex ) { console.error( "[dom.removeClass] Invalid class name: ", className ); console.error( ex ); } }); return elem; } function toggleClass(elem) { var args = [].slice.call( arguments, 1 ); args.forEach(function( className ) { if( hasClass( elem, className ) ) { removeClass( elem, className ); } else { addClass( elem, className ); } }); return elem; } function clear( element ) { // (!) On préfère retirer les éléments un par un du DOM plutôt que d'utiliser simplement // this.html(""). // En effet, le code simplifié a des conséquences inattendues dans IE9 et IE10 au moins. // Le bug des markers qui disparaissaients sur les cartes de Trail-Passion 4 a été corrigé // avec cette modification. element = $(element); var e = element; while(e.firstChild){ e.removeChild(e.firstChild); } var args = [].slice.call( arguments ); if( args.length > 1 ) { add.apply( this, args ); } return element; } function get( element, query ) { element = $(element); if( typeof query === 'undefined' ) { query = element; element = window.document; } return element.querySelector( query ); } function detach( element ) { element = $(element); var parent = element.parentElement; if( !parent ) return parent; parent.removeChild( element ); return parent; } function elem( target ) { var args = [].slice.call( arguments ); args.shift(); if (args.length == 0) args = ['div']; args.push('dom', 'custom'); var e; if (typeof args[0].element !== 'undefined') { e = args[0].element; addClass( e, 'dom', 'custom' ); } else if (typeof args[0].appendChild === 'function') { e = args[0]; addClass( e, 'dom', 'custom' ); } else { e = $.tag.apply( $, args ); } Object.defineProperty( target, 'element', { value: e, writable: false, configurable: false, enumerable: true }); return e; } function textOrHtml( element, content ) { if( typeof content === 'undefined' ) content = ''; if (content === null) content = ''; if (typeof content !== 'string') content = JSON.stringify( content ); if (content.substr(0, 6) == '<html>') { element.innerHTML = content.substr(6); } else { element.textContent = content; } return element; } function saveStyle( elements ) { if( !Array.isArray( elements ) ) return saveStyle( Array.prototype.slice.call( arguments ) ); elements.forEach(function (elem) { elem = $( elem ); if( typeof elem[SYMBOL] === 'undefined' ) elem[SYMBOL] = {}; if( !Array.isArray( elem[SYMBOL].style ) ) elem[SYMBOL].style = []; elem[SYMBOL].style.push( JSON.stringify( elem.style ) ); }); } function restoreStyle( elements ) { if( !Array.isArray( elements ) ) return restoreStyle( Array.prototype.slice.call( arguments ) ); elements.forEach(function (elem) { elem = $( elem ); if( typeof elem[SYMBOL] === 'undefined' || !Array.isArray( elem[SYMBOL].style ) ) throw Error( "[dom.restoreStyle] `saveStyle()` has never been used on this element!"); if( elem[SYMBOL].style.length == 0 ) throw Error( "[dom.restoreStyle] more `restore` than `save`!"); var styles = JSON.parse( elem[SYMBOL].style.pop() ); var k, v; for( k in styles ) { v = styles[k]; if( typeof v !== 'undefined' ) { elem.style[k] = v; } } }); };