UNPKG

kdf

Version:

KD: a non-document focused UI Framework for web applications.

574 lines (463 loc) 16.1 kB
Inflector = require './../../libs/inflector.js' module.exports = idCounter : 0 extend:(target, sources...)-> for source in sources target[key] = val for key, val of source return target dict: Object.create.bind null, null, (Object.create null) getNearestElementByTagName: (el, tagName) -> el = el.parentNode until not el? or @elementHasTag el, tagName return el elementShow: (el) -> el?.classList.remove "hidden" elementHide: (el) -> el?.classList.add "hidden" elementHasTag: (el, tagName) -> Boolean(el.tagName?.toLowerCase() is tagName.toLowerCase()) elementIsVisible: (el) -> return false if el.offsetWidth <= 0 or el.offsetHeight <= 0 height = document.documentElement.clientHeight rects = el.getClientRects() onTop = (r) -> x = (r.left + r.right) / 2 y = (r.top + r.bottom) / 2 document.elementFromPoint(x, y) is el i = 0 l = rects.length while i < l r = rects[i] inViewport = (if r.top > 0 then r.top <= height else (r.bottom > 0 and r.bottom <= height)) return true if inViewport and onTop(r) i++ return false formatPlural:(count, noun, showCount = yes)-> """ #{ if showCount then "#{count or 0} " else '' }#{ if count is 1 then noun else Inflector.pluralize noun } """ formatIndefiniteArticle: (noun) -> return "an #{noun}" if noun[0].toLowerCase() in ['a','e','i','o','u'] return "a #{noun}" getSelection:-> return window.getSelection() getSelectionRange:-> selection = @getSelection() return selection.getRangeAt 0 if selection.type isnt "None" getCursorNode:-> return @getSelectionRange().commonAncestorContainer addRange:(range)-> selection = window.getSelection() selection.removeAllRanges() selection.addRange range selectText:(element, start, end = start)-> if document.body.createTextRange range = document.body.createTextRange() range.moveToElementText element range.select() else if window.getSelection selection = window.getSelection() range = document.createRange() range.selectNodeContents element range.setStart element, start if start? range.setEnd element, end if end? selection.removeAllRanges() selection.addRange range selectEnd:(element, range)-> range or= document.createRange() element or= @getSelection().focusNode return unless element range.setStartAfter element range.collapse no @addRange range replaceRange:(node, replacement, start, end = start, appendTrailingSpace = yes)-> trailingSpace = document.createTextNode "\u00a0" range = new Range() if start? range.setStart node, start range.setEnd node, end else range.selectNode node range.deleteContents() range.insertNode replacement @selectEnd replacement, range if appendTrailingSpace range.insertNode trailingSpace @selectEnd trailingSpace, range getCallerChain:(args, depth)-> {callee:{caller}} = args chain = [caller] while depth-- and caller = caller?.caller chain.push caller chain createCounter: createCounter = (i = 0) -> -> i++ getUniqueId: do (inc = createCounter()) -> -> "kd-#{do inc}" getRandomNumber :(range=1e6, min=0)-> res = Math.floor Math.random()*range+1 return if res > min then res else res + min uniqueId : (prefix)-> id = @idCounter++ if prefix? then "#{prefix}#{id}" else id getRandomRGB :-> fn = @getRandomNumber return "rgb(#{fn 255},#{fn 255},#{fn 255})" getRandomHex : -> # hex = (Math.random()*0xFFFFFF<<0).toString(16) hex = (Math.random()*0x999999<<0).toString(16) while hex.length < 6 hex += "0" "##{hex}" curry:(obligatory, optional)-> obligatory + if optional then ' ' + optional else '' parseQuery:do-> params = /([^&=]+)=?([^&]*)/g # for chunking the key-val pairs plusses = /\+/g # for converting plus signs to spaces decode = (str)-> decodeURIComponent str.replace plusses, " " parseQuery = (queryString = location.search.substring 1)-> result = {} result[decode m[1]] = decode m[2] while m = params.exec queryString result stringifyQuery:do-> spaces = /\s/g encode =(str)-> encodeURIComponent str.replace spaces, "+" stringifyQuery = (obj)-> Object.keys(obj).map((key)-> "#{encode key}=#{encode obj[key]}").join('&').trim() capAndRemovePeriods:(path)-> newPath = for arg in path.split "." arg.capitalize() newPath.join "" slugify:(title = "")-> url = String(title) .toLowerCase() # change everything to lowercase .replace(/^\s+|\s+$/g, "") # trim leading and trailing spaces .replace(/[_|\s]+/g, "-") # change all spaces and underscores to a hyphen .replace(/[^a-z0-9-]+/g, "") # remove all non-alphanumeric characters except the hyphen .replace(/[-]+/g, "-") # replace multiple instances of the hyphen with a single instance .replace(/^-+|-+$/g, "") # trim leading and trailing hyphens stripTags:(value)-> value.replace /<(?:.|\n)*?>/gm, '' decimalToAnother:(n, radix) -> hex = [] for i in [0..10] hex[i+1] = i s = '' a = n while a >= radix b = a % radix a = Math.floor a / radix s += hex[b + 1] s += hex[a + 1] n = s.length t = '' for i in [0...n] t = t + s.substring n - i - 1, n - i s = t s enterFullscreen: do -> # Find the right method, call on correct element launchFullscreen = (element) -> if element.requestFullscreen then element.requestFullscreen() else if element.mozRequestFullScreen then element.mozRequestFullScreen() else if element.webkitRequestFullscreen then element.webkitRequestFullscreen() else if element.msRequestFullscreen then element.msRequestFullscreen() (element = document.documentElement) -> # Launch fullscreen for browsers that support it! launchFullscreen element exitFullscreen: -> if document.exitFullscreen then document.exitFullscreen() else if document.mozCancelFullScreen then document.mozCancelFullScreen() else if document.webkitExitFullscreen then document.webkitExitFullscreen() isFullscreen: -> return document.fullscreenElement or document.mozFullScreenElement or document.webkitIsFullScreen createExternalLink: (href) -> tag = document.createElement "a" tag.href = if href.indexOf("http") > -1 then href else "http://#{href}" tag.target = "_blank" document.body.appendChild tag tag.click() document.body.removeChild tag wait: (duration, fn)-> if "function" is typeof duration fn = duration duration = 0 return setTimeout fn, duration killWait:(id)-> clearTimeout id if id return null repeat: (duration, fn)-> if "function" is typeof duration fn = duration duration = 500 setInterval fn, duration killRepeat:(id)-> clearInterval id defer:do (queue = []) -> # this was ported from browserify's implementation of "process.nextTick" if window?.postMessage and window.addEventListener window.addEventListener "message", ((ev) -> if ev.source is window and ev.data is "kd-tick" ev.stopPropagation() do queue.shift() if queue.length > 0 ), yes (fn) -> queue.push fn; window.postMessage "kd-tick", "*" else (fn) -> setTimeout fn, 1 getCancellableCallback:(callback)-> cancelled = no kallback = (rest...)-> callback rest... unless cancelled kallback.cancel = -> cancelled = yes kallback # ~ GG # Returns a new callback which calls the failcallback if # first callback not finish its job in given timeout (default is 5000ms) # # Usage: # # Let assume that you have this: # # asyncFunc (data)-> # doSomethingWith data # # To set a timeout for it eg. 500ms: # # asyncFunc getTimedOutCallBack (data)-> # doSomethingWith data # , -> # console.log "asyncFunc is not responded in 500ms." # , 500 # getTimedOutCallback:(callback, failcallback, timeout=5000)-> cancelled = no kallback = (rest...)-> clearTimeout fallbackTimer callback rest... unless cancelled fallback = (rest...)-> failcallback rest... unless cancelled cancelled = yes fallbackTimer = setTimeout fallback, timeout kallback # Returns a new callback which calls the failcallback if # first callback doesn't finish its job within timeout. # # Also, keeps track of start and end times. # # Let's assume that you have this: # # asyncFunc (data)-> doSomethingWith data # # To set a timeout for 500ms: # # asyncFunc ,\ # KD.utils.getTimedOutCallbackOne # name :"asyncFunc" // optional, logs to KD.utils.timers # timeout : 500 // defaults to 5000 # onSucess : (data)-> # onTimeout: -> # onResult : -> // called when result comes after timeout getTimedOutCallbackOne: (options={})-> timerName = options.name or "undefined" timeout = options.timeout or 10000 onSuccess = options.onSuccess or -> onTimeout = options.onTimeout or -> onResult = options.onResult or -> timedOut = no kallback = (rest...)=> clearTimeout fallbackTimer @updateLogTimer timerName, fallbackTimer, Date.now() if timedOut then onResult rest... else onSuccess rest... fallback = (rest...)=> timedOut = yes @updateLogTimer timerName, fallbackTimer onTimeout rest... fallbackTimer = setTimeout fallback, timeout @logTimer timerName, fallbackTimer, Date.now() kallback.cancel =-> clearTimeout fallbackTimer kallback logTimer:(timerName, timerNumber, startTime)-> log "logTimer name:#{timerName}" @timers[timerName] ||= {} @timers[timerName][timerNumber] = start : startTime status : "started" updateLogTimer:(timerName, timerNumber, endTime)-> timer = @timers[timerName][timerNumber] status = if endTime then "ended" else "failed" startTime = timer.start elapsed = endTime-startTime timer = start : startTime end : endTime status : status elapsed : elapsed @timers[timerName][timerNumber] = timer log "updateLogTimer name:#{timerName}, status:#{status} elapsed:#{elapsed}" timers: {} stopDOMEvent :(event)-> return no unless event event.preventDefault() event.stopPropagation() return no utf8Encode:(string)-> string = string.replace(/\r\n/g, "\n") utftext = "" n = 0 while n < string.length c = string.charCodeAt(n) if c < 128 utftext += String.fromCharCode(c) else if (c > 127) and (c < 2048) utftext += String.fromCharCode((c >> 6) | 192) utftext += String.fromCharCode((c & 63) | 128) else utftext += String.fromCharCode((c >> 12) | 224) utftext += String.fromCharCode(((c >> 6) & 63) | 128) utftext += String.fromCharCode((c & 63) | 128) n++ utftext utf8Decode:(utftext)-> string = "" i = 0 c = c1 = c2 = 0 while i < utftext.length c = utftext.charCodeAt(i) if c < 128 string += String.fromCharCode(c) i++ else if (c > 191) and (c < 224) c2 = utftext.charCodeAt(i + 1) string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)) i += 2 else c2 = utftext.charCodeAt(i + 1) c3 = utftext.charCodeAt(i + 2) string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)) i += 3 string # Return true x% of time based on argument. # # Example: # runXpercent(10) => returns true 10% of the time runXpercent: (percent)-> chance = Math.floor(Math.random() * 100) chance <= percent shortenUrl: (url, callback)-> request = $.ajax "https://www.googleapis.com/urlshortener/v1/url", type : "POST" contentType : "application/json" data : JSON.stringify {longUrl: url} timeout : 4000 dataType : "json" request.done (data)=> callback data?.id or url, data request.error ({status, statusText, responseText})-> error "URL shorten error, returning self as fallback.", status, statusText, responseText callback url formatBytesToHumanReadable: (bytes, fixedAmout = 2) -> minus = '' if bytes < 0 minus = '-' bytes *= -1 thresh = 1024 units = ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] unitIndex = -1 return "#{bytes} B" if bytes < thresh loop bytes /= thresh ++unitIndex break unless bytes >= thresh return "#{minus}#{bytes.toFixed fixedAmout} #{units[unitIndex]}" splitTrim: (str, delim = ',', filterEmpty = yes) -> arr = (str?.split(delim).map (part) -> do part.trim) ? [] arr = arr.filter Boolean if filterEmpty return arr arrayToObject: (list, key) -> dict = {} dict[obj[key]] = obj for obj in list when obj[key]? dict # The partition function takes a list and predicate fn and returns the pair of lists # of elements which do and do not satisfy the predicate, respectively. # (stolen from CoffeeScriptRedux) partition: (list, fn) -> result = [[], []] result[+!fn item].push item for item in list result ### // Underscore.js 1.3.1 // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. // Underscore is freely distributable under the MIT license. // Portions of Underscore are inspired or borrowed from Prototype, // Oliver Steele's Functional, and John Resig's Micro-Templating. // For all details and documentation: // http://documentcloud.github.com/underscore ### throttle : (wait, func, options) -> [wait, func] = [func, wait] if (typeof func) is 'number' context = null args = null result = null timeout = null previous = 0 options ?= {} later = -> previous = (if options.leading is false then 0 else Date.now) timeout = null result = func.apply context, args context = args = null unless timeout -> now = Date.now previous = now if not previous and options.leading is false remaining = wait - (now - previous) context = this args = arguments if remaining <= 0 or remaining > wait if timeout clearTimeout timeout timeout = null previous = now result = func.apply context, args context = args = null unless timeout else if not timeout and options.trailing isnt false timeout = setTimeout later, remaining return result debounce : (wait, func, immediate) -> [wait, func] = [func, wait] if (typeof func) is 'number' timeout = null args = null context = null timestamp = null result = null later = -> last = Date.now - timestamp if last < wait and last >= 0 timeout = setTimeout(later, wait - last) else timeout = null unless immediate result = func.apply(context, args) context = args = null unless timeout -> context = this args = arguments timestamp = Date.now callNow = immediate and not timeout timeout ?= setTimeout later, wait if callNow result = func.apply(context, args) context = args = null return result relativeOffset: (child, parent) -> x = 0; y = 0 node = child while node x += node.offsetLeft y += node.offsetTop break if node is parent node = node.parentNode throw new Error "Not a descendant!" unless node? [x, y]