util.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  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. * Element.matches with IE support.
  71. *
  72. * @param {HTMLElement} target The element to match
  73. * @param {String} selector The CSS selector to match
  74. * the element against
  75. *
  76. * @return {Boolean}
  77. */
  78. export const matchesSelector = ( target, selector ) => {
  79. // There's some overhead doing this each time, we don't
  80. // want to rewrite the element prototype but should still
  81. // be enough to feature detect once at startup...
  82. let matchesMethod = parent.matches || parent.matchesSelector || parent.msMatchesSelector;
  83. // If we find a match, we're all set
  84. return !!( matchesMethod && matchesMethod.call( target, selector ) );
  85. }
  86. /**
  87. * Find the closest parent that matches the given
  88. * selector.
  89. *
  90. * @param {HTMLElement} target The child element
  91. * @param {String} selector The CSS selector to match
  92. * the parents against
  93. *
  94. * @return {HTMLElement} The matched parent or null
  95. * if no matching parent was found
  96. */
  97. export const closestParent = ( target, selector ) => {
  98. let parent = target.parentNode;
  99. while( parent ) {
  100. // If we find a match, we're all set
  101. if( matchesSelector( parent, selector ) ) {
  102. return parent;
  103. }
  104. // Keep searching
  105. parent = parent.parentNode;
  106. }
  107. return null;
  108. }
  109. /**
  110. * Handling the fullscreen functionality via the fullscreen API
  111. *
  112. * @see http://fullscreen.spec.whatwg.org/
  113. * @see https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode
  114. */
  115. export const enterFullscreen = element => {
  116. element = element || document.documentElement;
  117. // Check which implementation is available
  118. let requestMethod = element.requestFullscreen ||
  119. element.webkitRequestFullscreen ||
  120. element.webkitRequestFullScreen ||
  121. element.mozRequestFullScreen ||
  122. element.msRequestFullscreen;
  123. if( requestMethod ) {
  124. requestMethod.apply( element );
  125. }
  126. }
  127. /**
  128. * Creates an HTML element and returns a reference to it.
  129. * If the element already exists the existing instance will
  130. * be returned.
  131. *
  132. * @param {HTMLElement} container
  133. * @param {string} tagname
  134. * @param {string} classname
  135. * @param {string} innerHTML
  136. *
  137. * @return {HTMLElement}
  138. */
  139. export const createSingletonNode = ( container, tagname, classname, innerHTML='' ) => {
  140. // Find all nodes matching the description
  141. let nodes = container.querySelectorAll( '.' + classname );
  142. // Check all matches to find one which is a direct child of
  143. // the specified container
  144. for( let i = 0; i < nodes.length; i++ ) {
  145. let testNode = nodes[i];
  146. if( testNode.parentNode === container ) {
  147. return testNode;
  148. }
  149. }
  150. // If no node was found, create it now
  151. let node = document.createElement( tagname );
  152. node.className = classname;
  153. node.innerHTML = innerHTML;
  154. container.appendChild( node );
  155. return node;
  156. }
  157. /**
  158. * Injects the given CSS styles into the DOM.
  159. *
  160. * @param {string} value
  161. */
  162. export const createStyleSheet = ( value ) => {
  163. let tag = document.createElement( 'style' );
  164. tag.type = 'text/css';
  165. if( value && value.length > 0 ) {
  166. if( tag.styleSheet ) {
  167. tag.styleSheet.cssText = value;
  168. }
  169. else {
  170. tag.appendChild( document.createTextNode( value ) );
  171. }
  172. }
  173. document.head.appendChild( tag );
  174. return tag;
  175. }
  176. /**
  177. * Returns a key:value hash of all query params.
  178. */
  179. export const getQueryHash = () => {
  180. let query = {};
  181. location.search.replace( /[A-Z0-9]+?=([\w\.%-]*)/gi, a => {
  182. query[ a.split( '=' ).shift() ] = a.split( '=' ).pop();
  183. } );
  184. // Basic deserialization
  185. for( let i in query ) {
  186. let value = query[ i ];
  187. query[ i ] = deserialize( unescape( value ) );
  188. }
  189. // Do not accept new dependencies via query config to avoid
  190. // the potential of malicious script injection
  191. if( typeof query['dependencies'] !== 'undefined' ) delete query['dependencies'];
  192. return query;
  193. }
  194. /**
  195. * Returns the remaining height within the parent of the
  196. * target element.
  197. *
  198. * remaining height = [ configured parent height ] - [ current parent height ]
  199. *
  200. * @param {HTMLElement} element
  201. * @param {number} [height]
  202. */
  203. export const getRemainingHeight = ( element, height = 0 ) => {
  204. if( element ) {
  205. let newHeight, oldHeight = element.style.height;
  206. // Change the .stretch element height to 0 in order find the height of all
  207. // the other elements
  208. element.style.height = '0px';
  209. // In Overview mode, the parent (.slide) height is set of 700px.
  210. // Restore it temporarily to its natural height.
  211. element.parentNode.style.height = 'auto';
  212. newHeight = height - element.parentNode.offsetHeight;
  213. // Restore the old height, just in case
  214. element.style.height = oldHeight + 'px';
  215. // Clear the parent (.slide) height. .removeProperty works in IE9+
  216. element.parentNode.style.removeProperty('height');
  217. return newHeight;
  218. }
  219. return height;
  220. }