Guest User

managing-state-css-reusable-javascript-functions-part-2

a guest
May 31st, 2017
190
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. ;(function () {
  2. // SWIPE DETECT HELPER
  3. // ----------------------------------------------
  4.  
  5. // http://www.javascriptkit.com/javatutors/touchevents2.shtml
  6. var swipeDetect = function (el, callback) {
  7. var touchsurface = el,
  8. swipedir,
  9. startX,
  10. startY,
  11. distX,
  12. distY,
  13. threshold = 100, // required min distance traveled to be considered swipe
  14. restraint = 100, // maximum distance allowed at the same time in perpendicular direction
  15. allowedTime = 300, // maximum time allowed to travel that distance
  16. elapsedTime,
  17. startTime,
  18. eventObj,
  19. handleswipe = callback || function (swipedir, eventObj) {}
  20.  
  21. touchsurface.addEventListener('touchstart', function (e) {
  22. var touchobj = e.changedTouches[0]
  23. swipedir = 'none'
  24. startX = touchobj.pageX
  25. startY = touchobj.pageY
  26. startTime = Date.now() // record time when finger first makes contact with surface
  27. eventObj = e
  28. }, false)
  29.  
  30. touchsurface.addEventListener('touchend', function (e) {
  31. var touchobj = e.changedTouches[0]
  32. distX = touchobj.pageX - startX // get horizontal dist traveled by finger while in contact with surface
  33. distY = touchobj.pageY - startY // get vertical dist traveled by finger while in contact with surface
  34. elapsedTime = Date.now() - startTime // get time elapsed
  35.  
  36. if (elapsedTime <= allowedTime) {
  37. // first condition for awipe met
  38. if (Math.abs(distX) >= threshold && Math.abs(distY) <= restraint) {
  39. // 2nd condition for horizontal swipe met
  40. swipedir = distX < 0 ? 'left' : 'right' // if dist traveled is negative, it indicates left swipe
  41. } else if (Math.abs(distY) >= threshold && Math.abs(distX) <= restraint) {
  42. // 2nd condition for vertical swipe met
  43. swipedir = distY < 0 ? 'up' : 'down' // if dist traveled is negative, it indicates up swipe
  44. }
  45. }
  46.  
  47. handleswipe(swipedir, eventObj)
  48. }, false)
  49. }
  50.  
  51. // CLOSEST PARENT HELPER FUNCTION
  52. // ----------------------------------------------
  53.  
  54. var closestParent = function (child, match) {
  55. if (!child || child === document) return null
  56. if (child.classList.contains(match) || child.nodeName.toLowerCase() === match) return child
  57. return closestParent(child.parentNode, match)
  58. }
  59.  
  60. // REUSABLE FUNCTION
  61. // ----------------------------------------------
  62.  
  63. // Change function
  64. var processChange = function (elem) {
  65. // Grab data-state list and convert to array
  66. var dataState = elem.getAttribute('data-state')
  67. dataState = dataState.split(', ')
  68.  
  69. // Grab data-state-behaviour list if present and convert to array
  70. if (elem.getAttribute('data-state-behaviour')) {
  71. var dataStateBehaviour = elem.getAttribute('data-state-behaviour')
  72. dataStateBehaviour = dataStateBehaviour.split(', ')
  73. }
  74.  
  75. // Grab data-scope list if present and convert to array
  76. if (elem.getAttribute('data-state-scope')) {
  77. var dataStateScope = elem.getAttribute('data-state-scope')
  78. dataStateScope = dataStateScope.split(', ')
  79. }
  80.  
  81. // Grab data-state-element list and convert to array
  82. // If data-state-element isn't found, pass self, set scope to self if none is present, essentially replicating "this"
  83. var dataStateElement
  84. if (elem.getAttribute('data-state-element')) {
  85. dataStateElement = elem.getAttribute('data-state-element')
  86. dataStateElement = dataStateElement.split(', ')
  87. } else {
  88. dataStateElement = []
  89. dataStateElement.push(elem.classList[0])
  90. if (!dataStateScope) dataStateScope = dataStateElement
  91. }
  92.  
  93. // Find out which has the biggest length between states and elements and use that length as loop number
  94. // This is to make sure situations where we have one data-state-element value and many data-state values are correctly setup
  95. var dataLength = Math.max(dataStateElement.length, dataState.length)
  96.  
  97. // Loop
  98. for (var b = 0; b < dataLength; b++) {
  99. // If a data-state-element value isn't found, use last valid one
  100. if (dataStateElement[b] !== undefined) var dataStateElementValue = dataStateElement[b]
  101.  
  102. // If scope isn't found, use last valid one
  103. if (dataStateScope && dataStateScope[b] !== undefined) var cachedScope = dataStateScope[b]
  104. else if (cachedScope) dataStateScope[b] = cachedScope
  105.  
  106. // Grab elem references, apply scope if found
  107. var elemRef
  108. if (dataStateScope && dataStateScope[b] !== 'false') {
  109. // Grab parent
  110. var elemParent = closestParent(elem, dataStateScope[b])
  111.  
  112. // Grab all matching child elements of parent
  113. elemRef = elemParent.querySelectorAll('.' + dataStateElementValue)
  114.  
  115. // Convert to array
  116. elemRef = Array.prototype.slice.call(elemRef)
  117.  
  118. // Add parent if it matches the data-state-element and fits within scope
  119. if (elemParent.classList.contains(dataStateElementValue)) elemRef.unshift(elemParent)
  120. }
  121. else elemRef = document.querySelectorAll('.' + dataStateElementValue)
  122.  
  123. // Grab state we will add
  124. // If one isn't found, keep last valid one
  125. if (dataState[b] !== undefined) var elemState = dataState[b]
  126.  
  127. // Grab behaviour if any exists
  128. // If one isn't found, keep last valid one
  129. if (dataStateBehaviour && dataStateBehaviour[b] !== undefined) var elemBehaviour = dataStateBehaviour[b]
  130.  
  131. // Do
  132. for (var c = 0; c < elemRef.length; c++) {
  133. // Find out if we're manipulating aria-attributes or classes
  134. var toggleAttr
  135. if (elemRef[c].getAttribute(elemState)) toggleAttr = true
  136. else toggleAttr = false
  137.  
  138. if (elemBehaviour === 'add') {
  139. if (toggleAttr) elemRef[c].setAttribute(elemState, true)
  140. else elemRef[c].classList.add(elemState)
  141. }
  142. else if (elemBehaviour === 'remove') {
  143. if (toggleAttr) elemRef[c].setAttribute(elemState, false)
  144. else elemRef[c].classList.remove(elemState)
  145. }
  146. else if (toggleAttr) {
  147. if (elemRef[c].getAttribute(elemState) === 'true') elemRef[c].setAttribute(elemState, false)
  148. else elemRef[c].setAttribute(elemState, true)
  149. }
  150. else elemRef[c].classList.toggle(elemState)
  151. }
  152. }
  153. }
  154. // Init function
  155. var initDataState = function (elem) {
  156. // Detect data-swipe attribute before we do anything, as it's optional
  157. if (elem.getAttribute('data-state-swipe')) {
  158. // Grab swipe specific data from data-state-swipe
  159. var elemSwipe = elem.getAttribute('data-state-swipe').split(', '),
  160. direction = elemSwipe[0],
  161. elemSwipeBool = elemSwipe[1],
  162. currentElem = elem
  163.  
  164. // If the behaviour flag is set to "false", or not set at all, then assign our click event
  165. if (elemSwipeBool === 'false' || !elemSwipeBool) {
  166. // Assign click event
  167. elem.addEventListener('click', function (e) {
  168. // Prevent default action of element
  169. e.preventDefault()
  170. // Run state function
  171. processChange(this)
  172. })
  173. }
  174. // Use our swipeDetect helper function to determine if the swipe direction matches our desired direction
  175. swipeDetect(elem, function (swipedir) {
  176. // Run state function
  177. if (swipedir === direction) processChange(currentElem)
  178. })
  179. } else {
  180. // Assign click event
  181. elem.addEventListener('click', function (e) {
  182. // Prevent default action of element
  183. e.preventDefault()
  184. // Run state function
  185. processChange(this)
  186. })
  187. }
  188. // Add keyboard event for enter key or space to mimic anchor functionality
  189. elem.addEventListener('keypress', function (e) {
  190. if (e.which !== 13 && e.which !== 32) return
  191. // Prevent default action of element
  192. e.preventDefault()
  193. // Run state function
  194. processChange(this)
  195. })
  196. }
  197.  
  198. // Run when DOM has finished loading
  199. document.addEventListener('DOMContentLoaded', function () {
  200. // Grab all elements with required attributes
  201. var elems = document.querySelectorAll('[data-state]')
  202.  
  203. // Loop through our matches and add click events
  204. for (var a = 0; a < elems.length; a++) initDataState(elems[a])
  205.  
  206. // Setup mutation observer to track changes for matching elements added after initial DOM render
  207. var observer = new MutationObserver(function (mutations) {
  208. mutations.forEach(function (mutation) {
  209. for (var d = 0; d < mutation.addedNodes.length; d++) {
  210. // Check if we're dealing with an element node
  211. if (typeof mutation.addedNodes[d].getAttribute === 'function' && mutation.addedNodes[d].getAttribute('data-state')) {
  212. initDataState(mutation.addedNodes[d])
  213. }
  214. }
  215. })
  216. })
  217.  
  218. // Define type of change our observer will watch out for
  219. observer.observe(document.body, {
  220. childList: true,
  221. subtree: true
  222. })
  223. })
  224. })()
RAW Paste Data