/** @module up */ (function() { window.up = {}; }).call(this); /** Utility functions ================= Up.js comes with a number of utility functions that might save you from loading something like [Underscore.js](http://underscorejs.org/). @class up.util */ (function() { var slice = [].slice; up.util = (function($) { /** @function up.util.memoize @internal */ var $createElementFromSelector, ANIMATION_PROMISE_KEY, CONSOLE_PLACEHOLDERS, ajax, any, cache, castedAttr, clientSize, compact, config, contains, copy, copyAttributes, createElement, createElementFromHtml, cssAnimate, debug, detect, each, error, escapePressed, evalConsoleTemplate, extend, findWithSelf, finishCssAnimate, fixedToAbsolute, forceCompositing, isArray, isBlank, isDeferred, isDefined, isElement, isFunction, isGiven, isHash, isJQuery, isMissing, isNull, isNumber, isObject, isPresent, isPromise, isStandardPort, isString, isUndefined, isUnmodifiedKeyEvent, isUnmodifiedMouseEvent, last, locationFromXhr, map, measure, memoize, merge, methodFromXhr, multiSelector, nextFrame, normalizeMethod, normalizeUrl, nullJQuery, offsetParent, once, only, option, options, parseUrl, presence, presentAttr, remove, resolvableWhen, resolvedDeferred, resolvedPromise, scrollbarWidth, select, selectorForElement, setMissingAttrs, temporaryCss, times, titleFromXhr, toArray, trim, unJQuery, uniq, unresolvablePromise, unwrapElement, warn; memoize = function(func) { var cache, cached; cache = void 0; cached = false; return function() { var args; args = 1 <= arguments.length ? slice.call(arguments, 0) : []; if (cached) { return cache; } else { cached = true; return cache = func.apply(null, args); } }; }; /** @function up.util.ajax @internal */ ajax = function(request) { request = copy(request); if (request.selector) { request.headers || (request.headers = {}); request.headers['X-Up-Selector'] = request.selector; } return $.ajax(request); }; /** Returns if the given port is the default port for the given protocol. @function up.util.isStandardPort @internal */ isStandardPort = function(protocol, port) { port = port.toString(); return ((port === "" || port === "80") && protocol === 'http:') || (port === "443" && protocol === 'https:'); }; /** Normalizes relative paths and absolute paths to a full URL that can be checked for equality with other normalized URLs. By default hashes are ignored, search queries are included. @function up.util.normalizeUrl @param {Boolean} [options.hash=false] Whether to include an `#hash` anchor in the normalized URL @param {Boolean} [options.search=true] Whether to include a `?query` string in the normalized URL @param {Boolean} [options.stripTrailingSlash=false] Whether to strip a trailing slash from the pathname @internal */ normalizeUrl = function(urlOrAnchor, options) { var anchor, normalized, pathname; anchor = parseUrl(urlOrAnchor); normalized = anchor.protocol + "//" + anchor.hostname; if (!isStandardPort(anchor.protocol, anchor.port)) { normalized += ":" + anchor.port; } pathname = anchor.pathname; if (pathname[0] !== '/') { pathname = "/" + pathname; } if ((options != null ? options.stripTrailingSlash : void 0) === true) { pathname = pathname.replace(/\/$/, ''); } normalized += pathname; if ((options != null ? options.hash : void 0) === true) { normalized += anchor.hash; } if ((options != null ? options.search : void 0) !== false) { normalized += anchor.search; } return normalized; }; /** Parses the given URL into components such as hostname and path. If the given URL is not fully qualified, it is assumed to be relative to the current page. @function up.util.parseUrl @return {Object} The parsed URL as an object with `protocol`, `hostname`, `port`, `pathname`, `search` and `hash` properties. @experimental */ parseUrl = function(urlOrAnchor) { var anchor; anchor = null; if (isString(urlOrAnchor)) { anchor = $('').attr({ href: urlOrAnchor }).get(0); if (isBlank(anchor.hostname)) { anchor.href = anchor.href; } } else { anchor = unJQuery(urlOrAnchor); } return anchor; }; /** @function up.util.normalizeMethod @internal */ normalizeMethod = function(method) { if (method) { return method.toUpperCase(); } else { return 'GET'; } }; /** @function $createElementFromSelector @internal */ $createElementFromSelector = function(selector) { var $element, $parent, $root, classes, conjunction, depthSelector, expression, html, id, iteration, j, k, len, len1, path, tag; path = selector.split(/[ >]/); $root = null; for (iteration = j = 0, len = path.length; j < len; iteration = ++j) { depthSelector = path[iteration]; conjunction = depthSelector.match(/(^|\.|\#)[A-Za-z0-9\-_]+/g); tag = "div"; classes = []; id = null; for (k = 0, len1 = conjunction.length; k < len1; k++) { expression = conjunction[k]; switch (expression[0]) { case ".": classes.push(expression.substr(1)); break; case "#": id = expression.substr(1); break; default: tag = expression; } } html = "<" + tag; if (classes.length) { html += " class=\"" + classes.join(" ") + "\""; } if (id) { html += " id=\"" + id + "\""; } html += ">"; $element = $(html); if ($parent) { $element.appendTo($parent); } if (iteration === 0) { $root = $element; } $parent = $element; } return $root; }; createElement = function(tagName, html) { var element; element = document.createElement(tagName); if (isPresent(html)) { element.innerHTML = html; } return element; }; /** Prints a debugging message to the browser console. @function up.debug @param {String} message @param {Array} args... @internal */ debug = function() { var args, message, ref; message = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; message = "[UP] " + message; return (ref = up.browser).puts.apply(ref, ['debug', message].concat(slice.call(args))); }; /** @function up.warn @internal */ warn = function() { var args, message, ref; message = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; message = "[UP] " + message; return (ref = up.browser).puts.apply(ref, ['warn', message].concat(slice.call(args))); }; /** Throws a fatal error with the given message. - The error will be printed to the [error console](https://developer.mozilla.org/en-US/docs/Web/API/Console/error) - An [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) (exception) will be thrown, unwinding the current call stack - The error message will be printed in a corner of the screen \#\#\#\# Examples up.error('Division by zero') up.error('Unexpected result %o', result) @function up.error @internal */ error = function() { var $error, args, asString, ref; args = 1 <= arguments.length ? slice.call(arguments, 0) : []; args[0] = "[UP] " + args[0]; (ref = up.browser).puts.apply(ref, ['error'].concat(slice.call(args))); asString = evalConsoleTemplate.apply(null, args); $error = presence($('.up-error')) || $('
').prependTo('body'); $error.addClass('up-error'); $error.text(asString); throw new Error(asString); }; CONSOLE_PLACEHOLDERS = /\%[odisf]/g; evalConsoleTemplate = function() { var args, i, maxLength, message; args = 1 <= arguments.length ? slice.call(arguments, 0) : []; message = args[0]; i = 0; maxLength = 80; return message.replace(CONSOLE_PLACEHOLDERS, function() { var arg, argType; i += 1; arg = args[i]; argType = typeof arg; if (argType === 'string') { arg = arg.replace(/\s+/g, ' '); if (arg.length > maxLength) { arg = (arg.substr(0, maxLength)) + "…"; } arg = "\"" + arg + "\""; } else if (argType === 'undefined') { arg = 'undefined'; } else if (argType === 'number' || argType === 'function') { arg = arg.toString(); } else { arg = JSON.stringify(arg); } if (arg.length > maxLength) { arg = (arg.substr(0, maxLength)) + " …"; if (argType === 'object' || argType === 'function') { arg += " }"; } } return arg; }); }; /** Returns a CSS selector that matches the given element as good as possible. This uses, in decreasing order of priority: - The element's `up-id` attribute - The element's ID - The element's name - The element's classes - The element's tag names @function up.util.selectorForElement @param {String|Element|jQuery} The element for which to create a selector. @experimental */ selectorForElement = function(element) { var $element, classString, classes, id, j, klass, len, name, selector, upId; $element = $(element); selector = void 0; debug("Creating selector from element %o", $element.get(0)); if (upId = presence($element.attr("up-id"))) { selector = "[up-id='" + upId + "']"; } else if (id = presence($element.attr("id"))) { selector = "#" + id; } else if (name = presence($element.attr("name"))) { selector = "[name='" + name + "']"; } else if (classString = presence($element.attr("class"))) { classes = classString.split(' '); selector = ''; for (j = 0, len = classes.length; j < len; j++) { klass = classes[j]; selector += "." + klass; } } else { selector = $element.prop('tagName').toLowerCase(); } return selector; }; createElementFromHtml = function(html) { var anything, bodyElement, bodyMatch, bodyPattern, capture, closeTag, headElement, htmlElement, openTag, titleElement, titleMatch, titlePattern; openTag = function(tag) { return "<" + tag + "(?: [^>]*)?>"; }; closeTag = function(tag) { return ""; }; anything = '(?:.|\\n)*?'; capture = function(pattern) { return "(" + pattern + ")"; }; titlePattern = new RegExp(openTag('head') + anything + openTag('title') + capture(anything) + closeTag('title') + anything + closeTag('body'), 'i'); bodyPattern = new RegExp(openTag('body') + capture(anything) + closeTag('body'), 'i'); if (bodyMatch = html.match(bodyPattern)) { htmlElement = document.createElement('html'); bodyElement = createElement('body', bodyMatch[1]); htmlElement.appendChild(bodyElement); if (titleMatch = html.match(titlePattern)) { headElement = createElement('head'); htmlElement.appendChild(headElement); titleElement = createElement('title', titleMatch[1]); headElement.appendChild(titleElement); } return htmlElement; } else { return createElement('div', html); } }; /** Merge the contents of two or more objects together into the first object. @function up.util.extend @param {Object} target @param {Array} sources... @stable */ extend = $.extend; /** Returns a new string with whitespace removed from the beginning and end of the given string. @param {String} A string that might have whitespace at the beginning and end. @return {String} The trimmed string. @stable */ trim = $.trim; /** Calls the given function for each element (and, optional, index) of the given array. @function up.util.each @param {Array} array @param {Function} block A function that will be called with each element and (optional) iteration index. @stable */ each = function(array, block) { var index, item, j, len, results; results = []; for (index = j = 0, len = array.length; j < len; index = ++j) { item = array[index]; results.push(block(item, index)); } return results; }; /** Translate all items in an array to new array of items. @function up.util.map @param {Array} array @param {Function} block A function that will be called with each element and (optional) iteration index. @return {Array} A new array containing the result of each function call. @stable */ map = each; /** Calls the given function for the given number of times. @function up.util.times @param {Number} count @param {Function} block @stable */ times = function(count, block) { var iteration, j, ref, results; results = []; for (iteration = j = 0, ref = count - 1; 0 <= ref ? j <= ref : j >= ref; iteration = 0 <= ref ? ++j : --j) { results.push(block(iteration)); } return results; }; /** Returns whether the given argument is `null`. @function up.util.isNull @param object @return {Boolean} @stable */ isNull = function(object) { return object === null; }; /** Returns whether the given argument is `undefined`. @function up.util.isUndefined @param object @return {Boolean} @stable */ isUndefined = function(object) { return object === void(0); }; /** Returns whether the given argument is not `undefined`. @function up.util.isDefined @param object @return {Boolean} @stable */ isDefined = function(object) { return !isUndefined(object); }; /** Returns whether the given argument is either `undefined` or `null`. Note that empty strings or zero are *not* considered to be "missing". For the opposite of `up.util.isMissing` see [`up.util.isGiven`](/up.util.isGiven). @function up.util.isMissing @param object @return {Boolean} @stable */ isMissing = function(object) { return isUndefined(object) || isNull(object); }; /** Returns whether the given argument is neither `undefined` nor `null`. Note that empty strings or zero *are* considered to be "given". For the opposite of `up.util.isGiven` see [`up.util.isMissing`](/up.util.isMissing). @function up.util.isGiven @param object @return {Boolean} @stable */ isGiven = function(object) { return !isMissing(object); }; /** Return whether the given argument is considered to be blank. This returns `true` for: - `undefined` - `null` - Empty strings - Empty arrays - An object without own enumerable properties All other arguments return `false`. @function up.util.isBlank @param object @return {Boolean} @stable */ isBlank = function(object) { return isMissing(object) || (isObject(object) && Object.keys(object).length === 0) || (object.length === 0); }; /** Returns the given argument if the argument is [present](/up.util.isPresent), otherwise returns `undefined`. @function up.util.presence @param object @param {Function} [tester=up.util.isPresent] The function that will be used to test whether the argument is present. @return {T|Undefined} @stable */ presence = function(object, tester) { if (tester == null) { tester = isPresent; } if (tester(object)) { return object; } else { return void 0; } }; /** Returns whether the given argument is not [blank](/up.util.isBlank). @function up.util.isPresent @param object @return {Boolean} @stable */ isPresent = function(object) { return !isBlank(object); }; /** Returns whether the given argument is a function. @function up.util.isFunction @param object @return {Boolean} @stable */ isFunction = function(object) { return typeof object === 'function'; }; /** Returns whether the given argument is a string. @function up.util.isString @param object @return {Boolean} @stable */ isString = function(object) { return typeof object === 'string'; }; /** Returns whether the given argument is a number. Note that this will check the argument's *type*. It will return `false` for a string like `"123"`. @function up.util.isNumber @param object @return {Boolean} @stable */ isNumber = function(object) { return typeof object === 'number'; }; /** Returns whether the given argument is an object, but not a function. @function up.util.isHash @param object @return {Boolean} @stable */ isHash = function(object) { return typeof object === 'object' && !!object; }; /** Returns whether the given argument is an object. This also returns `true` for functions, which may behave like objects in Javascript. For an alternative that returns `false` for functions, see [`up.util.isHash`](/up.util.isHash). @function up.util.isObject @param object @return {Boolean} @stable */ isObject = function(object) { return isHash(object) || (typeof object === 'function'); }; /** Returns whether the given argument is a DOM element. @function up.util.isElement @param object @return {Boolean} @stable */ isElement = function(object) { return !!(object && object.nodeType === 1); }; /** Returns whether the given argument is a jQuery collection. @function up.util.isJQuery @param object @return {Boolean} @stable */ isJQuery = function(object) { return object instanceof jQuery; }; /** Returns whether the given argument is an object with a `then` method. @function up.util.isPromise @param object @return {Boolean} @stable */ isPromise = function(object) { return isObject(object) && isFunction(object.then); }; /** Returns whether the given argument is an object with `then` and `resolve` methods. @function up.util.isDeferred @param object @return {Boolean} @stable */ isDeferred = function(object) { return isPromise(object) && isFunction(object.resolve); }; /** Returns whether the given argument is an array. @function up.util.isArray @param object @return {Boolean} @stable */ isArray = Array.isArray || function(object) { return Object.prototype.toString.call(object) === '[object Array]'; }; /** Converts the given array-like argument into an array. Returns the array. @function up.util.isDeferred @param object @return {Array} @stable */ toArray = function(object) { return Array.prototype.slice.call(object); }; /** Shallow-copies the given array or object into a new array or object. Returns the new array or object. @function up.util.copy @param {Object|Array} object @return {Object|Array} @stable */ copy = function(object) { if (isArray(object)) { return object.slice(); } else { return extend({}, object); } }; /** If given a jQuery collection, returns the underlying array of DOM element. If given any other argument, returns the argument unchanged. @function up.util.unJQuery @param object @internal */ unJQuery = function(object) { if (isJQuery(object)) { return object.get(0); } else { return object; } }; /** Creates a new object by merging together the properties from the given objects. @function up.util.merge @param {Array} sources... @return Object @stable */ merge = function() { var sources; sources = 1 <= arguments.length ? slice.call(arguments, 0) : []; return extend.apply(null, [{}].concat(slice.call(sources))); }; /** Creates an options hash from the given argument and some defaults. The semantics of this function are confusing. We want to get rid of this in the future. @function up.util.options @param {Object} object @param {Object} [defaults] @return {Object} @internal */ options = function(object, defaults) { var defaultValue, key, merged, value; merged = object ? copy(object) : {}; if (defaults) { for (key in defaults) { defaultValue = defaults[key]; value = merged[key]; if (!isGiven(value)) { merged[key] = defaultValue; } else if (isObject(defaultValue) && isObject(value)) { merged[key] = options(value, defaultValue); } } } return merged; }; /** Returns the first argument that is considered present. If an argument is a function, it is called and the value is checked for presence. This function is useful when you have multiple option sources and the value can be boolean. In that case you cannot change the sources with a `||` operator (since that doesn't short-circuit at `false`). @function up.util.option @param {Array} args... @internal */ option = function() { var arg, args, j, len, match, value; args = 1 <= arguments.length ? slice.call(arguments, 0) : []; match = void 0; for (j = 0, len = args.length; j < len; j++) { arg = args[j]; value = arg; if (isFunction(value)) { value = value(); } if (isGiven(value)) { match = value; break; } } return match; }; /** Passes each element in the given array to the given function. Returns the first element for which the function returns a truthy value. If no object matches, returns `undefined`. @function up.util.detect @param {Array} array @param {Function} tester @return {T|Undefined} @stable */ detect = function(array, tester) { var element, j, len, match; match = void 0; for (j = 0, len = array.length; j < len; j++) { element = array[j]; if (tester(element)) { match = element; break; } } return match; }; /** Returns whether the given function returns a truthy value for any element in the given array. @function up.util.any @param {Array} array @param {Function} tester @return {Boolean} @experimental */ any = function(array, tester) { var element, j, len, match; match = false; for (j = 0, len = array.length; j < len; j++) { element = array[j]; if (tester(element)) { match = true; break; } } return match; }; /** Returns all elements from the given array that are neither `null` or `undefined`. @function up.util.compact @param {Array} array @return {Array} @stable */ compact = function(array) { return select(array, isGiven); }; /** Returns the given array without duplicates. @function up.util.uniq @param {Array} array @return {Array} @stable */ uniq = function(array) { var seen; seen = {}; return select(array, function(element) { if (seen.hasOwnProperty(element)) { return false; } else { return seen[element] = true; } }); }; /** Returns all elements from the given array that return a truthy value when passed to the given function. @function up.util.select @param {Array} array @return {Array} @stable */ select = function(array, tester) { var matches; matches = []; each(array, function(element) { if (tester(element)) { return matches.push(element); } }); return matches; }; /** Returns the first [present](/up.util.isPresent) element attribute among the given list of attribute names. @function up.util.presentAttr @internal */ presentAttr = function() { var $element, attrName, attrNames, values; $element = arguments[0], attrNames = 2 <= arguments.length ? slice.call(arguments, 1) : []; values = (function() { var j, len, results; results = []; for (j = 0, len = attrNames.length; j < len; j++) { attrName = attrNames[j]; results.push($element.attr(attrName)); } return results; })(); return detect(values, isPresent); }; /** Schedules the given function to be called in the next Javascript execution frame. @function up.util.nextFrame @param {Function} block @stable */ nextFrame = function(block) { return setTimeout(block, 0); }; /** Returns the last element of the given array. @function up.util.last @param {Array} array @return {T} */ last = function(array) { return array[array.length - 1]; }; /** Measures the drawable area of the document. @function up.util.clientSize @internal */ clientSize = function() { var element; element = document.documentElement; return { width: element.clientWidth, height: element.clientHeight }; }; /** Returns the width of a scrollbar. This only runs once per page load. @function up.util.scrollbarWidth @internal */ scrollbarWidth = memoize(function() { var $outer, outer, width; $outer = $('
').css({ position: 'absolute', top: '0', left: '0', width: '50px', height: '50px', overflowY: 'scroll' }); $outer.appendTo(document.body); outer = $outer.get(0); width = outer.offsetWidth - outer.clientWidth; $outer.remove(); return width; }); /** Modifies the given function so it only runs once. Subsequent calls will return the previous return value. @function up.util.once @param {Function} fun @experimental */ once = function(fun) { var result; result = void 0; return function() { var args; args = 1 <= arguments.length ? slice.call(arguments, 0) : []; if (fun != null) { result = fun.apply(null, args); } fun = void 0; return result; }; }; /** Temporarily sets the CSS for the given element. @function up.util.temporaryCss @param {jQuery} $element @param {Object} css @param {Function} [block] If given, the CSS is set, the block is called and the old CSS is restored. @return {Function} A function that restores the original CSS when called. @internal */ temporaryCss = function($element, css, block) { var memo, oldCss; oldCss = $element.css(Object.keys(css)); $element.css(css); memo = function() { return $element.css(oldCss); }; if (block) { block(); return memo(); } else { return once(memo); } }; /** Forces the given jQuery element into an accelerated compositing layer. @function up.util.forceCompositing @internal */ forceCompositing = function($element) { var memo, oldTransforms; oldTransforms = $element.css(['transform', '-webkit-transform']); if (isBlank(oldTransforms) || oldTransforms['transform'] === 'none') { memo = function() { return $element.css(oldTransforms); }; $element.css({ 'transform': 'translateZ(0)', '-webkit-transform': 'translateZ(0)' }); } else { memo = function() {}; } return memo; }; /** Animates the given element's CSS properties using CSS transitions. If the element is already being animated, the previous animation will instantly jump to its last frame before the new animation begins. To improve performance, the element will be forced into compositing for the duration of the animation. @function up.util.cssAnimate @param {Element|jQuery|String} elementOrSelector The element to animate. @param {Object} lastFrame The CSS properties that should be transitioned to. @param {Number} [options.duration=300] The duration of the animation, in milliseconds. @param {Number} [options.delay=0] The delay before the animation starts, in milliseconds. @param {String} [options.easing='ease'] The timing function that controls the animation's acceleration. See [W3C documentation](http://www.w3.org/TR/css3-transitions/#transition-timing-function) for a list of pre-defined timing functions. @return A promise for the animation's end. @internal */ cssAnimate = function(elementOrSelector, lastFrame, opts) { var $element, deferred, endTimeout, transition, withoutCompositing, withoutTransition; $element = $(elementOrSelector); if (up.browser.canCssAnimation()) { opts = options(opts, { duration: 300, delay: 0, easing: 'ease' }); deferred = $.Deferred(); transition = { 'transition-property': Object.keys(lastFrame).join(', '), 'transition-duration': opts.duration + "ms", 'transition-delay': opts.delay + "ms", 'transition-timing-function': opts.easing }; withoutCompositing = forceCompositing($element); withoutTransition = temporaryCss($element, transition); $element.css(lastFrame); deferred.then(withoutCompositing); deferred.then(withoutTransition); $element.data(ANIMATION_PROMISE_KEY, deferred); deferred.then(function() { return $element.removeData(ANIMATION_PROMISE_KEY); }); endTimeout = setTimeout((function() { return deferred.resolve(); }), opts.duration + opts.delay); deferred.then(function() { return clearTimeout(endTimeout); }); return deferred; } else { $element.css(lastFrame); return resolvedDeferred(); } }; ANIMATION_PROMISE_KEY = 'up-animation-promise'; /** Completes the animation for the given element by jumping to the last frame instantly. All callbacks chained to the original animation's promise will be called. Does nothing if the given element is not currently animating. Also see [`up.motion.finish`](/up.motion.finish). @function up.util.finishCssAnimate @param {Element|jQuery|String} elementOrSelector @internal */ finishCssAnimate = function(elementOrSelector) { return $(elementOrSelector).each(function() { var existingAnimation; if (existingAnimation = $(this).data(ANIMATION_PROMISE_KEY)) { return existingAnimation.resolve(); } }); }; /** Measures the given element. @function up.util.measure @internal */ measure = function($element, opts) { var $context, box, contextCoords, coordinates, elementCoords, viewport; opts = options(opts, { relative: false, inner: false, full: false }); if (opts.relative) { if (opts.relative === true) { coordinates = $element.position(); } else { $context = $(opts.relative); elementCoords = $element.offset(); if ($context.is(document)) { coordinates = elementCoords; } else { contextCoords = $context.offset(); coordinates = { left: elementCoords.left - contextCoords.left, top: elementCoords.top - contextCoords.top }; } } } else { coordinates = $element.offset(); } box = { left: coordinates.left, top: coordinates.top }; if (opts.inner) { box.width = $element.width(); box.height = $element.height(); } else { box.width = $element.outerWidth(); box.height = $element.outerHeight(); } if (opts.full) { viewport = clientSize(); box.right = viewport.width - (box.left + box.width); box.bottom = viewport.height - (box.top + box.height); } return box; }; /** Copies all attributes from the source element to the target element. @function up.util.copyAttributes @internal */ copyAttributes = function($source, $target) { var attr, j, len, ref, results; ref = $source.get(0).attributes; results = []; for (j = 0, len = ref.length; j < len; j++) { attr = ref[j]; if (attr.specified) { results.push($target.attr(attr.name, attr.value)); } else { results.push(void 0); } } return results; }; /** Looks for the given selector in the element and its descendants. @function up.util.findWithSelf @internal */ findWithSelf = function($element, selector) { return $element.find(selector).addBack(selector); }; /** Returns whether the given keyboard event involved the ESC key. @function up.util.escapePressed @internal */ escapePressed = function(event) { return event.keyCode === 27; }; /** Returns whether the given array or string contains the given element or substring. @function up.util.contains @param {Array|String} arrayOrString @param elementOrSubstring @stable */ contains = function(arrayOrString, elementOrSubstring) { return arrayOrString.indexOf(elementOrSubstring) >= 0; }; /** @function up.util.castedAttr @internal */ castedAttr = function($element, attrName) { var value; value = $element.attr(attrName); switch (value) { case 'false': return false; case 'true': return true; case '': return true; default: return value; } }; /** @function up.util.locationFromXhr @internal */ locationFromXhr = function(xhr) { return xhr.getResponseHeader('X-Up-Location'); }; /** @function up.util.titleFromXhr @internal */ titleFromXhr = function(xhr) { return xhr.getResponseHeader('X-Up-Title'); }; /** @function up.util.methodFromXhr @internal */ methodFromXhr = function(xhr) { return xhr.getResponseHeader('X-Up-Method'); }; /** Returns a copy of the given object that only contains the given properties. @function up.util.only @param {Object} object @param {Array} keys... @stable */ only = function() { var filtered, j, len, object, properties, property; object = arguments[0], properties = 2 <= arguments.length ? slice.call(arguments, 1) : []; filtered = {}; for (j = 0, len = properties.length; j < len; j++) { property = properties[j]; if (object.hasOwnProperty(property)) { filtered[property] = object[property]; } } return filtered; }; /** @function up.util.isUnmodifiedKeyEvent @internal */ isUnmodifiedKeyEvent = function(event) { return !(event.metaKey || event.shiftKey || event.ctrlKey); }; /** @function up.util.isUnmodifiedMouseEvent @internal */ isUnmodifiedMouseEvent = function(event) { var isLeftButton; isLeftButton = isUndefined(event.button) || event.button === 0; return isLeftButton && isUnmodifiedKeyEvent(event); }; /** Returns a [Deferred object](https://api.jquery.com/category/deferred-object/) that is already resolved. @function up.util.resolvedDeferred @return {Deferred} @stable */ resolvedDeferred = function() { var deferred; deferred = $.Deferred(); deferred.resolve(); return deferred; }; /** Returns a promise that is already resolved. @function up.util.resolvedPromise @return {Promise} @stable */ resolvedPromise = function() { return resolvedDeferred().promise(); }; /** Returns a promise that will never be resolved. @function up.util.unresolvablePromise @experimental */ unresolvablePromise = function() { return $.Deferred().promise(); }; /** Returns an empty jQuery collection. @function up.util.nullJQuery @internal */ nullJQuery = function() { return $(); }; /** Returns a new promise that resolves once all promises in arguments resolve. Other then [`$.when` from jQuery](https://api.jquery.com/jquery.when/), the combined promise will have a `resolve` method. This `resolve` method will resolve all the wrapped promises. @function up.util.resolvableWhen @internal */ resolvableWhen = function() { var deferreds, joined; deferreds = 1 <= arguments.length ? slice.call(arguments, 0) : []; joined = $.when.apply($, deferreds); joined.resolve = function() { return each(deferreds, function(deferred) { return typeof deferred.resolve === "function" ? deferred.resolve() : void 0; }); }; return joined; }; /** On the given element, set attributes that are still missing. @function up.util.setMissingAttrs @internal */ setMissingAttrs = function($element, attrs) { var key, results, value; results = []; for (key in attrs) { value = attrs[key]; if (isMissing($element.attr(key))) { results.push($element.attr(key, value)); } else { results.push(void 0); } } return results; }; /** Removes the given element from the given array. This changes the given array. @function up.util.remove @param {Array} array @param {T} element @stable */ remove = function(array, element) { var index; index = array.indexOf(element); if (index >= 0) { array.splice(index, 1); return element; } }; /** @function up.util.multiSelector @internal */ multiSelector = function(parts) { var combinedSelector, elements, j, len, obj, part, selectors; obj = {}; selectors = []; elements = []; for (j = 0, len = parts.length; j < len; j++) { part = parts[j]; if (isString(part)) { selectors.push(part); } else { elements.push(part); } } obj.parsed = elements; if (selectors.length) { combinedSelector = selectors.join(', '); obj.parsed.push(combinedSelector); } obj.select = function() { return obj.find(void 0); }; obj.find = function($root) { var $matches, $result, k, len1, ref, selector; $result = nullJQuery(); ref = obj.parsed; for (k = 0, len1 = ref.length; k < len1; k++) { selector = ref[k]; $matches = $root ? $root.find(selector) : $(selector); $result = $result.add($matches); } return $result; }; obj.findWithSelf = function($start) { var $matches; $matches = obj.find($start); if (obj.doesMatch($start)) { $matches = $matches.add($start); } return $matches; }; obj.doesMatch = function(element) { var $element; $element = $(element); return any(obj.parsed, function(selector) { return $element.is(selector); }); }; obj.seekUp = function(start) { var $element, $result, $start; $start = $(start); $element = $start; $result = void 0; while ($element.length) { if (obj.doesMatch($element)) { $result = $element; break; } $element = $element.parent(); } return $result || nullJQuery(); }; return obj; }; /** @function up.util.cache @param {Number|Function} [config.size] Maximum number of cache entries. Set to `undefined` to not limit the cache size. @param {Number|Function} [config.expiry] The number of milliseconds after which a cache entry will be discarded. @param {String} [config.log] A prefix for log entries printed by this cache object. @param {Function} [config.key] A function that takes an argument and returns a `String` key for storage. If omitted, `toString()` is called on the argument. @internal */ cache = function(config) { var alias, clear, expiryMilis, get, isFresh, keys, log, maxSize, normalizeStoreKey, set, store, timestamp; if (config == null) { config = {}; } store = void 0; clear = function() { return store = {}; }; clear(); log = function() { var args; args = 1 <= arguments.length ? slice.call(arguments, 0) : []; if (config.log) { args[0] = "[" + config.log + "] " + args[0]; return debug.apply(null, args); } }; keys = function() { return Object.keys(store); }; maxSize = function() { if (isMissing(config.size)) { return void 0; } else if (isFunction(config.size)) { return config.size(); } else if (isNumber(config.size)) { return config.size; } else { return error("Invalid size config: %o", config.size); } }; expiryMilis = function() { if (isMissing(config.expiry)) { return void 0; } else if (isFunction(config.expiry)) { return config.expiry(); } else if (isNumber(config.expiry)) { return config.expiry; } else { return error("Invalid expiry config: %o", config.expiry); } }; normalizeStoreKey = function(key) { if (config.key) { return config.key(key); } else { return key.toString(); } }; trim = function() { var oldestKey, oldestTimestamp, size, storeKeys; storeKeys = copy(keys()); size = maxSize(); if (size && storeKeys.length > size) { oldestKey = null; oldestTimestamp = null; each(storeKeys, function(key) { var promise, timestamp; promise = store[key]; timestamp = promise.timestamp; if (!oldestTimestamp || oldestTimestamp > timestamp) { oldestKey = key; return oldestTimestamp = timestamp; } }); if (oldestKey) { return delete store[oldestKey]; } } }; alias = function(oldKey, newKey) { var value; value = get(oldKey); if (isDefined(value)) { return set(newKey, value); } }; timestamp = function() { return (new Date()).valueOf(); }; set = function(key, value) { var storeKey; storeKey = normalizeStoreKey(key); return store[storeKey] = { timestamp: timestamp(), value: value }; }; remove = function(key) { var storeKey; storeKey = normalizeStoreKey(key); return delete store[storeKey]; }; isFresh = function(entry) { var expiry, timeSinceTouch; expiry = expiryMilis(); if (expiry) { timeSinceTouch = timestamp() - entry.timestamp; return timeSinceTouch < expiryMilis(); } else { return true; } }; get = function(key, fallback) { var entry, storeKey; if (fallback == null) { fallback = void 0; } storeKey = normalizeStoreKey(key); if (entry = store[storeKey]) { if (isFresh(entry)) { log("Cache hit for %o", key); return entry.value; } else { log("Discarding stale cache entry for %o", key); remove(key); return fallback; } } else { log("Cache miss for %o", key); return fallback; } }; return { alias: alias, get: get, set: set, remove: remove, clear: clear, keys: keys }; }; /** @function up.util.config @internal */ config = function(factoryOptions) { var hash; if (factoryOptions == null) { factoryOptions = {}; } hash = {}; hash.reset = function() { return extend(hash, factoryOptions); }; hash.reset(); Object.preventExtensions(hash); return hash; }; /** @function up.util.unwrapElement @internal */ unwrapElement = function(wrapper) { var parent, wrappedNodes; wrapper = unJQuery(wrapper); parent = wrapper.parentNode; wrappedNodes = toArray(wrapper.childNodes); each(wrappedNodes, function(wrappedNode) { return parent.insertBefore(wrappedNode, wrapper); }); return parent.removeChild(wrapper); }; /** @function up.util.offsetParent @internal */ offsetParent = function($element) { var $match, position; $match = void 0; while (($element = $element.parent()) && $element.length) { position = $element.css('position'); if (position === 'absolute' || position === 'relative' || $element.is('body')) { $match = $element; break; } } return $match; }; /** @function up.util.fixedToAbsolute @internal */ fixedToAbsolute = function(element, $viewport) { var $element, $futureOffsetParent, elementCoords, futureParentCoords; $element = $(element); $futureOffsetParent = offsetParent($element); elementCoords = $element.position(); futureParentCoords = $futureOffsetParent.offset(); return $element.css({ position: 'absolute', left: elementCoords.left - futureParentCoords.left, top: elementCoords.top - futureParentCoords.top + $viewport.scrollTop(), right: '', bottom: '' }); }; return { offsetParent: offsetParent, fixedToAbsolute: fixedToAbsolute, presentAttr: presentAttr, createElement: createElement, parseUrl: parseUrl, normalizeUrl: normalizeUrl, normalizeMethod: normalizeMethod, createElementFromHtml: createElementFromHtml, $createElementFromSelector: $createElementFromSelector, selectorForElement: selectorForElement, ajax: ajax, extend: extend, copy: copy, merge: merge, options: options, option: option, error: error, debug: debug, warn: warn, each: each, map: map, times: times, any: any, detect: detect, select: select, compact: compact, uniq: uniq, last: last, isNull: isNull, isDefined: isDefined, isUndefined: isUndefined, isGiven: isGiven, isMissing: isMissing, isPresent: isPresent, isBlank: isBlank, presence: presence, isObject: isObject, isFunction: isFunction, isString: isString, isElement: isElement, isJQuery: isJQuery, isPromise: isPromise, isDeferred: isDeferred, isHash: isHash, isUnmodifiedKeyEvent: isUnmodifiedKeyEvent, isUnmodifiedMouseEvent: isUnmodifiedMouseEvent, nullJQuery: nullJQuery, unJQuery: unJQuery, nextFrame: nextFrame, measure: measure, temporaryCss: temporaryCss, cssAnimate: cssAnimate, finishCssAnimate: finishCssAnimate, forceCompositing: forceCompositing, escapePressed: escapePressed, copyAttributes: copyAttributes, findWithSelf: findWithSelf, contains: contains, isArray: isArray, toArray: toArray, castedAttr: castedAttr, locationFromXhr: locationFromXhr, titleFromXhr: titleFromXhr, methodFromXhr: methodFromXhr, clientSize: clientSize, only: only, trim: trim, unresolvablePromise: unresolvablePromise, resolvedPromise: resolvedPromise, resolvedDeferred: resolvedDeferred, resolvableWhen: resolvableWhen, setMissingAttrs: setMissingAttrs, remove: remove, memoize: memoize, scrollbarWidth: scrollbarWidth, config: config, cache: cache, unwrapElement: unwrapElement, multiSelector: multiSelector, evalConsoleTemplate: evalConsoleTemplate }; })($); up.error = up.util.error; up.warn = up.util.warn; up.debug = up.util.debug; }).call(this); /** Browser interface ================= Some browser-interfacing methods and switches that we can't currently get rid off. @class up.browser */ (function() { var slice = [].slice; up.browser = (function($) { var canCssAnimation, canInputEvent, canLogSubstitution, canPushState, initialRequestMethod, isIE8OrWorse, isIE9OrWorse, isRecentJQuery, isSupported, loadPage, popCookie, puts, u, url; u = up.util; loadPage = function(url, options) { var $form, csrfParam, csrfToken, metadataInput, method, target; if (options == null) { options = {}; } method = u.option(options.method, 'get').toLowerCase(); if (method === 'get') { return location.href = url; } else if ($.rails) { target = options.target; csrfToken = $.rails.csrfToken(); csrfParam = $.rails.csrfParam(); $form = $("
"); metadataInput = ""; if (u.isDefined(csrfParam) && u.isDefined(csrfToken)) { metadataInput += ""; } if (target) { $form.attr('target', target); } $form.hide().append(metadataInput).appendTo('body'); return $form.submit(); } else { return error("Can't fake a " + (method.toUpperCase()) + " request without Rails UJS"); } }; /** A cross-browser way to interact with `console.log`, `console.error`, etc. This function falls back to `console.log` if the output stream is not implemented. It also prints substitution strings (e.g. `console.log("From %o to %o", "a", "b")`) as a single string if the browser console does not support substitution strings. \#\#\#\# Example up.browser.puts('log', 'Hi world'); up.browser.puts('error', 'There was an error in %o', obj); @function up.browser.puts @internal */ puts = function() { var args, message, stream; stream = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; u.isDefined(console[stream]) || (stream = 'log'); if (canLogSubstitution()) { return console[stream].apply(console, args); } else { message = u.evalConsoleTemplate.apply(u, args); return console[stream](message); } }; url = function() { return location.href; }; canPushState = u.memoize(function() { return u.isDefined(history.pushState) && initialRequestMethod() === 'get'; }); isIE8OrWorse = u.memoize(function() { return u.isUndefined(document.addEventListener); }); isIE9OrWorse = u.memoize(function() { return isIE8OrWorse() || navigator.appVersion.indexOf('MSIE 9.') !== -1; }); canCssAnimation = u.memoize(function() { return 'transition' in document.documentElement.style; }); canInputEvent = u.memoize(function() { return 'oninput' in document.createElement('input'); }); canLogSubstitution = u.memoize(function() { return !isIE9OrWorse(); }); isRecentJQuery = u.memoize(function() { var major, minor, parts, version; version = $.fn.jquery; parts = version.split('.'); major = parseInt(parts[0]); minor = parseInt(parts[1]); return major >= 2 || (major === 1 && minor >= 9); }); popCookie = function(name) { var ref, value; value = (ref = document.cookie.match(new RegExp(name + "=(\\w+)"))) != null ? ref[1] : void 0; if (u.isPresent(value)) { document.cookie = name + '=; expires=Thu, 01-Jan-70 00:00:01 GMT; path=/'; } return value; }; initialRequestMethod = u.memoize(function() { return (popCookie('_up_request_method') || 'get').toLowerCase(); }); /** Returns whether Up.js supports the current browser. Currently Up.js supports IE9 with jQuery 1.9+. On older browsers Up.js will prevent itself from [booting](/up.boot) and ignores all registered [event handlers](/up.on) and [compilers](/up.compiler). This leaves you with a classic server-side application. @function up.browser.isSupported @experimental */ isSupported = function() { return (!isIE8OrWorse()) && isRecentJQuery(); }; return { url: url, loadPage: loadPage, canPushState: canPushState, canCssAnimation: canCssAnimation, canInputEvent: canInputEvent, canLogSubstitution: canLogSubstitution, isSupported: isSupported, puts: puts }; })(jQuery); }).call(this); /** Events ====== Up.js has a convenient way to [listen to DOM events](/up.on): up.on('click', 'button', function(event, $button) { // $button is a jQuery collection containing // the clicked