mirror of https://github.com/freeCodeCamp/devdocs
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
369 lines
8.8 KiB
369 lines
8.8 KiB
#
|
|
# Traversing
|
|
#
|
|
|
|
@$ = (selector, el = document) ->
|
|
try el.querySelector(selector) catch
|
|
|
|
@$$ = (selector, el = document) ->
|
|
try el.querySelectorAll(selector) catch
|
|
|
|
$.id = (id) ->
|
|
document.getElementById(id)
|
|
|
|
$.hasChild = (parent, el) ->
|
|
return unless parent
|
|
while el
|
|
return true if el is parent
|
|
return if el is document.body
|
|
el = el.parentElement
|
|
|
|
$.closestLink = (el, parent = document.body) ->
|
|
while el
|
|
return el if el.tagName is 'A'
|
|
return if el is parent
|
|
el = el.parentElement
|
|
|
|
#
|
|
# Events
|
|
#
|
|
|
|
$.on = (el, event, callback, useCapture = false) ->
|
|
if event.indexOf(' ') >= 0
|
|
$.on el, name, callback for name in event.split(' ')
|
|
else
|
|
el.addEventListener(event, callback, useCapture)
|
|
return
|
|
|
|
$.off = (el, event, callback, useCapture = false) ->
|
|
if event.indexOf(' ') >= 0
|
|
$.off el, name, callback for name in event.split(' ')
|
|
else
|
|
el.removeEventListener(event, callback, useCapture)
|
|
return
|
|
|
|
$.trigger = (el, type, canBubble = true, cancelable = true) ->
|
|
event = document.createEvent 'Event'
|
|
event.initEvent(type, canBubble, cancelable)
|
|
el.dispatchEvent(event)
|
|
return
|
|
|
|
$.click = (el) ->
|
|
event = document.createEvent 'MouseEvent'
|
|
event.initMouseEvent 'click', true, true, window, null, 0, 0, 0, 0, false, false, false, false, 0, null
|
|
el.dispatchEvent(event)
|
|
return
|
|
|
|
$.stopEvent = (event) ->
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
event.stopImmediatePropagation()
|
|
return
|
|
|
|
#
|
|
# Manipulation
|
|
#
|
|
|
|
buildFragment = (value) ->
|
|
fragment = document.createDocumentFragment()
|
|
|
|
if $.isCollection(value)
|
|
fragment.appendChild(child) for child in $.makeArray(value)
|
|
else
|
|
fragment.innerHTML = value
|
|
|
|
fragment
|
|
|
|
$.append = (el, value) ->
|
|
if typeof value is 'string'
|
|
el.insertAdjacentHTML 'beforeend', value
|
|
else
|
|
value = buildFragment(value) if $.isCollection(value)
|
|
el.appendChild(value)
|
|
return
|
|
|
|
$.prepend = (el, value) ->
|
|
if not el.firstChild
|
|
$.append(value)
|
|
else if typeof value is 'string'
|
|
el.insertAdjacentHTML 'afterbegin', value
|
|
else
|
|
value = buildFragment(value) if $.isCollection(value)
|
|
el.insertBefore(value, el.firstChild)
|
|
return
|
|
|
|
$.before = (el, value) ->
|
|
if typeof value is 'string' or $.isCollection(value)
|
|
value = buildFragment(value)
|
|
|
|
el.parentElement.insertBefore(value, el)
|
|
return
|
|
|
|
$.after = (el, value) ->
|
|
if typeof value is 'string' or $.isCollection(value)
|
|
value = buildFragment(value)
|
|
|
|
if el.nextSibling
|
|
el.parentElement.insertBefore(value, el.nextSibling)
|
|
else
|
|
el.parentElement.appendChild(value)
|
|
return
|
|
|
|
$.remove = (value) ->
|
|
if $.isCollection(value)
|
|
el.parentElement?.removeChild(el) for el in $.makeArray(value)
|
|
else
|
|
value.parentElement?.removeChild(value)
|
|
return
|
|
|
|
$.empty = (el) ->
|
|
el.removeChild(el.firstChild) while el.firstChild
|
|
return
|
|
|
|
# Calls the function while the element is off the DOM to avoid triggering
|
|
# unecessary reflows and repaints.
|
|
$.batchUpdate = (el, fn) ->
|
|
parent = el.parentNode
|
|
sibling = el.nextSibling
|
|
parent.removeChild(el)
|
|
|
|
fn(el)
|
|
|
|
if (sibling)
|
|
parent.insertBefore(el, sibling)
|
|
else
|
|
parent.appendChild(el)
|
|
return
|
|
|
|
#
|
|
# Offset
|
|
#
|
|
|
|
$.rect = (el) ->
|
|
el.getBoundingClientRect()
|
|
|
|
$.offset = (el, container = document.body) ->
|
|
top = 0
|
|
left = 0
|
|
|
|
while el and el isnt container
|
|
top += el.offsetTop
|
|
left += el.offsetLeft
|
|
el = el.offsetParent
|
|
|
|
top: top
|
|
left: left
|
|
|
|
$.scrollParent = (el) ->
|
|
while el = el.parentElement
|
|
break if el.scrollTop > 0
|
|
break if getComputedStyle(el)?.overflowY in ['auto', 'scroll']
|
|
el
|
|
|
|
$.scrollTo = (el, parent, position = 'center', options = {}) ->
|
|
return unless el
|
|
|
|
parent ?= $.scrollParent(el)
|
|
return unless parent
|
|
|
|
parentHeight = parent.clientHeight
|
|
return unless parent.scrollHeight > parentHeight
|
|
|
|
top = $.offset(el, parent).top
|
|
|
|
switch position
|
|
when 'top'
|
|
parent.scrollTop = top - (if options.margin? then options.margin else 20)
|
|
when 'center'
|
|
parent.scrollTop = top - Math.round(parentHeight / 2 - el.offsetHeight / 2)
|
|
when 'continuous'
|
|
scrollTop = parent.scrollTop
|
|
height = el.offsetHeight
|
|
|
|
# If the target element is above the visible portion of its scrollable
|
|
# ancestor, move it near the top with a gap = options.topGap * target's height.
|
|
if top <= scrollTop + height * (options.topGap or 1)
|
|
parent.scrollTop = top - height * (options.topGap or 1)
|
|
# If the target element is below the visible portion of its scrollable
|
|
# ancestor, move it near the bottom with a gap = options.bottomGap * target's height.
|
|
else if top >= scrollTop + parentHeight - height * ((options.bottomGap or 1) + 1)
|
|
parent.scrollTop = top - parentHeight + height * ((options.bottomGap or 1) + 1)
|
|
return
|
|
|
|
$.scrollToWithImageLock = (el, parent, args...) ->
|
|
parent ?= $.scrollParent(el)
|
|
return unless parent
|
|
|
|
$.scrollTo el, parent, args...
|
|
|
|
# Lock the scroll position on the target element for up to 3 seconds while
|
|
# nearby images are loaded and rendered.
|
|
for image in parent.getElementsByTagName('img') when not image.complete
|
|
do ->
|
|
onLoad = (event) ->
|
|
clearTimeout(timeout)
|
|
unbind(event.target)
|
|
$.scrollTo el, parent, args...
|
|
|
|
unbind = (target) ->
|
|
$.off target, 'load', onLoad
|
|
|
|
$.on image, 'load', onLoad
|
|
timeout = setTimeout unbind.bind(null, image), 3000
|
|
return
|
|
|
|
# Calls the function while locking the element's position relative to the window.
|
|
$.lockScroll = (el, fn) ->
|
|
if parent = $.scrollParent(el)
|
|
top = $.rect(el).top
|
|
top -= $.rect(parent).top unless parent in [document.body, document.documentElement]
|
|
fn()
|
|
parent.scrollTop = $.offset(el, parent).top - top
|
|
else
|
|
fn()
|
|
return
|
|
|
|
smoothScroll = smoothStart = smoothEnd = smoothDistance = smoothDuration = null
|
|
|
|
$.smoothScroll = (el, end) ->
|
|
unless window.requestAnimationFrame
|
|
el.scrollTop = end
|
|
return
|
|
|
|
smoothEnd = end
|
|
|
|
if smoothScroll
|
|
newDistance = smoothEnd - smoothStart
|
|
smoothDuration += Math.min 300, Math.abs(smoothDistance - newDistance)
|
|
smoothDistance = newDistance
|
|
return
|
|
|
|
smoothStart = el.scrollTop
|
|
smoothDistance = smoothEnd - smoothStart
|
|
smoothDuration = Math.min 300, Math.abs(smoothDistance)
|
|
startTime = Date.now()
|
|
|
|
smoothScroll = ->
|
|
p = Math.min 1, (Date.now() - startTime) / smoothDuration
|
|
y = Math.max 0, Math.floor(smoothStart + smoothDistance * (if p < 0.5 then 2 * p * p else p * (4 - p * 2) - 1))
|
|
el.scrollTop = y
|
|
if p is 1
|
|
smoothScroll = null
|
|
else
|
|
requestAnimationFrame(smoothScroll)
|
|
requestAnimationFrame(smoothScroll)
|
|
|
|
#
|
|
# Utilities
|
|
#
|
|
|
|
$.extend = (target, objects...) ->
|
|
for object in objects when object
|
|
for key, value of object
|
|
target[key] = value
|
|
target
|
|
|
|
$.makeArray = (object) ->
|
|
if Array.isArray(object)
|
|
object
|
|
else
|
|
Array::slice.apply(object)
|
|
|
|
$.arrayDelete = (array, object) ->
|
|
index = array.indexOf(object)
|
|
if index >= 0
|
|
array.splice(index, 1)
|
|
true
|
|
else
|
|
false
|
|
|
|
# Returns true if the object is an array or a collection of DOM elements.
|
|
$.isCollection = (object) ->
|
|
Array.isArray(object) or typeof object?.item is 'function'
|
|
|
|
ESCAPE_HTML_MAP =
|
|
'&': '&'
|
|
'<': '<'
|
|
'>': '>'
|
|
'"': '"'
|
|
"'": '''
|
|
'/': '/'
|
|
|
|
ESCAPE_HTML_REGEXP = /[&<>"'\/]/g
|
|
|
|
$.escape = (string) ->
|
|
string.replace ESCAPE_HTML_REGEXP, (match) -> ESCAPE_HTML_MAP[match]
|
|
|
|
ESCAPE_REGEXP = /([.*+?^=!:${}()|\[\]\/\\])/g
|
|
|
|
$.escapeRegexp = (string) ->
|
|
string.replace ESCAPE_REGEXP, "\\$1"
|
|
|
|
$.urlDecode = (string) ->
|
|
decodeURIComponent string.replace(/\+/g, '%20')
|
|
|
|
$.classify = (string) ->
|
|
string = string.split('_')
|
|
for substr, i in string
|
|
string[i] = substr[0].toUpperCase() + substr[1..]
|
|
string.join('')
|
|
|
|
$.framify = (fn, obj) ->
|
|
if window.requestAnimationFrame
|
|
(args...) -> requestAnimationFrame(fn.bind(obj, args...))
|
|
else
|
|
fn
|
|
|
|
$.requestAnimationFrame = (fn) ->
|
|
if window.requestAnimationFrame
|
|
requestAnimationFrame(fn)
|
|
else
|
|
setTimeout(fn, 0)
|
|
return
|
|
|
|
#
|
|
# Miscellaneous
|
|
#
|
|
|
|
$.noop = ->
|
|
|
|
$.popup = (value) ->
|
|
win = window.open()
|
|
if win
|
|
win.opener = null if win.opener
|
|
win.location = value.href or value
|
|
else
|
|
window.open value.href or value, '_blank'
|
|
return
|
|
|
|
$.isWindows = ->
|
|
navigator.platform?.indexOf('Win') >= 0
|
|
|
|
$.isMac = ->
|
|
navigator.userAgent?.indexOf('Mac') >= 0
|
|
|
|
HIGHLIGHT_DEFAULTS =
|
|
className: 'highlight'
|
|
delay: 1000
|
|
|
|
$.highlight = (el, options = {}) ->
|
|
options = $.extend {}, HIGHLIGHT_DEFAULTS, options
|
|
el.classList.add(options.className)
|
|
setTimeout (-> el.classList.remove(options.className)), options.delay
|
|
return
|
|
|
|
$.copyToClipboard = (string) ->
|
|
textarea = document.createElement('textarea')
|
|
textarea.style.position = 'fixed'
|
|
textarea.style.opacity = 0
|
|
textarea.value = string
|
|
document.body.appendChild(textarea)
|
|
try
|
|
textarea.select()
|
|
result = !!document.execCommand('copy')
|
|
catch
|
|
result = false
|
|
finally
|
|
document.body.removeChild(textarea)
|
|
result
|