diff options
Diffstat (limited to 'present/js/utils/util.js')
| -rw-r--r-- | present/js/utils/util.js | 313 | 
1 files changed, 313 insertions, 0 deletions
| diff --git a/present/js/utils/util.js b/present/js/utils/util.js new file mode 100644 index 0000000..a5515e8 --- /dev/null +++ b/present/js/utils/util.js @@ -0,0 +1,313 @@ +/** + * Extend object a with the properties of object b. + * If there's a conflict, object b takes precedence. + * + * @param {object} a + * @param {object} b + */ +export const extend = ( a, b ) => { + +	for( let i in b ) { +		a[ i ] = b[ i ]; +	} + +	return a; + +} + +/** + * querySelectorAll but returns an Array. + */ +export const queryAll = ( el, selector ) => { + +	return Array.from( el.querySelectorAll( selector ) ); + +} + +/** + * classList.toggle() with cross browser support + */ +export const toggleClass = ( el, className, value ) => { +	if( value ) { +		el.classList.add( className ); +	} +	else { +		el.classList.remove( className ); +	} +} + +/** + * Utility for deserializing a value. + * + * @param {*} value + * @return {*} + */ +export const deserialize = ( value ) => { + +	if( typeof value === 'string' ) { +		if( value === 'null' ) return null; +		else if( value === 'true' ) return true; +		else if( value === 'false' ) return false; +		else if( value.match( /^-?[\d\.]+$/ ) ) return parseFloat( value ); +	} + +	return value; + +} + +/** + * Measures the distance in pixels between point a + * and point b. + * + * @param {object} a point with x/y properties + * @param {object} b point with x/y properties + * + * @return {number} + */ +export const distanceBetween = ( a, b ) => { + +	let dx = a.x - b.x, +		dy = a.y - b.y; + +	return Math.sqrt( dx*dx + dy*dy ); + +} + +/** + * Applies a CSS transform to the target element. + * + * @param {HTMLElement} element + * @param {string} transform + */ +export const transformElement = ( element, transform ) => { + +	element.style.transform = transform; + +} + +/** + * Element.matches with IE support. + * + * @param {HTMLElement} target The element to match + * @param {String} selector The CSS selector to match + * the element against + * + * @return {Boolean} + */ +export const matches = ( target, selector ) => { + +	let matchesMethod = target.matches || target.matchesSelector || target.msMatchesSelector; + +	return !!( matchesMethod && matchesMethod.call( target, selector ) ); + +} + +/** + * Find the closest parent that matches the given + * selector. + * + * @param {HTMLElement} target The child element + * @param {String} selector The CSS selector to match + * the parents against + * + * @return {HTMLElement} The matched parent or null + * if no matching parent was found + */ +export const closest = ( target, selector ) => { + +	// Native Element.closest +	if( typeof target.closest === 'function' ) { +		return target.closest( selector ); +	} + +	// Polyfill +	while( target ) { +		if( matches( target, selector ) ) { +			return target; +		} + +		// Keep searching +		target = target.parentNode; +	} + +	return null; + +} + +/** + * Handling the fullscreen functionality via the fullscreen API + * + * @see http://fullscreen.spec.whatwg.org/ + * @see https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode + */ +export const enterFullscreen = element => { + +	element = element || document.documentElement; + +	// Check which implementation is available +	let requestMethod = element.requestFullscreen || +						element.webkitRequestFullscreen || +						element.webkitRequestFullScreen || +						element.mozRequestFullScreen || +						element.msRequestFullscreen; + +	if( requestMethod ) { +		requestMethod.apply( element ); +	} + +} + +/** + * Creates an HTML element and returns a reference to it. + * If the element already exists the existing instance will + * be returned. + * + * @param {HTMLElement} container + * @param {string} tagname + * @param {string} classname + * @param {string} innerHTML + * + * @return {HTMLElement} + */ +export const createSingletonNode = ( container, tagname, classname, innerHTML='' ) => { + +	// Find all nodes matching the description +	let nodes = container.querySelectorAll( '.' + classname ); + +	// Check all matches to find one which is a direct child of +	// the specified container +	for( let i = 0; i < nodes.length; i++ ) { +		let testNode = nodes[i]; +		if( testNode.parentNode === container ) { +			return testNode; +		} +	} + +	// If no node was found, create it now +	let node = document.createElement( tagname ); +	node.className = classname; +	node.innerHTML = innerHTML; +	container.appendChild( node ); + +	return node; + +} + +/** + * Injects the given CSS styles into the DOM. + * + * @param {string} value + */ +export const createStyleSheet = ( value ) => { + +	let tag = document.createElement( 'style' ); +	tag.type = 'text/css'; + +	if( value && value.length > 0 ) { +		if( tag.styleSheet ) { +			tag.styleSheet.cssText = value; +		} +		else { +			tag.appendChild( document.createTextNode( value ) ); +		} +	} + +	document.head.appendChild( tag ); + +	return tag; + +} + +/** + * Returns a key:value hash of all query params. + */ +export const getQueryHash = () => { + +	let query = {}; + +	location.search.replace( /[A-Z0-9]+?=([\w\.%-]*)/gi, a => { +		query[ a.split( '=' ).shift() ] = a.split( '=' ).pop(); +	} ); + +	// Basic deserialization +	for( let i in query ) { +		let value = query[ i ]; + +		query[ i ] = deserialize( unescape( value ) ); +	} + +	// Do not accept new dependencies via query config to avoid +	// the potential of malicious script injection +	if( typeof query['dependencies'] !== 'undefined' ) delete query['dependencies']; + +	return query; + +} + +/** + * Returns the remaining height within the parent of the + * target element. + * + * remaining height = [ configured parent height ] - [ current parent height ] + * + * @param {HTMLElement} element + * @param {number} [height] + */ +export const getRemainingHeight = ( element, height = 0 ) => { + +	if( element ) { +		let newHeight, oldHeight = element.style.height; + +		// Change the .stretch element height to 0 in order find the height of all +		// the other elements +		element.style.height = '0px'; + +		// In Overview mode, the parent (.slide) height is set of 700px. +		// Restore it temporarily to its natural height. +		element.parentNode.style.height = 'auto'; + +		newHeight = height - element.parentNode.offsetHeight; + +		// Restore the old height, just in case +		element.style.height = oldHeight + 'px'; + +		// Clear the parent (.slide) height. .removeProperty works in IE9+ +		element.parentNode.style.removeProperty('height'); + +		return newHeight; +	} + +	return height; + +} + +const fileExtensionToMimeMap = { +	'mp4': 'video/mp4', +	'm4a': 'video/mp4', +	'ogv': 'video/ogg', +	'mpeg': 'video/mpeg', +	'webm': 'video/webm' +} + +/** + * Guess the MIME type for common file formats. + */ +export const getMimeTypeFromFile = ( filename='' ) => { +	return fileExtensionToMimeMap[filename.split('.').pop()] +} + +/** + * Encodes a string for RFC3986-compliant URL format. + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI#encoding_for_rfc3986 + * + * @param {string} url + */ +export const encodeRFC3986URI = ( url='' ) => { +	return encodeURI(url) +	  .replace(/%5B/g, "[") +	  .replace(/%5D/g, "]") +	  .replace( +		/[!'()*]/g, +		(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}` +	  ); +}
\ No newline at end of file | 
