Last active
August 29, 2015 14:22
-
-
Save tigerhawkvok/557653994a6e2f5c4a5a to your computer and use it in GitHub Desktop.
Core Coffeescript helper functions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Set up basic URI parameters | |
# Uses | |
# https://github.com/allmarkedup/purl | |
try | |
uri = new Object() | |
uri.o = $.url() | |
uri.urlString = uri.o.attr('protocol') + '://' + uri.o.attr('host') + uri.o.attr("directory") | |
uri.query = uri.o.attr("fragment") | |
catch e | |
console.warn("PURL not installed!") | |
window.locationData = new Object() | |
locationData.params = | |
enableHighAccuracy: true | |
locationData.last = undefined | |
window.debounce_timer = null | |
isBool = (str,strict = false) -> | |
if strict | |
return typeof str is "boolean" | |
try | |
if typeof str is "boolean" | |
return str is true or str is false | |
if typeof str is "string" | |
return str.toLowerCase() is "true" or str.toLowerCase() is "false" | |
if typeof str is "number" | |
return str is 1 or str is 0 | |
false | |
catch e | |
return false | |
isEmpty = (str) -> not str or str.length is 0 | |
isBlank = (str) -> not str or /^\s*$/.test(str) | |
isNull = (str) -> | |
try | |
if isEmpty(str) or isBlank(str) or not str? | |
unless str is false or str is 0 then return true | |
catch e | |
return false | |
false | |
isJson = (str) -> | |
if typeof str is 'object' then return true | |
try | |
JSON.parse(str) | |
return true | |
catch e | |
return false | |
false | |
isNumber = (n) -> not isNaN(parseFloat(n)) and isFinite(n) | |
toFloat = (str) -> | |
if not isNumber(str) or isNull(str) then return 0 | |
parseFloat(str) | |
toInt = (str) -> | |
if not isNumber(str) or isNull(str) then return 0 | |
parseInt(str) | |
String::toBool = -> @toString() is 'true' | |
Boolean::toBool = -> @toString() is 'true' | |
Number::toBool = -> @toString() is "1" | |
String::addSlashes = -> | |
`this.replace(/[\\"']/g, '\\$&').replace(/\u0000/g, '\\0')` | |
Object.size = (obj) -> | |
if typeof obj isnt "object" | |
try | |
return obj.length | |
catch e | |
console.error("Passed argument isn't an object and doesn't have a .length parameter") | |
console.warn(e.message) | |
size = 0 | |
size++ for key of obj when obj.hasOwnProperty(key) | |
size | |
delay = (ms,f) -> setTimeout(f,ms) | |
roundNumber = (number,digits = 0) -> | |
multiple = 10 ** digits | |
Math.round(number * multiple) / multiple | |
roundNumberSigfig = (number, digits = 0) -> | |
newNumber = roundNumber(number, digits).toString() | |
digArr = newNumber.split(".") | |
if digArr.length is 1 | |
return "#{newNumber}.#{Array(digits + 1).join("0")}" | |
trailingDigits = digArr.pop() | |
significand = "#{digArr[0]}." | |
if trailingDigits.length is digits | |
return newNumber | |
needDigits = digits - trailingDigits.length | |
trailingDigits += Array(needDigits + 1).join("0") | |
"#{significand}#{trailingDigits}" | |
jsonTo64 = (obj) -> | |
if typeof obj is "array" | |
obj = toObject(arr) | |
objString = JSON.stringify(obj) | |
encodeURIComponent(encode64(objString)) | |
encode64 = (string) -> | |
try | |
Base64.encode(string) | |
catch e | |
console.warn("Bad encode string provided") | |
string | |
decode64 = (string) -> | |
try | |
Base64.decode(string) | |
catch e | |
console.warn("Bad decode string provided") | |
string | |
jQuery.fn.polymerSelected = (setSelected = undefined, attrLookup = "attrForSelected") -> | |
### | |
# See | |
# https://elements.polymer-project.org/elements/paper-menu | |
# https://elements.polymer-project.org/elements/paper-radio-group | |
# | |
# @param attrLookup is based on | |
# https://elements.polymer-project.org/elements/iron-selector?active=Polymer.IronSelectableBehavior | |
### | |
attr = $(this).attr(attrLookup) | |
if setSelected? | |
if not isBool(setSelected) | |
try | |
$(this).get(0).select(setSelected) | |
catch e | |
return false | |
else | |
$(this).parent().children().removeAttribute("aria-selected") | |
$(this).parent().children().removeAttribute("active") | |
$(this).parent().children().removeClass("iron-selected") | |
$(this).prop("selected",setSelected) | |
$(this).prop("active",setSelected) | |
$(this).prop("aria-selected",setSelected) | |
if setSelected is true | |
$(this).addClass("iron-selected") | |
else | |
val = undefined | |
try | |
val = $(this).get(0).selected | |
if isNumber(val) and not isNull(attr) | |
itemSelector = $(this).find("paper-item")[toInt(val)] | |
val = $(itemSelector).attr(attr) | |
catch e | |
return false | |
if val is "null" or not val? | |
val = undefined | |
val | |
jQuery.fn.polymerChecked = (setChecked = undefined) -> | |
# See | |
# https://www.polymer-project.org/docs/elements/paper-elements.html#paper-dropdown-menu | |
if setChecked? | |
jQuery(this).prop("checked",setChecked) | |
else | |
val = jQuery(this)[0].checked | |
if val is "null" or not val? | |
val = undefined | |
val | |
isHovered = (selector) -> | |
$("#{selector}:hover").length > 0 | |
jQuery.fn.exists = -> jQuery(this).length > 0 | |
jQuery.fn.isVisible = -> | |
jQuery(this).is(":visible") and jQuery(this).css("visibility") isnt "hidden" | |
jQuery.fn.hasChildren = -> | |
Object.size(jQuery(this).children()) > 3 | |
byteCount = (s) => encodeURI(s).split(/%..|./).length - 1 | |
`function shuffle(o) { //v1.0 | |
for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x); | |
return o; | |
}` | |
toObject = (array) -> | |
rv = new Object() | |
for index, element of array | |
if element isnt undefined then rv[index] = element | |
rv | |
loadJS = (src, callback = new Object(), doCallbackOnError = true) -> | |
### | |
# Load a new javascript file | |
# | |
# If it's already been loaded, jump straight to the callback | |
# | |
# @param string src The source URL of the file | |
# @param function callback Function to execute after the script has | |
# been loaded | |
# @param bool doCallbackOnError Should the callback be executed if | |
# loading the script produces an error? | |
### | |
if $("script[src='#{src}']").exists() | |
if typeof callback is "function" | |
try | |
callback() | |
catch e | |
console.error "Script is already loaded, but there was an error executing the callback function - #{e.message}" | |
# Whether or not there was a callback, end the script | |
return true | |
# Create a new DOM selement | |
s = document.createElement("script") | |
# Set all the attributes. We can be a bit redundant about this | |
s.setAttribute("src",src) | |
s.setAttribute("async","async") | |
s.setAttribute("type","text/javascript") | |
s.src = src | |
s.async = true | |
# Onload function | |
onLoadFunction = -> | |
state = s.readyState | |
try | |
if not callback.done and (not state or /loaded|complete/.test(state)) | |
callback.done = true | |
if typeof callback is "function" | |
try | |
callback() | |
catch e | |
console.error "Postload callback error - #{e.message}" | |
catch e | |
console.error "Onload error - #{e.message}" | |
# Error function | |
errorFunction = -> | |
console.warn "There may have been a problem loading #{src}" | |
try | |
unless callback.done | |
callback.done = true | |
if typeof callback is "function" and doCallbackOnError | |
try | |
callback() | |
catch e | |
console.error "Post error callback error - #{e.message}" | |
catch e | |
console.error "There was an error in the error handler! #{e.message}" | |
# Set the attributes | |
s.setAttribute("onload",onLoadFunction) | |
s.setAttribute("onreadystate",onLoadFunction) | |
s.setAttribute("onerror",errorFunction) | |
s.onload = s.onreadystate = onLoadFunction | |
s.onerror = errorFunction | |
document.getElementsByTagName('head')[0].appendChild(s) | |
true | |
String::toTitleCase = -> | |
# From http://stackoverflow.com/a/6475125/1877527 | |
str = | |
@replace /([^\W_]+[^\s-]*) */g, (txt) -> | |
txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase() | |
# Certain minor words should be left lowercase unless | |
# they are the first or last words in the string | |
lowers = [ | |
"A" | |
"An" | |
"The" | |
"And" | |
"But" | |
"Or" | |
"For" | |
"Nor" | |
"As" | |
"At" | |
"By" | |
"For" | |
"From" | |
"In" | |
"Into" | |
"Near" | |
"Of" | |
"On" | |
"Onto" | |
"To" | |
"With" | |
] | |
for lower in lowers | |
lowerRegEx = new RegExp("\\s#{lower}\\s","g") | |
str = str.replace lowerRegEx, (txt) -> txt.toLowerCase() | |
# Certain words such as initialisms or acronyms should be left | |
# uppercase | |
uppers = [ | |
"Id" | |
"Tv" | |
] | |
for upper in uppers | |
upperRegEx = new RegExp("\\b#{upper}\\b","g") | |
str = str.replace upperRegEx, upper.toUpperCase() | |
str | |
Function::debounce = (threshold = 300, execAsap = false, timeout = debounce_timer, args...) -> | |
# Borrowed from http://coffeescriptcookbook.com/chapters/functions/debounce | |
# Only run the prototyped function once per interval. | |
func = this | |
delayed = -> | |
func.apply(func, args) unless execAsap | |
console.log("Debounce applied") | |
if timeout? | |
try | |
clearTimeout(timeout) | |
catch e | |
# just do nothing | |
else if execAsap | |
func.apply(obj, args) | |
console.log("Executed immediately") | |
setTimeout(delayed, threshold) | |
randomInt = (lower = 0, upper = 1) -> | |
start = Math.random() | |
if not lower? | |
[lower, upper] = [0, lower] | |
if lower > upper | |
[lower, upper] = [upper, lower] | |
return Math.floor(start * (upper - lower + 1) + lower) | |
# Animations | |
animateLoad = (elId = "loader") -> | |
### | |
# Suggested CSS to go with this: | |
# | |
# #loader { | |
# position:fixed; | |
# top:50%; | |
# left:50%; | |
# } | |
# #loader.good::shadow .circle { | |
# border-color: rgba(46,190,17,0.9); | |
# } | |
# #loader.bad::shadow .circle { | |
# border-color:rgba(255,0,0,0.9); | |
# } | |
# | |
# Uses Polymer 1.0 | |
### | |
if isNumber(elId) then elId = "loader" | |
if elId.slice(0,1) is "#" | |
selector = elId | |
elId = elId.slice(1) | |
else | |
selector = "##{elId}" | |
try | |
if not $(selector).exists() | |
$("body").append("<paper-spinner id=\"#{elId}\" active></paper-spinner") | |
else | |
$(selector).attr("active",true) | |
false | |
catch e | |
console.log('Could not animate loader', e.message) | |
startLoad = animateLoad | |
stopLoad = (elId = "loader", fadeOut = 1000) -> | |
if elId.slice(0,1) is "#" | |
selector = elId | |
elId = elId.slice(1) | |
else | |
selector = "##{elId}" | |
try | |
if $(selector).exists() | |
$(selector).addClass("good") | |
delay fadeOut, -> | |
$(selector).removeClass("good") | |
$(selector).removeAttr("active") | |
catch e | |
console.log('Could not stop load animation', e.message) | |
stopLoadError = (message, elId = "loader", fadeOut = 5000) -> | |
if elId.slice(0,1) is "#" | |
selector = elId | |
elId = elId.slice(1) | |
else | |
selector = "##{elId}" | |
try | |
if $(selector).exists() | |
$(selector).addClass("bad") | |
if message? then toastStatusMessage(message,"",fadeOut) | |
delay fadeOut, -> | |
$(selector).removeClass("bad") | |
$(selector).removeAttr("active") | |
catch e | |
console.log('Could not stop load error animation', e.message) | |
toastStatusMessage = (message, className = "", duration = 3000, selector = "#status-message") -> | |
### | |
# Pop up a status message | |
### | |
unless window.metaTracker?.isToasting? | |
unless window.metaTracker? | |
window.metaTracker = new Object() | |
window.metaTracker.isToasting = false | |
if window.metaTracker.isToasting | |
delay 250, -> | |
# Wait and call again | |
toastStatusMessage(message, className, duration, selector) | |
return false | |
window.metaTracker.isToasting = true | |
if not isNumber(duration) | |
duration = 3000 | |
if selector.slice(0,1) is not "#" | |
selector = "##{selector}" | |
if not $(selector).exists() | |
html = "<paper-toast id=\"#{selector.slice(1)}\" duration=\"#{duration}\"></paper-toast>" | |
$(html).appendTo("body") | |
$(selector) | |
.attr("text",message) | |
.text(message) | |
.addClass(className) | |
$(selector).get(0).show() | |
delay duration + 500, -> | |
# A short time after it hides, clean it up | |
$(selector).empty() | |
$(selector).removeClass(className) | |
$(selector).attr("text","") | |
window.metaTracker.isToasting = false | |
openLink = (url) -> | |
if not url? then return false | |
window.open(url) | |
false | |
openTab = (url) -> | |
openLink(url) | |
goTo = (url) -> | |
if not url? then return false | |
window.location.href = url | |
false | |
mapNewWindows = (stopPropagation = true) -> | |
# Do new windows | |
$(".newwindow").each -> | |
# Add a click and keypress listener to | |
# open links with this class in a new window | |
curHref = $(this).attr("href") | |
if not curHref? | |
# Support non-standard elements | |
curHref = $(this).attr("data-href") | |
openInNewWindow = (url) -> | |
if not url? then return false | |
window.open(url) | |
return false | |
$(this).click (e) -> | |
if stopPropagation | |
e.preventDefault() | |
e.stopPropagation() | |
openInNewWindow(curHref) | |
$(this).keypress -> | |
openInNewWindow(curHref) | |
deepJQuery = (selector) -> | |
### | |
# Do a shadow-piercing selector | |
# | |
# Cross-browser, works with Chrome, Firefox, Opera, Safari, and IE | |
# Falls back to standard jQuery selector when everything fails. | |
### | |
try | |
# Chrome uses /deep/ which has been deprecated | |
# See http://dev.w3.org/csswg/css-scoping/#deep-combinator | |
# https://w3c.github.io/webcomponents/spec/shadow/#composed-trees | |
# This is current as of Chrome 44.0.2391.0 dev-m | |
# See https://code.google.com/p/chromium/issues/detail?id=446051 | |
unless $("html /deep/ #{selector}").exists() | |
throw("Bad /deep/ selector") | |
return $("html /deep/ #{selector}") | |
catch e | |
try | |
# Firefox uses >>> instead of "deep" | |
# https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM | |
# This is actually the correct selector | |
unless $("html >>> #{selector}").exists() | |
throw("Bad >>> selector") | |
return $("html >>> #{selector}") | |
catch e | |
# These don't match at all -- do the normal jQuery selector | |
return $(selector) | |
d$ = (selector) -> | |
deepJQuery(selector) | |
bindClicks = (selector = ".click") -> | |
### | |
# Helper function. Bind everything with a selector | |
# to execute a function data-function or to go to a | |
# URL data-href. | |
### | |
$(selector).each -> | |
try | |
url = $(this).attr("data-href") | |
if not isNull(url) | |
$(this).unbind() | |
# console.log("Binding a url to ##{$(this).attr("id")}") | |
try | |
if url is uri.o.attr("path") and $(this).prop("tagName").toLowerCase() is "paper-tab" | |
$(this).parent().prop("selected",$(this).index()) | |
catch e | |
console.warn("tagname lower case error") | |
$(this).click -> | |
if $(this).attr("newTab").toBool() or $(this).attr("newtab").toBool() | |
openTab(url) | |
else | |
goTo(url) | |
return url | |
else | |
# Check for onclick function | |
callable = $(this).attr("data-function") | |
if callable? | |
$(this).unbind() | |
# console.log("Binding #{callable}() to ##{$(this).attr("id")}") | |
$(this).click -> | |
try | |
console.log("Executing bound function #{callable}()") | |
window[callable]() | |
catch e | |
console.error("'#{callable}()' is a bad function - #{e.message}") | |
catch e | |
console.error("There was a problem binding to ##{$(this).attr("id")} - #{e.message}") | |
false | |
getPosterFromSrc = (srcString) -> | |
### | |
# Take the "src" attribute of a video and get the | |
# "png" screencap from it, and return the value. | |
### | |
try | |
split = srcString.split(".") | |
dummy = split.pop() | |
split.push("png"); | |
return split.join(".") | |
catch e | |
return "" | |
doCORSget = (url, args, callback = undefined, callbackFail = undefined) -> | |
corsFail = -> | |
if typeof callbackFail is "function" | |
callbackFail() | |
else | |
throw new Error("There was an error performing the CORS request") | |
# First try the jquery way | |
settings = | |
url: url | |
data: args | |
type: "get" | |
crossDomain: true | |
try | |
$.ajax(settings) | |
.done (result) -> | |
if typeof callback is "function" | |
callback() | |
return false | |
console.log(response) | |
.fail (result,status) -> | |
console.warn("Couldn't perform jQuery AJAX CORS. Attempting manually.") | |
catch e | |
console.warn("There was an error using jQuery to perform the CORS request. Attemping manually.") | |
# Then try the long way | |
url = "#{url}?#{args}" | |
createCORSRequest = (method = "get", url) -> | |
# From http://www.html5rocks.com/en/tutorials/cors/ | |
xhr = new XMLHttpRequest() | |
if "withCredentials" of xhr | |
# Check if the XMLHttpRequest object has a "withCredentials" | |
# property. | |
# "withCredentials" only exists on XMLHTTPRequest2 objects. | |
xhr.open(method,url,true) | |
else if typeof XDomainRequest isnt "undefined" | |
# Otherwise, check if XDomainRequest. | |
# XDomainRequest only exists in IE, and is IE's way of making CORS requests. | |
xhr = new XDomainRequest() | |
xhr.open(method,url) | |
else | |
xhr = null | |
return xhr | |
# Now execute it | |
xhr = createCORSRequest("get",url) | |
if !xhr | |
throw new Error("CORS not supported") | |
xhr.onload = -> | |
response = xhr.responseText | |
if typeof callback is "function" | |
callback(response) | |
console.log(response) | |
return false | |
xhr.onerror = -> | |
console.warn("Couldn't do manual XMLHttp CORS request") | |
# Place this in the last error | |
corsFail() | |
xhr.send() | |
false | |
lightboxImages = (selector = ".lightboximage", lookDeeply = false) -> | |
### | |
# Lightbox images with this selector | |
# | |
# If the image has it, wrap it in an anchor and bind; | |
# otherwise just apply to the selector. | |
# | |
# Requires ImageLightbox | |
# https://github.com/rejas/imagelightbox | |
### | |
# The options! | |
options = | |
onStart: -> | |
overlayOn() | |
onEnd: -> | |
overlayOff() | |
activityIndicatorOff() | |
onLoadStart: -> | |
activityIndicatorOn() | |
onLoadEnd: -> | |
activityIndicatorOff() | |
allowedTypes: 'png|jpg|jpeg|gif|bmp|webp' | |
quitOnDocClick: true | |
quitOnImgClick: true | |
jqo = if lookDeeply then d$(selector) else $(selector) | |
jqo | |
.click (e) -> | |
try | |
$(this).imageLightbox(options).startImageLightbox() | |
# We want to stop the events propogating up for these | |
e.preventDefault() | |
e.stopPropagation() | |
console.warn("Event propagation was stopped when clicking on this.") | |
catch e | |
console.error("Unable to lightbox this image!") | |
# Set up the items | |
.each -> | |
console.log("Using selectors '#{selector}' / '#{this}' for lightboximages") | |
try | |
if $(this).prop("tagName").toLowerCase() is "img" and $(this).parent().prop("tagName").toLowerCase() isnt "a" | |
tagHtml = $(this).removeClass("lightboximage").prop("outerHTML") | |
imgUrl = switch | |
when not isNull($(this).attr("data-layzr-retina")) | |
$(this).attr("data-layzr-retina") | |
when not isNull($(this).attr("data-layzr")) | |
$(this).attr("data-layzr") | |
else | |
$(this).attr("src") | |
$(this).replaceWith("<a href='#{imgUrl}' class='lightboximage'>#{tagHtml}</a>") | |
catch e | |
console.log("Couldn't parse through the elements") | |
activityIndicatorOn = -> | |
$('<div id="imagelightbox-loading"><div></div></div>' ).appendTo('body') | |
activityIndicatorOff = -> | |
$('#imagelightbox-loading').remove() | |
$("#imagelightbox-overlay").click -> | |
# Clicking anywhere on the overlay clicks on the image | |
# It loads too late to let the quitOnDocClick work | |
$("#imagelightbox").click() | |
overlayOn = -> | |
$('<div id="imagelightbox-overlay"></div>').appendTo('body') | |
overlayOff = -> | |
$('#imagelightbox-overlay').remove() | |
formatScientificNames = (selector = ".sciname") -> | |
$(".sciname").each -> | |
# Is it italic? | |
nameStyle = if $(this).css("font-style") is "italic" then "normal" else "italic" | |
$(this).css("font-style",nameStyle) | |
prepURI = (string) -> | |
string = encodeURIComponent(string) | |
string.replace(/%20/g,"+") | |
window.locationData = new Object() | |
locationData.params = | |
enableHighAccuracy: true | |
locationData.last = undefined | |
getLocation = (callback = undefined) -> | |
geoSuccess = (pos,callback) -> | |
window.locationData.lat = pos.coords.latitude | |
window.locationData.lng = pos.coords.longitude | |
window.locationData.acc = pos.coords.accuracy | |
window.locationData.last = Date.now() # ms, unix time | |
if callback? | |
callback(window.locationData) | |
false | |
geoFail = (error,callback) -> | |
locationError = switch error.code | |
when 0 then "There was an error while retrieving your location: #{error.message}" | |
when 1 then "The user prevented this page from retrieving a location" | |
when 2 then "The browser was unable to determine your location: #{error.message}" | |
when 3 then "The browser timed out retrieving your location." | |
console.error(locationError) | |
if callback? | |
callback(false) | |
false | |
if navigator.geolocation | |
navigator.geolocation.getCurrentPosition(geoSuccess,geoFail,window.locationData.params) | |
else | |
console.warn("This browser doesn't support geolocation!") | |
if callback? | |
callback(false) | |
getMaxZ = -> | |
mapFunction = -> | |
$.map $("body *"), (e,n) -> | |
if $(e).css("position") isnt "static" | |
return parseInt $(e).css("z-index") or 1 | |
Math.max.apply null, mapFunction() | |
foo = -> | |
toastStatusMessage("Sorry, this feature is not yet finished") | |
stopLoad() | |
false | |
$ -> | |
bindClicks() | |
formatScientificNames() | |
try | |
$('[data-toggle="tooltip"]').tooltip() | |
catch e | |
console.warn("Tooltips were attempted to be set up, but do not exist") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment