1
0

util.coffee 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. #
  2. # Traversing
  3. #
  4. @$ = (selector, el = document) ->
  5. try el.querySelector(selector) catch
  6. @$$ = (selector, el = document) ->
  7. try el.querySelectorAll(selector) catch
  8. $.id = (id) ->
  9. document.getElementById(id)
  10. $.hasChild = (parent, el) ->
  11. return unless parent
  12. while el
  13. return true if el is parent
  14. return if el is document.body
  15. el = el.parentElement
  16. $.closestLink = (el, parent = document.body) ->
  17. while el
  18. return el if el.tagName is 'A'
  19. return if el is parent
  20. el = el.parentElement
  21. #
  22. # Events
  23. #
  24. $.on = (el, event, callback, useCapture = false) ->
  25. if event.indexOf(' ') >= 0
  26. $.on el, name, callback for name in event.split(' ')
  27. else
  28. el.addEventListener(event, callback, useCapture)
  29. return
  30. $.off = (el, event, callback, useCapture = false) ->
  31. if event.indexOf(' ') >= 0
  32. $.off el, name, callback for name in event.split(' ')
  33. else
  34. el.removeEventListener(event, callback, useCapture)
  35. return
  36. $.trigger = (el, type, canBubble = true, cancelable = true) ->
  37. event = document.createEvent 'Event'
  38. event.initEvent(type, canBubble, cancelable)
  39. el.dispatchEvent(event)
  40. return
  41. $.click = (el) ->
  42. event = document.createEvent 'MouseEvent'
  43. event.initMouseEvent 'click', true, true, window, null, 0, 0, 0, 0, false, false, false, false, 0, null
  44. el.dispatchEvent(event)
  45. return
  46. $.stopEvent = (event) ->
  47. event.preventDefault()
  48. event.stopPropagation()
  49. event.stopImmediatePropagation()
  50. return
  51. #
  52. # Manipulation
  53. #
  54. buildFragment = (value) ->
  55. fragment = document.createDocumentFragment()
  56. if $.isCollection(value)
  57. fragment.appendChild(child) for child in $.makeArray(value)
  58. else
  59. fragment.innerHTML = value
  60. fragment
  61. $.append = (el, value) ->
  62. if typeof value is 'string'
  63. el.insertAdjacentHTML 'beforeend', value
  64. else
  65. value = buildFragment(value) if $.isCollection(value)
  66. el.appendChild(value)
  67. return
  68. $.prepend = (el, value) ->
  69. if not el.firstChild
  70. $.append(value)
  71. else if typeof value is 'string'
  72. el.insertAdjacentHTML 'afterbegin', value
  73. else
  74. value = buildFragment(value) if $.isCollection(value)
  75. el.insertBefore(value, el.firstChild)
  76. return
  77. $.before = (el, value) ->
  78. if typeof value is 'string' or $.isCollection(value)
  79. value = buildFragment(value)
  80. el.parentElement.insertBefore(value, el)
  81. return
  82. $.after = (el, value) ->
  83. if typeof value is 'string' or $.isCollection(value)
  84. value = buildFragment(value)
  85. if el.nextSibling
  86. el.parentElement.insertBefore(value, el.nextSibling)
  87. else
  88. el.parentElement.appendChild(value)
  89. return
  90. $.remove = (value) ->
  91. if $.isCollection(value)
  92. el.parentElement.removeChild(el) for el in $.makeArray(value)
  93. else
  94. value.parentElement.removeChild(value)
  95. return
  96. $.empty = (el) ->
  97. el.removeChild(el.firstChild) while el.firstChild
  98. return
  99. # Calls the function while the element is off the DOM to avoid triggering
  100. # unecessary reflows and repaints.
  101. $.batchUpdate = (el, fn) ->
  102. parent = el.parentNode
  103. sibling = el.nextSibling
  104. parent.removeChild(el)
  105. fn(el)
  106. if (sibling)
  107. parent.insertBefore(el, sibling)
  108. else
  109. parent.appendChild(el)
  110. return
  111. #
  112. # Offset
  113. #
  114. $.rect = (el) ->
  115. el.getBoundingClientRect()
  116. $.offset = (el, container = document.body) ->
  117. top = 0
  118. left = 0
  119. while el and el isnt container
  120. top += el.offsetTop
  121. left += el.offsetLeft
  122. el = el.offsetParent
  123. top: top
  124. left: left
  125. $.scrollParent = (el) ->
  126. while el = el.parentElement
  127. break if el.scrollTop > 0
  128. break if getComputedStyle(el).overflowY in ['auto', 'scroll']
  129. el
  130. $.scrollTo = (el, parent, position = 'center', options = {}) ->
  131. return unless el
  132. parent ?= $.scrollParent(el)
  133. return unless parent
  134. parentHeight = parent.clientHeight
  135. return unless parent.scrollHeight > parentHeight
  136. top = $.offset(el, parent).top
  137. switch position
  138. when 'top'
  139. parent.scrollTop = top - (if options.margin? then options.margin else 20)
  140. when 'center'
  141. parent.scrollTop = top - Math.round(parentHeight / 2 - el.offsetHeight / 2)
  142. when 'continuous'
  143. scrollTop = parent.scrollTop
  144. height = el.offsetHeight
  145. # If the target element is above the visible portion of its scrollable
  146. # ancestor, move it near the top with a gap = options.topGap * target's height.
  147. if top <= scrollTop + height * (options.topGap or 1)
  148. parent.scrollTop = top - height * (options.topGap or 1)
  149. # If the target element is below the visible portion of its scrollable
  150. # ancestor, move it near the bottom with a gap = options.bottomGap * target's height.
  151. else if top >= scrollTop + parentHeight - height * ((options.bottomGap or 1) + 1)
  152. parent.scrollTop = top - parentHeight + height * ((options.bottomGap or 1) + 1)
  153. return
  154. $.scrollToWithImageLock = (el, parent, args...) ->
  155. parent ?= $.scrollParent(el)
  156. return unless parent
  157. $.scrollTo el, parent, args...
  158. # Lock the scroll position on the target element for up to 3 seconds while
  159. # nearby images are loaded and rendered.
  160. for image in parent.getElementsByTagName('img') when not image.complete
  161. do ->
  162. onLoad = (event) ->
  163. clearTimeout(timeout)
  164. unbind(event.target)
  165. $.scrollTo el, parent, args...
  166. unbind = (target) ->
  167. $.off target, 'load', onLoad
  168. $.on image, 'load', onLoad
  169. timeout = setTimeout unbind.bind(null, image), 3000
  170. return
  171. # Calls the function while locking the element's position relative to the window.
  172. $.lockScroll = (el, fn) ->
  173. if parent = $.scrollParent(el)
  174. top = $.rect(el).top
  175. top -= $.rect(parent).top unless parent in [document.body, document.documentElement]
  176. fn()
  177. parent.scrollTop = $.offset(el, parent).top - top
  178. else
  179. fn()
  180. return
  181. #
  182. # Utilities
  183. #
  184. $.extend = (target, objects...) ->
  185. for object in objects when object
  186. for key, value of object
  187. target[key] = value
  188. target
  189. $.makeArray = (object) ->
  190. if Array.isArray(object)
  191. object
  192. else
  193. Array::slice.apply(object)
  194. # Returns true if the object is an array or a collection of DOM elements.
  195. $.isCollection = (object) ->
  196. Array.isArray(object) or typeof object?.item is 'function'
  197. ESCAPE_HTML_MAP =
  198. '&': '&amp;'
  199. '<': '&lt;'
  200. '>': '&gt;'
  201. '"': '&quot;'
  202. "'": '&#x27;'
  203. '/': '&#x2F;'
  204. ESCAPE_HTML_REGEXP = /[&<>"'\/]/g
  205. $.escape = (string) ->
  206. string.replace ESCAPE_HTML_REGEXP, (match) -> ESCAPE_HTML_MAP[match]
  207. ESCAPE_REGEXP = /([.*+?^=!:${}()|\[\]\/\\])/g
  208. $.escapeRegexp = (string) ->
  209. string.replace ESCAPE_REGEXP, "\\$1"
  210. $.urlDecode = (string) ->
  211. decodeURIComponent string.replace(/\+/g, '%20')
  212. #
  213. # Miscellaneous
  214. #
  215. $.noop = ->
  216. $.popup = (value) ->
  217. open value.href or value, '_blank'
  218. return
  219. $.isTouchScreen = ->
  220. typeof ontouchstart isnt 'undefined'
  221. $.isWindows = ->
  222. navigator.platform?.indexOf('Win') >= 0
  223. $.isMac = ->
  224. navigator.userAgent?.indexOf('Mac') >= 0
  225. HIGHLIGHT_DEFAULTS =
  226. className: 'highlight'
  227. delay: 1000
  228. $.highlight = (el, options = {}) ->
  229. options = $.extend {}, HIGHLIGHT_DEFAULTS, options
  230. el.classList.add(options.className)
  231. setTimeout (-> el.classList.remove(options.className)), options.delay
  232. return
  233. $.copyToClipboard = (string) ->
  234. textarea = document.createElement('textarea')
  235. textarea.style.position = 'fixed'
  236. textarea.style.opacity = 0
  237. textarea.value = string
  238. document.body.appendChild(textarea)
  239. try
  240. textarea.select()
  241. result = !!document.execCommand('copy')
  242. catch
  243. result = false
  244. finally
  245. document.body.removeChild(textarea)
  246. result