util.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. /**
  2. * Extend object a with the properties of object b.
  3. * If there's a conflict, object b takes precedence.
  4. *
  5. * @param {object} a
  6. * @param {object} b
  7. */
  8. export const extend = ( a, b ) => {
  9. for( let i in b ) {
  10. a[ i ] = b[ i ];
  11. }
  12. return a;
  13. }
  14. /**
  15. * querySelectorAll but returns an Array.
  16. */
  17. export const queryAll = ( el, selector ) => {
  18. return Array.from( el.querySelectorAll( selector ) );
  19. }
  20. /**
  21. * classList.toggle() with cross browser support
  22. */
  23. export const toggleClass = ( el, className, value ) => {
  24. if( value ) {
  25. el.classList.add( className );
  26. }
  27. else {
  28. el.classList.remove( className );
  29. }
  30. }
  31. /**
  32. * Utility for deserializing a value.
  33. *
  34. * @param {*} value
  35. * @return {*}
  36. */
  37. export const deserialize = ( value ) => {
  38. if( typeof value === 'string' ) {
  39. if( value === 'null' ) return null;
  40. else if( value === 'true' ) return true;
  41. else if( value === 'false' ) return false;
  42. else if( value.match( /^-?[\d\.]+$/ ) ) return parseFloat( value );
  43. }
  44. return value;
  45. }
  46. /**
  47. * Measures the distance in pixels between point a
  48. * and point b.
  49. *
  50. * @param {object} a point with x/y properties
  51. * @param {object} b point with x/y properties
  52. *
  53. * @return {number}
  54. */
  55. export const distanceBetween = ( a, b ) => {
  56. let dx = a.x - b.x,
  57. dy = a.y - b.y;
  58. return Math.sqrt( dx*dx + dy*dy );
  59. }
  60. /**
  61. * Applies a CSS transform to the target element.
  62. *
  63. * @param {HTMLElement} element
  64. * @param {string} transform
  65. */
  66. export const transformElement = ( element, transform ) => {
  67. element.style.transform = transform;
  68. }
  69. /**
  70. * Find the closest parent that matches the given
  71. * selector.
  72. *
  73. * @param {HTMLElement} target The child element
  74. * @param {String} selector The CSS selector to match
  75. * the parents against
  76. *
  77. * @return {HTMLElement} The matched parent or null
  78. * if no matching parent was found
  79. */
  80. export const closestParent = ( target, selector ) => {
  81. let parent = target.parentNode;
  82. while( parent ) {
  83. // There's some overhead doing this each time, we don't
  84. // want to rewrite the element prototype but should still
  85. // be enough to feature detect once at startup...
  86. let matchesMethod = parent.matches || parent.matchesSelector || parent.msMatchesSelector;
  87. // If we find a match, we're all set
  88. if( matchesMethod && matchesMethod.call( parent, selector ) ) {
  89. return parent;
  90. }
  91. // Keep searching
  92. parent = parent.parentNode;
  93. }
  94. return null;
  95. }
  96. /**
  97. * Handling the fullscreen functionality via the fullscreen API
  98. *
  99. * @see http://fullscreen.spec.whatwg.org/
  100. * @see https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode
  101. */
  102. export const enterFullscreen = element => {
  103. element = element || document.documentElement;
  104. // Check which implementation is available
  105. let requestMethod = element.requestFullscreen ||
  106. element.webkitRequestFullscreen ||
  107. element.webkitRequestFullScreen ||
  108. element.mozRequestFullScreen ||
  109. element.msRequestFullscreen;
  110. if( requestMethod ) {
  111. requestMethod.apply( element );
  112. }
  113. }
  114. /**
  115. * Creates an HTML element and returns a reference to it.
  116. * If the element already exists the existing instance will
  117. * be returned.
  118. *
  119. * @param {HTMLElement} container
  120. * @param {string} tagname
  121. * @param {string} classname
  122. * @param {string} innerHTML
  123. *
  124. * @return {HTMLElement}
  125. */
  126. export const createSingletonNode = ( container, tagname, classname, innerHTML='' ) => {
  127. // Find all nodes matching the description
  128. let nodes = container.querySelectorAll( '.' + classname );
  129. // Check all matches to find one which is a direct child of
  130. // the specified container
  131. for( let i = 0; i < nodes.length; i++ ) {
  132. let testNode = nodes[i];
  133. if( testNode.parentNode === container ) {
  134. return testNode;
  135. }
  136. }
  137. // If no node was found, create it now
  138. let node = document.createElement( tagname );
  139. node.className = classname;
  140. node.innerHTML = innerHTML;
  141. container.appendChild( node );
  142. return node;
  143. }
  144. /**
  145. * Injects the given CSS styles into the DOM.
  146. *
  147. * @param {string} value
  148. */
  149. export const createStyleSheet = ( value ) => {
  150. let tag = document.createElement( 'style' );
  151. tag.type = 'text/css';
  152. if( value && value.length > 0 ) {
  153. if( tag.styleSheet ) {
  154. tag.styleSheet.cssText = value;
  155. }
  156. else {
  157. tag.appendChild( document.createTextNode( value ) );
  158. }
  159. }
  160. document.head.appendChild( tag );
  161. return tag;
  162. }
  163. /**
  164. * Returns a key:value hash of all query params.
  165. */
  166. export const getQueryHash = () => {
  167. let query = {};
  168. location.search.replace( /[A-Z0-9]+?=([\w\.%-]*)/gi, a => {
  169. query[ a.split( '=' ).shift() ] = a.split( '=' ).pop();
  170. } );
  171. // Basic deserialization
  172. for( let i in query ) {
  173. let value = query[ i ];
  174. query[ i ] = deserialize( unescape( value ) );
  175. }
  176. // Do not accept new dependencies via query config to avoid
  177. // the potential of malicious script injection
  178. if( typeof query['dependencies'] !== 'undefined' ) delete query['dependencies'];
  179. return query;
  180. }
  181. /**
  182. * Returns the remaining height within the parent of the
  183. * target element.
  184. *
  185. * remaining height = [ configured parent height ] - [ current parent height ]
  186. *
  187. * @param {HTMLElement} element
  188. * @param {number} [height]
  189. */
  190. export const getRemainingHeight = ( element, height = 0 ) => {
  191. if( element ) {
  192. let newHeight, oldHeight = element.style.height;
  193. // Change the .stretch element height to 0 in order find the height of all
  194. // the other elements
  195. element.style.height = '0px';
  196. // In Overview mode, the parent (.slide) height is set of 700px.
  197. // Restore it temporarily to its natural height.
  198. element.parentNode.style.height = 'auto';
  199. newHeight = height - element.parentNode.offsetHeight;
  200. // Restore the old height, just in case
  201. element.style.height = oldHeight + 'px';
  202. // Clear the parent (.slide) height. .removeProperty works in IE9+
  203. element.parentNode.style.removeProperty('height');
  204. return newHeight;
  205. }
  206. return height;
  207. }