util.js 5.5 KB

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