/*** @module up */ (function() { window.up = { version: "0.56.0", renamedModule: function(oldName, newName) { return typeof Object.defineProperty === "function" ? Object.defineProperty(up, oldName, { get: function() { up.log.warn("up." + oldName + " has been renamed to up." + newName); return up[newName]; } }) : void 0; } }; }).call(this); /*** Utility functions ================= Unpoly comes with a number of utility functions that might save you from loading something like [Lodash](https://lodash.com/). @class up.util */ (function() { var slice = [].slice, hasProp = {}.hasOwnProperty, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; up.util = (function($) { /*** A function that does nothing. @function up.util.noop @experimental */ var $createElementFromSelector, $createPlaceholder, $submittingButton, CASE_CONVERSION_GROUP, CSS_LENGTH_PROPS, DivertibleChain, ESCAPE_HTML_ENTITY_MAP, addClass, addTemporaryClass, all, always, any, appendRequestData, arrayToSet, assign, assignPolyfill, asyncNoop, attributeSelector, camelCase, camelCaseKeys, castedAttr, changeClassList, clientSize, compact, concludeCssTransition, config, contains, convertCase, copy, copyAttributes, copyWithRenamedKeys, createElementFromHtml, cssLength, detachWith, detect, documentHasVerticalScrollbar, each, escapeHtml, escapePressed, evalOption, except, extractFromStyleObject, extractOptions, fail, fixedToAbsolute, flatten, forceRepaint, getElement, hasClass, hasCssTransition, hide, horizontalScreenHalf, identity, intersect, isArray, isBlank, isBodyDescendant, isCrossDomain, isDefined, isDetached, isElement, isEqual, isFixed, isFormData, isFunction, isGiven, isJQuery, isMissing, isNull, isNumber, isObject, isOptions, isPresent, isPromise, isStandardPort, isString, isTruthy, isUndefined, isUnmodifiedKeyEvent, isUnmodifiedMouseEvent, kebabCase, kebabCaseKeys, last, listBlock, map, margins, measure, memoize, merge, mergeRequestData, methodAllowsPayload, microtask, muteRejection, newDeferred, nextFrame, nonUpClasses, noop, normalizeMethod, normalizeStyleValueForWrite, normalizeUrl, nullJQuery, offsetParent, only, opacity, openConfig, option, options, parseUrl, pluckData, pluckKey, presence, presentAttr, previewable, promiseTimer, readComputedStyle, readComputedStyleNumber, readInlineStyle, reject, rejectOnError, remove, removeClass, renameKey, requestDataAsArray, requestDataAsQuery, requestDataFromForm, scrollbarWidth, select, selectInDynasty, selectInSubtree, selectorForElement, sequence, setMissingAttrs, setTimer, setToArray, submittedValue, times, toArray, trim, uniq, uniqBy, unresolvablePromise, unwrapElement, whenReady, writeInlineStyle, writeTemporaryStyle; noop = (function() {}); /*** A function that returns a resolved promise. @function up.util.asyncNoop @internal */ asyncNoop = function() { return Promise.resolve(); }; /*** Ensures that the given function can only be called a single time. Subsequent calls will return the return value of the first call. Note that this is a simple implementation that doesn't distinguish between argument lists. @function up.util.memoize @internal */ memoize = function(func) { var cached, cachedValue; cachedValue = void 0; cached = false; return function() { var args; args = 1 <= arguments.length ? slice.call(arguments, 0) : []; if (cached) { return cachedValue; } else { cached = true; return cachedValue = func.apply(null, args); } }; }; /*** 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 normalized, parts, pathname; parts = parseUrl(urlOrAnchor); normalized = parts.protocol + "//" + parts.hostname; if (!isStandardPort(parts.protocol, parts.port)) { normalized += ":" + parts.port; } pathname = parts.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 += parts.hash; } if ((options != null ? options.search : void 0) !== false) { normalized += parts.search; } return normalized; }; isCrossDomain = function(targetUrl) { var currentUrl; currentUrl = parseUrl(location.href); targetUrl = parseUrl(targetUrl); return currentUrl.protocol !== targetUrl.protocol || currentUrl.host !== targetUrl.host; }; /*** 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; if (isJQuery(urlOrAnchor)) { urlOrAnchor = getElement(urlOrAnchor); } if (urlOrAnchor.pathname) { return urlOrAnchor; } anchor = $('').attr({ href: urlOrAnchor }).get(0); if (isBlank(anchor.hostname)) { anchor.href = anchor.href; } return anchor; }; /*** @function up.util.normalizeMethod @internal */ normalizeMethod = function(method) { if (method) { return method.toUpperCase(); } else { return 'GET'; } }; /*** @function up.util.methodAllowsPayload @internal */ methodAllowsPayload = function(method) { return method !== 'GET' && method !== 'HEAD'; }; /*** @function $createElementFromSelector @internal */ $createElementFromSelector = function(selector) { var $element, $parent, $root, classes, conjunction, depthSelector, expression, html, id, iteration, j, l, 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 (l = 0, len1 = conjunction.length; l < len1; l++) { expression = conjunction[l]; 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; }; /*** @function $createPlaceHolder @internal */ $createPlaceholder = function(selector, container) { var $placeholder; if (container == null) { container = document.body; } $placeholder = $createElementFromSelector(selector); $placeholder.addClass('up-placeholder'); $placeholder.appendTo(container); return $placeholder; }; /*** 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, ariaLabel, classes, id, j, klass, len, name, selector, tagName, upId; $element = $(element); selector = void 0; tagName = $element.prop('tagName').toLowerCase(); if (upId = presence($element.attr("up-id"))) { selector = attributeSelector('up-id', upId); } else if (id = presence($element.attr("id"))) { if (id.match(/^[a-z0-9\-_]+$/i)) { selector = "#" + id; } else { selector = attributeSelector('id', id); } } else if (name = presence($element.attr("name"))) { selector = tagName + attributeSelector('name', name); } else if (classes = presence(nonUpClasses($element))) { selector = ''; for (j = 0, len = classes.length; j < len; j++) { klass = classes[j]; selector += "." + klass; } } else if (ariaLabel = presence($element.attr("aria-label"))) { selector = attributeSelector('aria-label', ariaLabel); } else { selector = tagName; } return selector; }; attributeSelector = function(attribute, value) { value = value.replace(/"/g, '\\"'); return "[" + attribute + "=\"" + value + "\"]"; }; nonUpClasses = function($element) { var classString, classes; classString = $element.attr('class') || ''; classes = classString.split(' '); return select(classes, function(klass) { return isPresent(klass) && !klass.match(/^up-/); }); }; createElementFromHtml = function(html) { var parser; parser = new DOMParser(); return parser.parseFromString(html, 'text/html'); }; assignPolyfill = function() { var j, key, len, source, sources, target, value; target = arguments[0], sources = 2 <= arguments.length ? slice.call(arguments, 1) : []; for (j = 0, len = sources.length; j < len; j++) { source = sources[j]; for (key in source) { if (!hasProp.call(source, key)) continue; value = source[key]; target[key] = value; } } return target; }; /*** Merge the own properties of one or more `sources` into the `target` object. @function up.util.assign @param {Object} target @param {Array} sources... @stable */ assign = Object.assign || assignPolyfill; /*** 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; listBlock = function(block) { if (isString(block)) { return function(item) { return item[block]; }; } else { return block; } }; /*** Translate all items in an array to new array of items. @function up.util.map @param {Array} array @param {Function(T, number): any|String} block A function that will be called with each element and (optional) iteration index. You can also pass a property name as a String, which will be collected from each item in the array. @return {Array} A new array containing the result of each function call. @stable */ map = function(array, block) { var index, item, j, len, results; if (array.length === 0) { return []; } block = listBlock(block); results = []; for (index = j = 0, len = array.length; j < len; index = ++j) { item = array[index]; results.push(block(item, index)); } return results; }; /*** Calls the given function for each element (and, optional, index) of the given array. @function up.util.each @param {Array} array @param {Function(T, number)} block A function that will be called with each element and (optional) iteration index. @stable */ each = map; /*** 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) { if (isMissing(object)) { return true; } if (isFunction(object)) { return false; } if (isObject(object) && Object.keys(object).length === 0) { return true; } if (object.length === 0) { return true; } return false; }; /*** Returns the given argument if the argument is [present](/up.util.isPresent), otherwise returns `undefined`. @function up.util.presence @param object @param {Function(T): boolean} [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' || object instanceof 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' || object instanceof Number; }; /*** Returns whether the given argument is an options hash, Differently from [`up.util.isObject()`], this returns false for functions, jQuery collections, promises, `FormData` instances and arrays. @function up.util.isOptions @param object @return {boolean} @internal */ isOptions = function(object) { return typeof object === 'object' && !isNull(object) && !isJQuery(object) && !isPromise(object) && !isFormData(object) && !isArray(object); }; /*** Returns whether the given argument is an object. This also returns `true` for functions, which may behave like objects in JavaScript. @function up.util.isObject @param object @return {boolean} @stable */ isObject = function(object) { var typeOfResult; typeOfResult = typeof object; return (typeOfResult === 'object' && !isNull(object)) || typeOfResult === '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 array. @function up.util.isArray @param object @return {boolean} @stable */ isArray = Array.isArray; /*** Returns whether the given argument is a `FormData` instance. Always returns `false` in browsers that don't support `FormData`. @function up.util.isFormData @param object @return {boolean} @internal */ isFormData = function(object) { return object instanceof FormData; }; /*** Converts the given array-like argument into an array. Returns the array. @function up.util.toArray @param object @return {Array} @stable */ toArray = function(object) { return Array.prototype.slice.call(object); }; /*** Returns a shallow copy of the given array or object. @function up.util.copy @param {Object|Array} object @return {Object|Array} @stable */ copy = function(object) { if (isArray(object)) { object = object.slice(); } else if (isObject(object) && !isFunction(object)) { object = assign({}, object); } else { up.fail('Cannot copy %o', object); } return object; }; /*** If given a jQuery collection, returns the first native DOM element in the collection. If given a string, returns the first element matching that string. If given any other argument, returns the argument unchanged. @function up.util.element @param {jQuery|Element|String} object @return {Element} @internal */ getElement = function(object) { if (isJQuery(object)) { return object.get(0); } else if (isString(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 assign.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 (isMissing(value)) { value = defaultValue; } merged[key] = value; } } return merged; }; /*** Returns the first argument that is considered [given](/up.util.isGiven). 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 args; args = 1 <= arguments.length ? slice.call(arguments, 0) : []; return detect(args, isGiven); }; /*** 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(T): boolean} 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(T, number): boolean} tester A function that will be called with each element and (optional) iteration index. @return {boolean} @experimental */ any = function(array, tester) { var element, index, j, len, match; tester = listBlock(tester); match = false; for (index = j = 0, len = array.length; j < len; index = ++j) { element = array[index]; if (tester(element, index)) { match = true; break; } } return match; }; /*** Returns whether the given function returns a truthy value for all elements in the given array. @function up.util.all @param {Array} array @param {Function(T, number): boolean} tester A function that will be called with each element and (optional) iteration index. @return {boolean} @experimental */ all = function(array, tester) { var element, index, j, len, match; tester = listBlock(tester); match = true; for (index = j = 0, len = array.length; j < len; index = ++j) { element = array[index]; if (!tester(element, index)) { match = false; 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) { if (array.length < 2) { return array; } return setToArray(arrayToSet(array)); }; /** This function is like [`uniq`](/up.util.uniq), accept that the given function is invoked for each element to generate the value for which uniquness is computed. @function up.util.uniqBy @param {Array} array @param {Function: any} array @return {Array} @experimental */ uniqBy = function(array, mapper) { var set; if (array.length < 2) { return array; } mapper = listBlock(mapper); set = new Set(); return select(array, function(elem, index) { var mapped; mapped = mapper(elem, index); if (set.has(mapped)) { return false; } else { set.add(mapped); return true; } }); }; /** @function up.util.setToArray @internal */ setToArray = function(set) { var array; array = []; set.forEach(function(elem) { return array.push(elem); }); return array; }; /** @function up.util.arrayToSet @internal */ arrayToSet = function(array) { var set; set = new Set(); array.forEach(function(elem) { return set.add(elem); }); return set; }; /*** 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 @param {Function(T, number): boolean} tester @return {Array} @stable */ select = function(array, tester) { var matches; tester = listBlock(tester); matches = []; each(array, function(element, index) { if (tester(element, index)) { return matches.push(element); } }); return matches; }; /*** Returns all elements from the given array that do not return a truthy value when passed to the given function. @function up.util.reject @param {Array} array @param {Function(T, number): boolean} tester @return {Array} @stable */ reject = function(array, tester) { tester = listBlock(tester); return select(array, function(element, index) { return !tester(element, index); }); }; /*** Returns the intersection of the given two arrays. Implementation is not optimized. Don't use it for large arrays. @function up.util.intersect @internal */ intersect = function(array1, array2) { return select(array1, function(element) { return contains(array2, element); }); }; addClass = function(element, klassOrKlasses) { return changeClassList(element, klassOrKlasses, 'add'); }; removeClass = function(element, klassOrKlasses) { return changeClassList(element, klassOrKlasses, 'remove'); }; changeClassList = function(element, klassOrKlasses, fnName) { var classList; classList = getElement(element).classList; if (isArray(klassOrKlasses)) { return each(klassOrKlasses, function(klass) { return classList[fnName](klass); }); } else { return classList[fnName](klassOrKlasses); } }; addTemporaryClass = function(element, klassOrKlasses) { addClass(element, klassOrKlasses); return function() { return removeClass(element, klassOrKlasses); }; }; hasClass = function(element, klass) { var classList; classList = getElement(element).classList; return classList.contains(klass); }; /*** 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); }; /*** Waits for the given number of milliseconds, the runs the given callback. Instead of `up.util.setTimer(0, fn)` you can also use [`up.util.nextFrame(fn)`](/up.util.nextFrame). @function up.util.setTimer @param {number} millis @param {Function} callback @stable */ setTimer = function(millis, callback) { return setTimeout(callback, millis); }; /*** 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); }; /*** Queue a function to be executed in the next microtask. @function up.util.queueMicrotask @param {Function} task @internal */ microtask = function(task) { return Promise.resolve().then(task); }; /*** 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 = $('
'); outer = $outer.get(0); $outer.attr('up-viewport', ''); writeInlineStyle(outer, { position: 'absolute', top: '0', left: '0', width: '100px', height: '100px', overflowY: 'scroll' }); $outer.appendTo(document.body); width = outer.offsetWidth - outer.clientWidth; $outer.remove(); return width; }); /*** Returns whether the given element is currently showing a vertical scrollbar. @function up.util.documentHasVerticalScrollbar @internal */ documentHasVerticalScrollbar = function() { var $body, body, bodyOverflow, forcedHidden, forcedScroll, html; body = document.body; $body = $(body); html = document.documentElement; bodyOverflow = readComputedStyle($body, 'overflowY'); forcedScroll = bodyOverflow === 'scroll'; forcedHidden = bodyOverflow === 'hidden'; return forcedScroll || (!forcedHidden && html.scrollHeight > html.clientHeight); }; /*** Temporarily sets the CSS for the given element. @function up.util.writeTemporaryStyle @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 */ writeTemporaryStyle = function(elementOrSelector, newCss, block) { var $element, oldStyles, restoreOldStyles; $element = $(elementOrSelector); oldStyles = readInlineStyle($element, Object.keys(newCss)); restoreOldStyles = function() { return writeInlineStyle($element, oldStyles); }; writeInlineStyle($element, newCss); if (block) { block(); return restoreOldStyles(); } else { return restoreOldStyles; } }; /*** Forces a repaint of the given element. @function up.util.forceRepaint @internal */ forceRepaint = function(element) { element = getElement(element); return element.offsetHeight; }; /** @function up.util.finishTransition @internal */ concludeCssTransition = function(element) { var undo; undo = writeTemporaryStyle(element, { transition: 'none' }); forceRepaint(element); return undo; }; /*** @internal */ margins = function(selectorOrElement) { var element; element = getElement(selectorOrElement); return { top: readComputedStyleNumber(element, 'marginTop'), right: readComputedStyleNumber(element, 'marginRight'), bottom: readComputedStyleNumber(element, 'marginBottom'), left: readComputedStyleNumber(element, 'marginLeft') }; }; /*** Measures the given element. @function up.util.measure @internal */ measure = function($element, opts) { var $context, box, contextCoords, coordinates, elementCoords, mgs; opts = options(opts, { relative: false, inner: false, includeMargin: 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.includeMargin) { mgs = margins($element); box.left -= mgs.left; box.top -= mgs.top; box.height += mgs.top + mgs.bottom; box.width += mgs.left + mgs.right; } 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.selectInSubtree @internal */ selectInSubtree = function($element, selector) { var $matches; $matches = $(); if ($element.is(selector)) { $matches = $matches.add($element); } $matches = $matches.add($element.find(selector)); return $matches; }; /*** Looks for the given selector in the element, its descendants and its ancestors. @function up.util.selectInDynasty @internal */ selectInDynasty = function($element, selector) { var $ancestors, $subtree; $subtree = selectInSubtree($element, selector); $ancestors = $element.parents(selector); return $subtree.add($ancestors); }; /*** 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': case '': case attrName: return true; default: return value; } }; /*** 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 (property in object) { filtered[property] = object[property]; } } return filtered; }; /*** Returns a copy of the given object that contains all except the given properties. @function up.util.except @param {Object} object @param {Array} keys... @stable */ except = function() { var filtered, j, len, object, properties, property; object = arguments[0], properties = 2 <= arguments.length ? slice.call(arguments, 1) : []; filtered = copy(object); for (j = 0, len = properties.length; j < len; j++) { property = properties[j]; delete filtered[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 promise that will never be resolved. @function up.util.unresolvablePromise @experimental */ unresolvablePromise = function() { return new Promise(noop); }; /*** Returns an empty jQuery collection. @function up.util.nullJQuery @internal */ nullJQuery = function() { return $(); }; /*** 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; } }; /*** If the given `value` is a function, calls the function with the given `args`. Otherwise it just returns `value`. @function up.util.evalOption @internal */ evalOption = function() { var args, value; value = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; if (isFunction(value)) { return value.apply(null, args); } else { return value; } }; /*** @function up.util.config @param {Object|Function} blueprint Default configuration options. Will be restored by calling `reset` on the returned object. @return {Object} An object with a `reset` function. @internal */ config = function(blueprint) { var hash; hash = openConfig(blueprint); Object.preventExtensions(hash); return hash; }; /*** @function up.util.openConfig @internal */ openConfig = function(blueprint) { var hash; if (blueprint == null) { blueprint = {}; } hash = {}; hash.reset = function() { var newOptions; newOptions = blueprint; if (isFunction(newOptions)) { newOptions = newOptions(); } return assign(hash, newOptions); }; hash.reset(); return hash; }; /*** @function up.util.unwrapElement @internal */ unwrapElement = function(wrapper) { var parent, wrappedNodes; wrapper = getElement(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 = readComputedStyle($element, 'position'); if (position === 'absolute' || position === 'relative' || $element.is('body')) { $match = $element; break; } } return $match; }; /*** Returns if the given element has a `fixed` position. @function up.util.isFixed @internal */ isFixed = function(element) { var $element, position; $element = $(element); while (true) { position = readComputedStyle($element, 'position'); if (position === 'fixed') { return true; } else { $element = $element.parent(); if ($element.length === 0 || $element.is(document)) { return false; } } } }; /*** @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 writeInlineStyle($element, { position: 'absolute', left: elementCoords.left - futureParentCoords.left, top: elementCoords.top - futureParentCoords.top + $viewport.scrollTop(), right: '', bottom: '' }); }; /*** Normalizes the given params object to the form returned by [`jQuery.serializeArray`](https://api.jquery.com/serializeArray/). @function up.util.requestDataAsArray @param {Object|Array|undefined|null} data @internal */ requestDataAsArray = function(data) { var array, j, len, pair, part, query, ref; if (isArray(data)) { data; } if (isFormData(data)) { return up.fail('Cannot convert FormData into an array'); } else { query = requestDataAsQuery(data); array = []; ref = query.split('&'); for (j = 0, len = ref.length; j < len; j++) { part = ref[j]; if (isPresent(part)) { pair = part.split('='); array.push({ name: decodeURIComponent(pair[0]), value: decodeURIComponent(pair[1]) }); } } return array; } }; /*** Returns an URL-encoded query string for the given params object. The returned string does **not** include a leading `?` character. @function up.util.requestDataAsQuery @param {Object|Array|undefined|null} data @internal */ requestDataAsQuery = function(data, opts) { var query; opts = options(opts, { purpose: 'url' }); if (isString(data)) { return data.replace(/^\?/, ''); } else if (isFormData(data)) { return up.fail('Cannot convert FormData into a query string'); } else if (isPresent(data)) { query = $.param(data); switch (opts.purpose) { case 'url': query = query.replace(/\+/g, '%20'); break; case 'form': query = query.replace(/\%20/g, '+'); break; default: up.fail('Unknown purpose %o', opts.purpose); } return query; } else { return ""; } }; $submittingButton = function($form) { var $activeElement, submitButtonSelector; submitButtonSelector = 'input[type=submit], button[type=submit], button:not([type])'; $activeElement = $(document.activeElement); if ($activeElement.is(submitButtonSelector) && $form.has($activeElement)) { return $activeElement; } else { return $form.find(submitButtonSelector).first(); } }; /*** Serializes the given form into a request data representation. @function up.util.requestDataFromForm @return {Array|FormData} @internal */ requestDataFromForm = function(form) { var $button, $form, buttonName, buttonValue, data, hasFileInputs; $form = $(form); hasFileInputs = $form.find('input[type=file]').length; $button = $submittingButton($form); buttonName = $button.attr('name'); buttonValue = $button.val(); data = hasFileInputs ? new FormData($form.get(0)) : $form.serializeArray(); if (isPresent(buttonName)) { appendRequestData(data, buttonName, buttonValue); } return data; }; /*** Adds a key/value pair to the given request data representation. This mutates the given `data` if `data` is a `FormData`, an object or an array. When `data` is a string a new string with the appended key/value pair is returned. @function up.util.appendRequestData @param {FormData|Object|Array|undefined|null} data @param {string} key @param {string|Blob|File} value @internal */ appendRequestData = function(data, name, value, opts) { var newPair; data || (data = []); if (isArray(data)) { data.push({ name: name, value: value }); } else if (isFormData(data)) { data.append(name, value); } else if (isObject(data)) { data[name] = value; } else if (isString(data)) { newPair = requestDataAsQuery([ { name: name, value: value } ], opts); data = [data, newPair].join('&'); } return data; }; /*** Merges the request data in `source` into `target`. Will modify the passed-in `target`. @return The merged form data. */ mergeRequestData = function(target, source) { each(requestDataAsArray(source), function(field) { return target = appendRequestData(target, field.name, field.value); }); return target; }; /*** Throws a [JavaScript error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) with the given message. The message will also be printed to the [error log](/up.log.error). Also a notification will be shown at the bottom of the screen. The message may contain [substitution marks](https://developer.mozilla.org/en-US/docs/Web/API/console#Using_string_substitutions). \#\#\# Examples up.fail('Division by zero') up.fail('Unexpected result %o', result) @function up.fail @param {string} message A message with details about the error. The message can contain [substitution marks](https://developer.mozilla.org/en-US/docs/Web/API/console#Using_string_substitutions) like `%s` or `%o`. @param {Array} vars... A list of variables to replace any substitution marks in the error message. @experimental */ fail = function() { var args, asString, messageArgs, ref, ref1, toastOptions; args = 1 <= arguments.length ? slice.call(arguments, 0) : []; if (isArray(args[0])) { messageArgs = args[0]; toastOptions = args[1] || {}; } else { messageArgs = args; toastOptions = {}; } (ref = up.log).error.apply(ref, messageArgs); whenReady().then(function() { return up.toast.open(messageArgs, toastOptions); }); asString = (ref1 = up.browser).sprintf.apply(ref1, messageArgs); throw new Error(asString); }; ESCAPE_HTML_ENTITY_MAP = { "&": "&", "<": "<", ">": ">", '"': '"' }; /*** Escapes the given string of HTML by replacing control chars with their HTML entities. @function up.util.escapeHtml @param {string} string The text that should be escaped @experimental */ escapeHtml = function(string) { return string.replace(/[&<>"]/g, function(char) { return ESCAPE_HTML_ENTITY_MAP[char]; }); }; pluckKey = function(object, key) { var value; value = object[key]; delete object[key]; return value; }; renameKey = function(object, oldKey, newKey) { return object[newKey] = pluckKey(object, oldKey); }; pluckData = function(elementOrSelector, key) { var $element, value; $element = $(elementOrSelector); value = $element.data(key); $element.removeData(key); return value; }; extractOptions = function(args) { var lastArg; lastArg = last(args); if (isOptions(lastArg)) { return args.pop(); } else { return {}; } }; CASE_CONVERSION_GROUP = /[^\-\_]+?(?=[A-Z\-\_]|$)/g; convertCase = function(string, separator, fn) { var parts; parts = string.match(CASE_CONVERSION_GROUP); parts = map(parts, fn); return parts.join(separator); }; /*** Returns a copy of the given string that is transformed to `kebab-case`. @function up.util.kebabCase @param {string} string @return {string} @internal */ kebabCase = function(string) { return convertCase(string, '-', function(part) { return part.toLowerCase(); }); }; /*** Returns a copy of the given string that is transformed to `camelCase`. @function up.util.camelCase @param {string} string @return {string} @internal */ camelCase = function(string) { return convertCase(string, '', function(part, i) { if (i === 0) { return part.toLowerCase(); } else { return part.charAt(0).toUpperCase() + part.substr(1).toLowerCase(); } }); }; /*** Returns a copy of the given object with all keys renamed in `kebab-case`. Does not change the given object. @function up.util.kebabCaseKeys @param {object} obj @return {object} @internal */ kebabCaseKeys = function(obj) { return copyWithRenamedKeys(obj, kebabCase); }; /*** Returns a copy of the given object with all keys renamed in `camelCase`. Does not change the given object. @function up.util.camelCaseKeys @param {object} obj @return {object} @internal */ camelCaseKeys = function(obj) { return copyWithRenamedKeys(obj, camelCase); }; copyWithRenamedKeys = function(obj, keyTransformer) { var k, result, v; result = {}; for (k in obj) { v = obj[k]; k = keyTransformer(k); result[k] = v; } return result; }; opacity = function(element) { return readComputedStyleNumber(element, 'opacity'); }; whenReady = memoize(function() { if ($.isReady) { return Promise.resolve(); } else { return new Promise(function(resolve) { return $(resolve); }); } }); identity = function(arg) { return arg; }; /*** Returns whether the given element has been detached from the DOM (or whether it was never attached). @function up.util.isDetached @internal */ isDetached = function(element) { element = getElement(element); return !$.contains(document.documentElement, element); }; /*** Given a function that will return a promise, returns a proxy function with an additional `.promise` attribute. When the proxy is called, the inner function is called. The proxy's `.promise` attribute is available even before the function is called and will resolve when the inner function's returned promise resolves. If the inner function does not return a promise, the proxy's `.promise` attribute will resolve as soon as the inner function returns. @function up.util.previewable @internal */ previewable = function(fun) { var deferred, preview; deferred = newDeferred(); preview = function() { var args, funValue; args = 1 <= arguments.length ? slice.call(arguments, 0) : []; funValue = fun.apply(null, args); deferred.resolve(funValue); return funValue; }; preview.promise = deferred.promise(); return preview; }; /*** A linear task queue whose (2..n)th tasks can be changed at any time. @function up.util.DivertibleChain @internal */ DivertibleChain = (function() { function DivertibleChain() { this.asap = bind(this.asap, this); this.poke = bind(this.poke, this); this.allTasks = bind(this.allTasks, this); this.promise = bind(this.promise, this); this.reset = bind(this.reset, this); this.reset(); } DivertibleChain.prototype.reset = function() { this.queue = []; return this.currentTask = void 0; }; DivertibleChain.prototype.promise = function() { var lastTask; lastTask = last(this.allTasks()); return (lastTask != null ? lastTask.promise : void 0) || Promise.resolve(); }; DivertibleChain.prototype.allTasks = function() { var tasks; tasks = []; if (this.currentTask) { tasks.push(this.currentTask); } tasks = tasks.concat(this.queue); return tasks; }; DivertibleChain.prototype.poke = function() { var promise; if (!this.currentTask) { if (this.currentTask = this.queue.shift()) { promise = this.currentTask(); return always(promise, (function(_this) { return function() { _this.currentTask = void 0; return _this.poke(); }; })(this)); } } }; DivertibleChain.prototype.asap = function() { var newTasks; newTasks = 1 <= arguments.length ? slice.call(arguments, 0) : []; this.queue = map(newTasks, previewable); this.poke(); return this.promise(); }; return DivertibleChain; })(); /*** @function up.util.submittedValue @internal */ submittedValue = function(fieldOrSelector) { var $field; $field = $(fieldOrSelector); if ($field.is('[type=checkbox], [type=radio]') && !$field.is(':checked')) { return void 0; } else { return $field.val(); } }; /*** @function up.util.sequence @param {Array} functions... @return {Function} A function that will call all `functions` if called. @internal */ sequence = function() { var functions; functions = 1 <= arguments.length ? slice.call(arguments, 0) : []; return function() { return map(functions, function(f) { return f(); }); }; }; /*** @function up.util.promiseTimer @internal */ promiseTimer = function(ms) { var promise, timeout; timeout = void 0; promise = new Promise(function(resolve, reject) { return timeout = setTimer(ms, resolve); }); promise.cancel = function() { return clearTimeout(timeout); }; return promise; }; /*** Returns `'left'` if the center of the given element is in the left 50% of the screen. Otherwise returns `'right'`. @function up.util.horizontalScreenHalf @internal */ horizontalScreenHalf = function($element) { var elementDims, elementMid, screenDims, screenMid; elementDims = measure($element); screenDims = clientSize(); elementMid = elementDims.left + 0.5 * elementDims.width; screenMid = 0.5 * screenDims.width; if (elementMid < screenMid) { return 'left'; } else { return 'right'; } }; /*** Like `$old.replaceWith($new)`, but keeps event handlers bound to `$old`. Note that this is a memory leak unless you re-attach `$old` to the DOM aferwards. @function up.util.detachWith @internal */ detachWith = function($old, $new) { var $insertion; $insertion = $('
'); $insertion.insertAfter($old); $old.detach(); $insertion.replaceWith($new); return $old; }; /*** Hides the given element faster than `jQuery.fn.hide()`. @function up.util.hide @param {jQuery|Element} element */ hide = function(element) { return writeInlineStyle(element, { display: 'none' }); }; /*** Gets the computed style(s) for the given element. @function up.util.readComputedStyle @param {jQuery|Element} element @param {String|Array} propOrProps One or more CSS property names in camelCase. @return {string|object} @internal */ readComputedStyle = function(element, props) { var style; element = getElement(element); style = window.getComputedStyle(element); return extractFromStyleObject(style, props); }; /*** Gets a computed style value for the given element. If a value is set, the value is parsed to a number before returning. @function up.util.readComputedStyleNumber @param {jQuery|Element} element @param {String} prop A CSS property name in camelCase. @return {string|object} @internal */ readComputedStyleNumber = function(element, prop) { var rawValue; rawValue = readComputedStyle(element, prop); if (isGiven(rawValue)) { return parseFloat(rawValue); } else { return void 0; } }; /*** Gets the given inline style(s) from the given element's `[style]` attribute. @function up.util.readInlineStyle @param {jQuery|Element} element @param {String|Array} propOrProps One or more CSS property names in camelCase. @return {string|object} @internal */ readInlineStyle = function(element, props) { var style; element = getElement(element); style = element.style; return extractFromStyleObject(style, props); }; extractFromStyleObject = function(style, keyOrKeys) { if (isString(keyOrKeys)) { return style[keyOrKeys]; } else { return only.apply(null, [style].concat(slice.call(keyOrKeys))); } }; /*** Merges the given inline style(s) into the given element's `[style]` attribute. @function up.util.readInlineStyle @param {jQuery|Element} element @param {Object} props One or more CSS properties with camelCase keys. @return {string|object} @internal */ writeInlineStyle = function(element, props) { var key, results, style, value; element = getElement(element); style = element.style; results = []; for (key in props) { value = props[key]; value = normalizeStyleValueForWrite(key, value); results.push(style[key] = value); } return results; }; normalizeStyleValueForWrite = function(key, value) { if (isMissing(value)) { value = ''; } else if (CSS_LENGTH_PROPS.has(key)) { value = cssLength(value); } return value; }; CSS_LENGTH_PROPS = arrayToSet(['top', 'right', 'bottom', 'left', 'padding', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft', 'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft', 'width', 'height', 'maxWidth', 'maxHeight', 'minWidth', 'minHeight']); /** Converts the given value to a CSS length value, adding a `px` unit if required. @function up.util.cssLength @internal */ cssLength = function(obj) { if (isNumber(obj) || (isString(obj) && /^\d+$/.test(obj))) { return obj.toString() + "px"; } else { return obj; } }; /** Returns whether the given element has a CSS transition set. @function up.util.hasCssTransition @return {boolean} @internal */ hasCssTransition = function(elementOrStyleHash) { var duration, element, noTransition, prop, style; if (isOptions(elementOrStyleHash)) { style = elementOrStyleHash; } else { element = getElement(element); style = getComputedStyle(element); } prop = style.transitionProperty; duration = style.transitionDuration; noTransition = prop === 'none' || (prop === 'all' && duration === 0); return !noTransition; }; /*** Flattens the given `array` a single level deep. @function up.util.flatten @param {Array} array An array which might contain other arrays @return {Array} The flattened array @internal */ flatten = function(array) { var flattened, j, len, object; flattened = []; for (j = 0, len = array.length; j < len; j++) { object = array[j]; if (isArray(object)) { flattened = flattened.concat(object); } else { flattened.push(object); } } return flattened; }; /*** Returns whether the given value is truthy. @function up.util.isTruthy @internal */ isTruthy = function(object) { return !!object; }; /*** Sets the given callback as both fulfillment and rejection handler for the given promise. @function up.util.always @internal */ always = function(promise, callback) { return promise.then(callback, callback); }; /*** * Registers an empty rejection handler with the given promise. * This prevents browsers from printing "Uncaught (in promise)" to the error * console when the promise is rejection. # * This is helpful for event handlers where it is clear that no rejection * handler will be registered: # * up.on('submit', 'form[up-target]', (event, $form) => { * promise = up.submit($form) * up.util.muteRejection(promise) * }) # * Does nothing if passed a missing value. # * @function up.util.muteRejection * @param {Promise|undefined|null} promise * @return {Promise} */ muteRejection = function(promise) { return promise != null ? promise["catch"](noop) : void 0; }; /*** @function up.util.newDeferred @internal */ newDeferred = function() { var nativePromise, resolve; resolve = void 0; reject = void 0; nativePromise = new Promise(function(givenResolve, givenReject) { resolve = givenResolve; return reject = givenReject; }); nativePromise.resolve = resolve; nativePromise.reject = reject; nativePromise.promise = function() { return nativePromise; }; return nativePromise; }; /*** Calls the given block. If the block throws an exception, a rejected promise is returned instead. @function up.util.rejectOnError @internal */ rejectOnError = function(block) { var error; try { return block(); } catch (_error) { error = _error; return Promise.reject(error); } }; /*** Returns whether the given element is a descendant of the `` element. @function up.util.isBodyDescendant @internal */ isBodyDescendant = function(element) { return $(element).parents('body').length > 0; }; isEqual = function(a, b) { if (typeof a !== typeof b) { return false; } else if (isArray(a)) { return a.length === b.length && all(a, function(elem, index) { return isEqual(elem, b[index]); }); } else if (isObject(a)) { return fail('isEqual cannot compare objects yet'); } else { return a === b; } }; return { requestDataAsArray: requestDataAsArray, requestDataAsQuery: requestDataAsQuery, appendRequestData: appendRequestData, mergeRequestData: mergeRequestData, requestDataFromForm: requestDataFromForm, offsetParent: offsetParent, fixedToAbsolute: fixedToAbsolute, isFixed: isFixed, presentAttr: presentAttr, parseUrl: parseUrl, normalizeUrl: normalizeUrl, normalizeMethod: normalizeMethod, methodAllowsPayload: methodAllowsPayload, createElementFromHtml: createElementFromHtml, $createElementFromSelector: $createElementFromSelector, $createPlaceholder: $createPlaceholder, selectorForElement: selectorForElement, assign: assign, assignPolyfill: assignPolyfill, copy: copy, merge: merge, options: options, option: option, fail: fail, each: each, map: map, times: times, any: any, all: all, detect: detect, select: select, reject: reject, intersect: intersect, compact: compact, uniq: uniq, uniqBy: uniqBy, last: last, isNull: isNull, isDefined: isDefined, isUndefined: isUndefined, isGiven: isGiven, isMissing: isMissing, isPresent: isPresent, isBlank: isBlank, presence: presence, isObject: isObject, isFunction: isFunction, isString: isString, isNumber: isNumber, isElement: isElement, isJQuery: isJQuery, isPromise: isPromise, isOptions: isOptions, isArray: isArray, isFormData: isFormData, isUnmodifiedKeyEvent: isUnmodifiedKeyEvent, isUnmodifiedMouseEvent: isUnmodifiedMouseEvent, nullJQuery: nullJQuery, element: getElement, setTimer: setTimer, nextFrame: nextFrame, measure: measure, addClass: addClass, removeClass: removeClass, hasClass: hasClass, addTemporaryClass: addTemporaryClass, writeTemporaryStyle: writeTemporaryStyle, forceRepaint: forceRepaint, concludeCssTransition: concludeCssTransition, escapePressed: escapePressed, copyAttributes: copyAttributes, selectInSubtree: selectInSubtree, selectInDynasty: selectInDynasty, contains: contains, toArray: toArray, castedAttr: castedAttr, clientSize: clientSize, only: only, except: except, trim: trim, unresolvablePromise: unresolvablePromise, setMissingAttrs: setMissingAttrs, remove: remove, memoize: memoize, scrollbarWidth: scrollbarWidth, documentHasVerticalScrollbar: documentHasVerticalScrollbar, config: config, openConfig: openConfig, unwrapElement: unwrapElement, camelCase: camelCase, camelCaseKeys: camelCaseKeys, kebabCase: kebabCase, kebabCaseKeys: kebabCaseKeys, error: fail, pluckData: pluckData, pluckKey: pluckKey, renameKey: renameKey, extractOptions: extractOptions, isDetached: isDetached, noop: noop, asyncNoop: asyncNoop, opacity: opacity, whenReady: whenReady, identity: identity, escapeHtml: escapeHtml, DivertibleChain: DivertibleChain, submittedValue: submittedValue, sequence: sequence, promiseTimer: promiseTimer, previewable: previewable, evalOption: evalOption, horizontalScreenHalf: horizontalScreenHalf, detachWith: detachWith, flatten: flatten, isTruthy: isTruthy, newDeferred: newDeferred, always: always, muteRejection: muteRejection, rejectOnError: rejectOnError, isBodyDescendant: isBodyDescendant, isCrossDomain: isCrossDomain, microtask: microtask, isEqual: isEqual, hide: hide, cssLength: cssLength, readComputedStyle: readComputedStyle, readComputedStyleNumber: readComputedStyleNumber, readInlineStyle: readInlineStyle, writeInlineStyle: writeInlineStyle, hasCssTransition: hasCssTransition }; })(jQuery); up.fail = up.util.fail; }).call(this); (function() { var u, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, slice = [].slice; u = up.util; /*** @class up.Cache @internal */ up.Cache = (function() { /*** @constructor @param {number|Function() :number} [config.size] Maximum number of cache entries. Set to `undefined` to not limit the cache size. @param {number|Function(): number} [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(any): string} [config.key] A function that takes an argument and returns a string key for storage. If omitted, `toString()` is called on the argument. @param {Function(any): boolean} [config.cachable] A function that takes a potential cache entry and returns whether this entry can be stored in the hash. If omitted, all entries are considered cachable. */ function Cache(config) { this.config = config != null ? config : {}; this.get = bind(this.get, this); this.isFresh = bind(this.isFresh, this); this.remove = bind(this.remove, this); this.set = bind(this.set, this); this.timestamp = bind(this.timestamp, this); this.alias = bind(this.alias, this); this.makeRoomForAnotherKey = bind(this.makeRoomForAnotherKey, this); this.keys = bind(this.keys, this); this.log = bind(this.log, this); this.clear = bind(this.clear, this); this.isCachable = bind(this.isCachable, this); this.isEnabled = bind(this.isEnabled, this); this.normalizeStoreKey = bind(this.normalizeStoreKey, this); this.expiryMillis = bind(this.expiryMillis, this); this.maxKeys = bind(this.maxKeys, this); this.store = {}; } Cache.prototype.maxKeys = function() { return u.evalOption(this.config.size); }; Cache.prototype.expiryMillis = function() { return u.evalOption(this.config.expiry); }; Cache.prototype.normalizeStoreKey = function(key) { if (this.config.key) { return this.config.key(key); } else { return this.key.toString(); } }; Cache.prototype.isEnabled = function() { return this.maxKeys() !== 0 && this.expiryMillis() !== 0; }; Cache.prototype.isCachable = function(key) { if (this.config.cachable) { return this.config.cachable(key); } else { return true; } }; Cache.prototype.clear = function() { return this.store = {}; }; Cache.prototype.log = function() { var args; args = 1 <= arguments.length ? slice.call(arguments, 0) : []; if (this.config.logPrefix) { args[0] = "[" + this.config.logPrefix + "] " + args[0]; return up.puts.apply(up, args); } }; Cache.prototype.keys = function() { return Object.keys(this.store); }; Cache.prototype.makeRoomForAnotherKey = function() { var max, oldestKey, oldestTimestamp, storeKeys; storeKeys = u.copy(this.keys()); max = this.maxKeys(); if (max && storeKeys.length >= max) { oldestKey = null; oldestTimestamp = null; u.each(storeKeys, (function(_this) { return function(key) { var promise, timestamp; promise = _this.store[key]; timestamp = promise.timestamp; if (!oldestTimestamp || oldestTimestamp > timestamp) { oldestKey = key; return oldestTimestamp = timestamp; } }; })(this)); if (oldestKey) { return delete this.store[oldestKey]; } } }; Cache.prototype.alias = function(oldKey, newKey) { var value; value = this.get(oldKey, { silent: true }); if (u.isDefined(value)) { return this.set(newKey, value); } }; Cache.prototype.timestamp = function() { return (new Date()).valueOf(); }; Cache.prototype.set = function(key, value) { var storeKey; if (this.isEnabled() && this.isCachable(key)) { this.makeRoomForAnotherKey(); storeKey = this.normalizeStoreKey(key); this.log("Setting entry %o to %o", storeKey, value); return this.store[storeKey] = { timestamp: this.timestamp(), value: value }; } }; Cache.prototype.remove = function(key) { var storeKey; if (this.isCachable(key)) { storeKey = this.normalizeStoreKey(key); return delete this.store[storeKey]; } }; Cache.prototype.isFresh = function(entry) { var millis, timeSinceTouch; millis = this.expiryMillis(); if (millis) { timeSinceTouch = this.timestamp() - entry.timestamp; return timeSinceTouch < millis; } else { return true; } }; Cache.prototype.get = function(key, options) { var entry; if (options == null) { options = {}; } if (this.isCachable(key) && (entry = this.store[this.normalizeStoreKey(key)])) { if (this.isFresh(entry)) { if (!options.silent) { this.log("Cache hit for '%s'", key); } return entry.value; } else { if (!options.silent) { this.log("Discarding stale cache entry for '%s'", key); } this.remove(key); return void 0; } } else { if (!options.silent) { this.log("Cache miss for '%s'", key); } return void 0; } }; return Cache; })(); }).call(this); (function() { var u, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; u = up.util; up.CssTransition = (function() { function CssTransition($element, lastFrame, options) { this.startMotion = bind(this.startMotion, this); this.resumeOldTransition = bind(this.resumeOldTransition, this); this.pauseOldTransition = bind(this.pauseOldTransition, this); this.finish = bind(this.finish, this); this.onTransitionEnd = bind(this.onTransitionEnd, this); this.stopListenToTransitionEnd = bind(this.stopListenToTransitionEnd, this); this.listenToTransitionEnd = bind(this.listenToTransitionEnd, this); this.stopFallbackTimer = bind(this.stopFallbackTimer, this); this.startFallbackTimer = bind(this.startFallbackTimer, this); this.onFinishEvent = bind(this.onFinishEvent, this); this.stopListenToFinishEvent = bind(this.stopListenToFinishEvent, this); this.listenToFinishEvent = bind(this.listenToFinishEvent, this); this.start = bind(this.start, this); this.$element = $element; this.element = u.element($element); this.lastFrameCamel = u.camelCaseKeys(lastFrame); this.lastFrameKebab = u.kebabCaseKeys(lastFrame); this.lastFrameKeysKebab = Object.keys(this.lastFrameKebab); this.finishEvent = options.finishEvent; this.duration = options.duration; this.delay = options.delay; this.totalDuration = this.delay + this.duration; this.easing = options.easing; this.finished = false; } CssTransition.prototype.start = function() { if (this.lastFrameKeysKebab.length === 0) { this.finished = true; return Promise.resolve(); } this.deferred = u.newDeferred(); this.pauseOldTransition(); this.startTime = new Date(); this.startFallbackTimer(); this.listenToFinishEvent(); this.listenToTransitionEnd(); this.startMotion(); return this.deferred.promise(); }; CssTransition.prototype.listenToFinishEvent = function() { if (this.finishEvent) { return this.$element.on(this.finishEvent, this.onFinishEvent); } }; CssTransition.prototype.stopListenToFinishEvent = function() { if (this.finishEvent) { return this.$element.off(this.finishEvent, this.onFinishEvent); } }; CssTransition.prototype.onFinishEvent = function(event) { event.stopPropagation(); return this.finish(); }; CssTransition.prototype.startFallbackTimer = function() { var timingTolerance; timingTolerance = 100; return this.fallbackTimer = u.setTimer(this.totalDuration + timingTolerance, (function(_this) { return function() { return _this.finish(); }; })(this)); }; CssTransition.prototype.stopFallbackTimer = function() { return clearTimeout(this.fallbackTimer); }; CssTransition.prototype.listenToTransitionEnd = function() { return this.$element.on('transitionend', this.onTransitionEnd); }; CssTransition.prototype.stopListenToTransitionEnd = function() { return this.$element.off('transitionend', this.onTransitionEnd); }; CssTransition.prototype.onTransitionEnd = function(event) { var completedPropertyKebab, elapsed; if (event.target !== this.element) { return; } elapsed = new Date() - this.startTime; if (!(elapsed > 0.25 * this.totalDuration)) { return; } completedPropertyKebab = event.originalEvent.propertyName; if (!u.contains(this.lastFrameKeysKebab, completedPropertyKebab)) { return; } return this.finish(); }; CssTransition.prototype.finish = function() { if (this.finished) { return; } this.finished = true; this.stopFallbackTimer(); this.stopListenToFinishEvent(); this.stopListenToTransitionEnd(); u.concludeCssTransition(this.element); this.resumeOldTransition(); return this.deferred.resolve(); }; CssTransition.prototype.pauseOldTransition = function() { var oldTransition, oldTransitionFrameCamel, oldTransitionFrameKebab, oldTransitionProperties; oldTransition = u.readComputedStyle(this.element, ['transitionProperty', 'transitionDuration', 'transitionDelay', 'transitionTimingFunction']); if (u.hasCssTransition(oldTransition)) { if (oldTransition.transitionProperty !== 'all') { oldTransitionProperties = oldTransition.transitionProperty.split(/\s*,\s*/); oldTransitionFrameKebab = u.readComputedStyle(this.element, oldTransitionProperties); oldTransitionFrameCamel = u.camelCaseKeys(oldTransitionFrameKebab); this.setOldTransitionTargetFrame = u.writeTemporaryStyle(this.element, oldTransitionFrameCamel); } return this.setOldTransition = u.concludeCssTransition(this.element); } }; CssTransition.prototype.resumeOldTransition = function() { if (typeof this.setOldTransitionTargetFrame === "function") { this.setOldTransitionTargetFrame(); } return typeof this.setOldTransition === "function" ? this.setOldTransition() : void 0; }; CssTransition.prototype.startMotion = function() { u.writeInlineStyle(this.element, { transitionProperty: Object.keys(this.lastFrameKebab).join(', '), transitionDuration: this.duration + "ms", transitionDelay: this.delay + "ms", transitionTimingFunction: this.easing }); return u.writeInlineStyle(this.element, this.lastFrameCamel); }; return CssTransition; })(); }).call(this); (function() { var u, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; u = up.util; up.ExtractCascade = (function() { function ExtractCascade(selector, options) { this.oldPlanNotFound = bind(this.oldPlanNotFound, this); this.matchingPlanNotFound = bind(this.matchingPlanNotFound, this); this.bestMatchingSteps = bind(this.bestMatchingSteps, this); this.bestPreflightSelector = bind(this.bestPreflightSelector, this); this.detectPlan = bind(this.detectPlan, this); this.matchingPlan = bind(this.matchingPlan, this); this.newPlan = bind(this.newPlan, this); this.oldPlan = bind(this.oldPlan, this); this.options = u.options(options, { humanizedTarget: 'selector', layer: 'auto' }); this.options.transition = u.option(this.options.transition, this.options.animation); this.options.hungry = u.option(this.options.hungry, true); this.candidates = this.buildCandidates(selector); this.plans = u.map(this.candidates, (function(_this) { return function(candidate, i) { var planOptions; planOptions = u.copy(_this.options); if (i > 0) { planOptions.transition = u.option(up.dom.config.fallbackTransition, _this.options.transition); } return new up.ExtractPlan(candidate, planOptions); }; })(this)); } ExtractCascade.prototype.buildCandidates = function(selector) { var candidates; candidates = [selector, this.options.fallback, up.dom.config.fallbacks]; candidates = u.flatten(candidates); candidates = u.select(candidates, u.isTruthy); candidates = u.uniq(candidates); if (this.options.fallback === false || this.options.provideTarget) { candidates = [candidates[0]]; } return candidates; }; ExtractCascade.prototype.oldPlan = function() { return this.detectPlan('oldExists'); }; ExtractCascade.prototype.newPlan = function() { return this.detectPlan('newExists'); }; ExtractCascade.prototype.matchingPlan = function() { return this.detectPlan('matchExists'); }; ExtractCascade.prototype.detectPlan = function(checker) { return u.detect(this.plans, function(plan) { return plan[checker](); }); }; ExtractCascade.prototype.bestPreflightSelector = function() { var plan; if (this.options.provideTarget) { plan = this.plans[0]; } else { plan = this.oldPlan(); } if (plan) { plan.resolveNesting(); return plan.selector(); } else { return this.oldPlanNotFound(); } }; ExtractCascade.prototype.bestMatchingSteps = function() { var plan; if (plan = this.matchingPlan()) { plan.addHungrySteps(); plan.resolveNesting(); return plan.steps; } else { return this.matchingPlanNotFound(); } }; ExtractCascade.prototype.matchingPlanNotFound = function() { var inspectAction, message; if (this.newPlan()) { return this.oldPlanNotFound(); } else { if (this.oldPlan()) { message = "Could not find " + this.options.humanizedTarget + " in response"; } else { message = "Could not match " + this.options.humanizedTarget + " in current page and response"; } if (this.options.inspectResponse) { inspectAction = { label: 'Open response', callback: this.options.inspectResponse }; } return up.fail([message + " (tried %o)", this.candidates], { action: inspectAction }); } }; ExtractCascade.prototype.oldPlanNotFound = function() { var layerProse; layerProse = this.options.layer; if (layerProse === 'auto') { layerProse = 'page, modal or popup'; } return up.fail("Could not find " + this.options.humanizedTarget + " in current " + layerProse + " (tried %o)", this.candidates); }; return ExtractCascade; })(); }).call(this); (function() { var u, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; u = up.util; up.ExtractPlan = (function() { function ExtractPlan(selector, options) { this.addHungrySteps = bind(this.addHungrySteps, this); this.parseSteps = bind(this.parseSteps, this); this.selector = bind(this.selector, this); this.resolveNesting = bind(this.resolveNesting, this); this.addSteps = bind(this.addSteps, this); this.matchExists = bind(this.matchExists, this); this.newExists = bind(this.newExists, this); this.oldExists = bind(this.oldExists, this); this.findNew = bind(this.findNew, this); this.findOld = bind(this.findOld, this); var originalSelector; this.reveal = options.reveal; this.origin = options.origin; this.hungry = options.hungry; this.transition = options.transition; this.response = options.response; this.oldLayer = options.layer; originalSelector = up.dom.resolveSelector(selector, this.origin); this.parseSteps(originalSelector); } ExtractPlan.prototype.findOld = function() { return u.each(this.steps, (function(_this) { return function(step) { return step.$old = up.dom.first(step.selector, { layer: _this.oldLayer }); }; })(this)); }; ExtractPlan.prototype.findNew = function() { return u.each(this.steps, (function(_this) { return function(step) { return step.$new = _this.response.first(step.selector); }; })(this)); }; ExtractPlan.prototype.oldExists = function() { this.findOld(); return u.all(this.steps, function(step) { return step.$old; }); }; ExtractPlan.prototype.newExists = function() { this.findNew(); return u.all(this.steps, function(step) { return step.$new; }); }; ExtractPlan.prototype.matchExists = function() { return this.oldExists() && this.newExists(); }; ExtractPlan.prototype.addSteps = function(steps) { return this.steps = this.steps.concat(steps); }; ExtractPlan.prototype.resolveNesting = function() { var compressed; if (this.steps.length < 2) { return; } compressed = u.copy(this.steps); compressed = u.uniqBy(compressed, function(step) { return step.$old[0]; }); compressed = u.select(compressed, (function(_this) { return function(candidateStep, candidateIndex) { return u.all(compressed, function(rivalStep, rivalIndex) { var candidateElement, rivalElement; if (rivalIndex === candidateIndex) { return true; } else { candidateElement = candidateStep.$old[0]; rivalElement = rivalStep.$old[0]; return rivalStep.pseudoClass || !$.contains(rivalElement, candidateElement); } }); }; })(this)); compressed[0].reveal = this.steps[0].reveal; return this.steps = compressed; }; ExtractPlan.prototype.selector = function() { return u.map(this.steps, 'expression').join(', '); }; ExtractPlan.prototype.parseSteps = function(originalSelector) { var comma, disjunction; comma = /\ *,\ */; this.steps = []; disjunction = originalSelector.split(comma); return u.each(disjunction, (function(_this) { return function(expression, i) { var doReveal, expressionParts, pseudoClass, selector; expressionParts = expression.match(/^(.+?)(?:\:(before|after))?$/); expressionParts || up.fail('Could not parse selector literal "%s"', expression); selector = expressionParts[1]; if (selector === 'html') { selector = 'body'; } pseudoClass = expressionParts[2]; doReveal = i === 0 ? _this.reveal : false; return _this.steps.push({ expression: expression, selector: selector, pseudoClass: pseudoClass, transition: _this.transition, origin: _this.origin, reveal: doReveal }); }; })(this)); }; ExtractPlan.prototype.addHungrySteps = function() { var $hungries, $hungry, $newHungry, hungry, hungrySteps, j, len, selector, transition; hungrySteps = []; if (this.hungry) { $hungries = $(up.radio.hungrySelector()); transition = u.option(up.radio.config.hungryTransition, this.transition); for (j = 0, len = $hungries.length; j < len; j++) { hungry = $hungries[j]; $hungry = $(hungry); selector = u.selectorForElement($hungry); if ($newHungry = this.response.first(selector)) { hungrySteps.push({ selector: selector, $old: $hungry, $new: $newHungry, transition: transition, reveal: false, origin: null }); } } } return this.addSteps(hungrySteps); }; return ExtractPlan; })(); }).call(this); (function() { var u; u = up.util; up.ExtractStep = (function() { function ExtractStep() {} return ExtractStep; })(); }).call(this); (function() { var u, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; u = up.util; up.FieldObserver = (function() { var CHANGE_EVENTS; CHANGE_EVENTS = 'input change'; function FieldObserver($field, options) { this.$field = $field; this.check = bind(this.check, this); this.readFieldValue = bind(this.readFieldValue, this); this.requestCallback = bind(this.requestCallback, this); this.isNewValue = bind(this.isNewValue, this); this.scheduleTimer = bind(this.scheduleTimer, this); this.cancelTimer = bind(this.cancelTimer, this); this.stop = bind(this.stop, this); this.start = bind(this.start, this); this.delay = options.delay; this.callback = options.callback; } FieldObserver.prototype.start = function() { this.scheduledValue = null; this.processedValue = this.readFieldValue(); this.currentTimer = void 0; this.currentCallback = void 0; return this.$field.on(CHANGE_EVENTS, this.check); }; FieldObserver.prototype.stop = function() { this.$field.off(CHANGE_EVENTS, this.check); return this.cancelTimer(); }; FieldObserver.prototype.cancelTimer = function() { clearTimeout(this.currentTimer); return this.currentTimer = void 0; }; FieldObserver.prototype.scheduleTimer = function() { return this.currentTimer = u.setTimer(this.delay, (function(_this) { return function() { _this.currentTimer = void 0; return _this.requestCallback(); }; })(this)); }; FieldObserver.prototype.isNewValue = function(value) { return value !== this.processedValue && (this.scheduledValue === null || this.scheduledValue !== value); }; FieldObserver.prototype.requestCallback = function() { var callbackDone; if (this.scheduledValue !== null && !this.currentTimer && !this.currentCallback) { this.processedValue = this.scheduledValue; this.scheduledValue = null; this.currentCallback = (function(_this) { return function() { return _this.callback.call(_this.$field.get(0), _this.processedValue, _this.$field); }; })(this); callbackDone = Promise.resolve(this.currentCallback()); return u.always(callbackDone, (function(_this) { return function() { _this.currentCallback = void 0; return _this.requestCallback(); }; })(this)); } }; FieldObserver.prototype.readFieldValue = function() { return u.submittedValue(this.$field); }; FieldObserver.prototype.check = function() { var value; value = this.readFieldValue(); if (this.isNewValue(value)) { this.scheduledValue = value; this.cancelTimer(); return this.scheduleTimer(); } }; return FieldObserver; })(); }).call(this); (function() { var u, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, slice = [].slice; u = up.util; up.FollowVariant = (function() { function FollowVariant(selector, options) { this.matchesLink = bind(this.matchesLink, this); this.preloadLink = bind(this.preloadLink, this); this.followLink = bind(this.followLink, this); this.fullSelector = bind(this.fullSelector, this); this.onMousedown = bind(this.onMousedown, this); this.onClick = bind(this.onClick, this); this.followNow = options.follow; this.preloadNow = options.preload; this.selectors = selector.split(/\s*,\s*/); } FollowVariant.prototype.onClick = function(event, $link) { if (up.link.shouldProcessEvent(event, $link)) { if ($link.is('[up-instant]')) { return up.bus.haltEvent(event); } else { up.bus.consumeAction(event); return this.followLink($link); } } else { return up.link.allowDefault(event); } }; FollowVariant.prototype.onMousedown = function(event, $link) { if (up.link.shouldProcessEvent(event, $link)) { up.bus.consumeAction(event); return this.followLink($link); } }; FollowVariant.prototype.fullSelector = function(additionalClause) { var parts; if (additionalClause == null) { additionalClause = ''; } parts = []; this.selectors.forEach(function(variantSelector) { return ['a', '[up-href]'].forEach(function(tagSelector) { return parts.push("" + tagSelector + variantSelector + additionalClause); }); }); return parts.join(', '); }; FollowVariant.prototype.registerEvents = function() { up.on('click', this.fullSelector(), (function(_this) { return function() { var args; args = 1 <= arguments.length ? slice.call(arguments, 0) : []; return u.muteRejection(_this.onClick.apply(_this, args)); }; })(this)); return up.on('mousedown', this.fullSelector('[up-instant]'), (function(_this) { return function() { var args; args = 1 <= arguments.length ? slice.call(arguments, 0) : []; return u.muteRejection(_this.onMousedown.apply(_this, args)); }; })(this)); }; FollowVariant.prototype.followLink = function($link, options) { if (options == null) { options = {}; } return up.bus.whenEmitted('up:link:follow', { $element: $link }).then((function(_this) { return function() { return up.feedback.start($link, options, function() { return _this.followNow($link, options); }); }; })(this)); }; FollowVariant.prototype.preloadLink = function($link, options) { if (options == null) { options = {}; } return this.preloadNow($link, options); }; FollowVariant.prototype.matchesLink = function($link) { return $link.is(this.fullSelector()); }; return FollowVariant; })(); }).call(this); (function() { var u, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; u = up.util; up.MotionTracker = (function() { function MotionTracker(name) { this.reset = bind(this.reset, this); this.whileForwardingFinishEvent = bind(this.whileForwardingFinishEvent, this); this.forwardFinishEvent = bind(this.forwardFinishEvent, this); this.unmarkCluster = bind(this.unmarkCluster, this); this.markCluster = bind(this.markCluster, this); this.whenElementFinished = bind(this.whenElementFinished, this); this.emitFinishEvent = bind(this.emitFinishEvent, this); this.finishOneElement = bind(this.finishOneElement, this); this.isActive = bind(this.isActive, this); this.expandFinishRequest = bind(this.expandFinishRequest, this); this.finish = bind(this.finish, this); this.claim = bind(this.claim, this); this.activeClass = "up-" + name; this.dataKey = "up-" + name + "-finished"; this.selector = "." + this.activeClass; this.finishEvent = "up:" + name + ":finish"; this.finishCount = 0; this.clusterCount = 0; } /*** Finishes all animations in the given element cluster's ancestors and descendants, then calls the animator. The animation returned by the animator is tracked so it can be [`finished`](/up.MotionTracker.finish) later. @method claim @param {jQuery} $cluster @param {Function(jQuery): Promise} animator @param {Object} memory.trackMotion = true Whether @return {Promise} A promise that is fulfilled when the new animation ends. */ MotionTracker.prototype.claim = function(cluster, animator, memory) { var $cluster; if (memory == null) { memory = {}; } $cluster = $(cluster); memory.trackMotion = u.option(memory.trackMotion, up.motion.isEnabled()); if (memory.trackMotion === false) { return u.microtask(animator); } else { memory.trackMotion = false; return this.finish($cluster).then((function(_this) { return function() { var promise; promise = _this.whileForwardingFinishEvent($cluster, animator); promise = promise.then(function() { return _this.unmarkCluster($cluster); }); _this.markCluster($cluster, promise); return promise; }; })(this)); } }; /*** @method finish @param {jQuery} [elements] If no element is given, finishes all animations in the documnet. If an element is given, only finishes animations in its subtree and ancestors. @return {Promise} A promise that is fulfilled when animations have finished. */ MotionTracker.prototype.finish = function(elements) { var $elements, allFinished; this.finishCount++; if (this.clusterCount === 0 || !up.motion.isEnabled()) { return Promise.resolve(); } $elements = this.expandFinishRequest(elements); allFinished = u.map($elements, this.finishOneElement); return Promise.all(allFinished); }; MotionTracker.prototype.expandFinishRequest = function(elements) { if (elements) { return u.selectInDynasty($(elements), this.selector); } else { return $(this.selector); } }; MotionTracker.prototype.isActive = function(element) { return u.hasClass(element, this.activeClass); }; MotionTracker.prototype.finishOneElement = function(element) { var $element; $element = $(element); this.emitFinishEvent($element); return this.whenElementFinished($element); }; MotionTracker.prototype.emitFinishEvent = function($element, eventAttrs) { if (eventAttrs == null) { eventAttrs = {}; } eventAttrs = u.merge({ $element: $element, message: false }, eventAttrs); return up.emit(this.finishEvent, eventAttrs); }; MotionTracker.prototype.whenElementFinished = function($element) { return $element.data(this.dataKey) || Promise.resolve(); }; MotionTracker.prototype.markCluster = function($cluster, promise) { this.clusterCount++; $cluster.addClass(this.activeClass); return $cluster.data(this.dataKey, promise); }; MotionTracker.prototype.unmarkCluster = function($cluster) { this.clusterCount--; $cluster.removeClass(this.activeClass); return $cluster.removeData(this.dataKey); }; MotionTracker.prototype.forwardFinishEvent = function($original, $ghost, duration) { return this.start($original, (function(_this) { return function() { var doForward; doForward = function() { return $ghost.trigger(_this.finishEvent); }; $original.on(_this.finishEvent, doForward); return duration.then(function() { return $original.off(_this.finishEvent, doForward); }); }; })(this)); }; MotionTracker.prototype.whileForwardingFinishEvent = function($elements, fn) { var doForward; if ($elements.length < 2) { return fn(); } doForward = (function(_this) { return function(event) { if (!event.forwarded) { return u.each($elements, function(element) { var $element; $element = $(element); if (element !== event.target && _this.isActive($element)) { return _this.emitFinishEvent($element, { forwarded: true }); } }); } }; })(this); $elements.on(this.finishEvent, doForward); return fn().then((function(_this) { return function() { return $elements.off(_this.finishEvent, doForward); }; })(this)); }; MotionTracker.prototype.reset = function() { return this.finish().then((function(_this) { return function() { _this.finishCount = 0; return _this.clusterCount = 0; }; })(this)); }; return MotionTracker; })(); }).call(this); (function() { var u, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, slice = [].slice; u = up.util; up.Record = (function() { Record.prototype.fields = function() { throw 'Return an array of property names'; }; function Record(options) { this.copy = bind(this.copy, this); this.attributes = bind(this.attributes, this); u.assign(this, this.attributes(options)); } Record.prototype.attributes = function(source) { if (source == null) { source = this; } return u.only.apply(u, [source].concat(slice.call(this.fields()))); }; Record.prototype.copy = function(changes) { var attributesWithChanges; if (changes == null) { changes = {}; } attributesWithChanges = u.merge(this.attributes(), changes); return new this.constructor(attributesWithChanges); }; return Record; })(); }).call(this); (function() { var u, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, hasProp = {}.hasOwnProperty; u = up.util; /*** Instances of `up.Request` normalizes properties of an [`AJAX request`](/up.request) such as the requested URL, form parameters and HTTP method. @class up.Request */ up.Request = (function(superClass) { extend(Request, superClass); /*** The HTTP method for the request. @property up.Request#method @param {string} method @stable */ /*** The URL for the request. @property up.Request#url @param {string} url @stable */ /*** Parameters that should be sent as the request's payload. Parameters may be passed as one of the following forms: 1. An object where keys are param names and the values are param values 2. An array of `{ name: 'param-name', value: 'param-value' }` objects 3. A [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) object @property up.Request#data @param {String} data @stable */ /*** The CSS selector that will be sent as an [`X-Up-Target` header](/up.protocol#optimizing-responses). @property up.Request#target @param {string} target @stable */ /*** The CSS selector that will be sent as an [`X-Up-Fail-Target` header](/up.protocol#optimizing-responses). @property up.Request#failTarget @param {string} failTarget @stable */ /*** An object of additional HTTP headers. @property up.Request#headers @param {object} headers @stable */ /*** A timeout in milliseconds. If [`up.proxy.config.maxRequests`](/up.proxy.config#config.maxRequests) is set, the timeout will not include the time spent waiting in the queue. @property up.Request#timeout @param {object|undefined} timeout @stable */ Request.prototype.fields = function() { return ['method', 'url', 'data', 'target', 'failTarget', 'headers', 'timeout']; }; /*** @constructor up.Request @param {string} [attributes] */ function Request(options) { this.cacheKey = bind(this.cacheKey, this); this.isCachable = bind(this.isCachable, this); this.buildResponse = bind(this.buildResponse, this); this.isCrossDomain = bind(this.isCrossDomain, this); this.csrfToken = bind(this.csrfToken, this); this.navigate = bind(this.navigate, this); this.send = bind(this.send, this); this.isSafe = bind(this.isSafe, this); this.transferSearchToData = bind(this.transferSearchToData, this); this.transferDataToUrl = bind(this.transferDataToUrl, this); this.extractHashFromUrl = bind(this.extractHashFromUrl, this); this.normalize = bind(this.normalize, this); Request.__super__.constructor.call(this, options); this.normalize(); } Request.prototype.normalize = function() { this.method = u.normalizeMethod(this.method); this.headers || (this.headers = {}); this.extractHashFromUrl(); if (u.methodAllowsPayload(this.method)) { return this.transferSearchToData(); } else { return this.transferDataToUrl(); } }; Request.prototype.extractHashFromUrl = function() { var urlParts; urlParts = u.parseUrl(this.url); this.hash = urlParts.hash; return this.url = u.normalizeUrl(urlParts, { hash: false }); }; Request.prototype.transferDataToUrl = function() { var query, separator; if (this.data && !u.isFormData(this.data)) { query = u.requestDataAsQuery(this.data); separator = u.contains(this.url, '?') ? '&' : '?'; this.url += separator + query; return this.data = void 0; } }; Request.prototype.transferSearchToData = function() { var query, urlParts; urlParts = u.parseUrl(this.url); query = urlParts.search; if (query) { this.data = u.mergeRequestData(this.data, query); return this.url = u.normalizeUrl(urlParts, { search: false }); } }; Request.prototype.isSafe = function() { return up.proxy.isSafeMethod(this.method); }; Request.prototype.send = function() { return new Promise((function(_this) { return function(resolve, reject) { var csrfToken, header, ref, resolveWithResponse, value, xhr, xhrData, xhrHeaders, xhrMethod, xhrUrl; xhr = new XMLHttpRequest(); xhrHeaders = u.copy(_this.headers); xhrData = _this.data; xhrMethod = _this.method; xhrUrl = _this.url; ref = up.proxy.wrapMethod(xhrMethod, xhrData), xhrMethod = ref[0], xhrData = ref[1]; if (u.isFormData(xhrData)) { delete xhrHeaders['Content-Type']; } else if (u.isPresent(xhrData)) { xhrData = u.requestDataAsQuery(xhrData, { purpose: 'form' }); xhrHeaders['Content-Type'] = 'application/x-www-form-urlencoded'; } else { xhrData = null; } if (_this.target) { xhrHeaders[up.protocol.config.targetHeader] = _this.target; } if (_this.failTarget) { xhrHeaders[up.protocol.config.failTargetHeader] = _this.failTarget; } if (!_this.isCrossDomain()) { xhrHeaders['X-Requested-With'] || (xhrHeaders['X-Requested-With'] = 'XMLHttpRequest'); } if (csrfToken = _this.csrfToken()) { xhrHeaders[up.protocol.config.csrfHeader] = csrfToken; } xhr.open(xhrMethod, xhrUrl); for (header in xhrHeaders) { value = xhrHeaders[header]; xhr.setRequestHeader(header, value); } resolveWithResponse = function() { var response; response = _this.buildResponse(xhr); if (response.isSuccess()) { return resolve(response); } else { return reject(response); } }; xhr.onload = resolveWithResponse; xhr.onerror = resolveWithResponse; xhr.ontimeout = resolveWithResponse; if (_this.timeout) { xhr.timeout = _this.timeout; } return xhr.send(xhrData); }; })(this)); }; Request.prototype.navigate = function() { var $form, addField, csrfParam, csrfToken, formMethod; this.transferSearchToData(); $form = $('
'); addField = function(field) { return $('').attr(field).appendTo($form); }; if (this.method === 'GET') { formMethod = 'GET'; } else { addField({ name: up.protocol.config.methodParam, value: this.method }); formMethod = 'POST'; } $form.attr({ method: formMethod, action: this.url }); if ((csrfParam = up.protocol.csrfParam()) && (csrfToken = this.csrfToken())) { addField({ name: csrfParam, value: csrfToken }); } u.each(u.requestDataAsArray(this.data), addField); $form.hide().appendTo('body'); return up.browser.submitForm($form); }; Request.prototype.csrfToken = function() { if (!this.isSafe() && !this.isCrossDomain()) { return up.protocol.csrfToken(); } }; Request.prototype.isCrossDomain = function() { return u.isCrossDomain(this.url); }; Request.prototype.buildResponse = function(xhr) { var ref, responseAttrs, urlFromServer; responseAttrs = { method: this.method, url: this.url, text: xhr.responseText, status: xhr.status, request: this, xhr: xhr }; if (urlFromServer = up.protocol.locationFromXhr(xhr)) { responseAttrs.url = urlFromServer; responseAttrs.method = (ref = up.protocol.methodFromXhr(xhr)) != null ? ref : 'GET'; } responseAttrs.title = up.protocol.titleFromXhr(xhr); return new up.Response(responseAttrs); }; Request.prototype.isCachable = function() { return this.isSafe() && !u.isFormData(this.data); }; Request.prototype.cacheKey = function() { return [this.url, this.method, u.requestDataAsQuery(this.data), this.target].join('|'); }; Request.wrap = function(object) { if (object instanceof this) { return object; } else { return new this(object); } }; return Request; })(up.Record); }).call(this); (function() { var u, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, hasProp = {}.hasOwnProperty; u = up.util; /*** Instances of `up.Response` describe the server response to an [`AJAX request`](/up.request). \#\#\# Example up.request('/foo').then(function(response) { console.log(response.status); // 200 console.log(response.text); // "..." }); @class up.Response */ up.Response = (function(superClass) { extend(Response, superClass); /*** The HTTP method used for the response. This is usually the HTTP method used by the request. However, after a redirect the server should signal a `GET` method using an [`X-Up-Method: GET` header](/up.protocol#redirect-detection). @property up.Response#method @param {string} method @stable */ /*** The URL used for the response. This is usually the requested URL. However, after a redirect the server should signal a the new URL using an [`X-Up-Location: /new-url` header](/up.protocol#redirect-detection). @property up.Response#url @param {string} method @stable */ /*** The response body as a `string`. @property up.Response#text @param {string} text @stable */ /*** The response's [HTTP status code](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) as a `number`. A successful response will usually have a `200` or `201' status code. @property up.Response#status @param {number} status @stable */ /*** The [request](/up.Request) that triggered this response. @property up.Response#request @param {up.Request} request @experimental */ /*** The [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) object that was used to create this response. @property up.Response#xhr @param {XMLHttpRequest} xhr @experimental */ /*** A [document title pushed by the server](/up.protocol#pushing-a-document-title-to-the-client). If the server pushed no title via HTTP header, this will be `undefined`. @property up.Response#title @param {string} [title] @stable */ Response.prototype.fields = function() { return ['method', 'url', 'text', 'status', 'request', 'xhr', 'title']; }; function Response(options) { this.isFatalError = bind(this.isFatalError, this); this.isError = bind(this.isError, this); this.isSuccess = bind(this.isSuccess, this); Response.__super__.constructor.call(this, options); } /*** Returns whether the server responded with a 2xx HTTP status. @function up.Response#isSuccess @return {boolean} @experimental */ Response.prototype.isSuccess = function() { return this.status && (this.status >= 200 && this.status <= 299); }; /*** Returns whether the response was not [successful](/up.Request.prototype.isSuccess). This also returns `true` when the request encountered a [fatal error](/up.Request.prototype.isFatalError) like a timeout or loss of network connectivity. @function up.Response#isError @return {boolean} @experimental */ Response.prototype.isError = function() { return !this.isSuccess(); }; /*** Returns whether the request encountered a [fatal error](/up.Request.prototype.isFatalError) like a timeout or loss of network connectivity. When the server produces an error message with an HTTP status like `500`, this is not considered a fatal error and `false` is returned. @function up.Response#isFatalError @return {boolean} @experimental */ Response.prototype.isFatalError = function() { return this.isError() && u.isBlank(this.text); }; return Response; })(up.Record); }).call(this); (function() { var u, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; u = up.util; up.UrlSet = (function() { function UrlSet(urls, options) { this.urls = urls; if (options == null) { options = {}; } this.isEqual = bind(this.isEqual, this); this.matchesAny = bind(this.matchesAny, this); this.doesMatchPrefix = bind(this.doesMatchPrefix, this); this.doesMatchFully = bind(this.doesMatchFully, this); this.matches = bind(this.matches, this); this.normalizeUrl = options.normalizeUrl || u.normalizeUrl; this.urls = u.map(this.urls, this.normalizeUrl); this.urls = u.compact(this.urls); } UrlSet.prototype.matches = function(testUrl) { if (testUrl.substr(-1) === '*') { return this.doesMatchPrefix(testUrl.slice(0, -1)); } else { return this.doesMatchFully(testUrl); } }; UrlSet.prototype.doesMatchFully = function(testUrl) { return u.contains(this.urls, testUrl); }; UrlSet.prototype.doesMatchPrefix = function(prefix) { return u.detect(this.urls, function(url) { return url.indexOf(prefix) === 0; }); }; UrlSet.prototype.matchesAny = function(testUrls) { return u.detect(testUrls, this.matches); }; UrlSet.prototype.isEqual = function(otherSet) { return u.isEqual(this.urls, otherSet != null ? otherSet.urls : void 0); }; return UrlSet; })(); }).call(this); /*** Browser support =============== Unpoly supports all modern browsers. Chrome, Firefox, Edge, Safari : Full support Internet Explorer 11 : Full support with a `Promise` polyfill like [es6-promise](https://github.com/stefanpenner/es6-promise) (2.4 KB). Internet Explorer 10 or lower : Unpoly prevents itself from booting itself, leaving you with a classic server-side application. @class up.browser */ (function() { var slice = [].slice; up.browser = (function($) { var CONSOLE_PLACEHOLDERS, canConsole, canCssTransition, canDOMParser, canFormData, canInputEvent, canPromise, canPushState, hash, isIE10OrWorse, isRecentJQuery, isSupported, navigate, polyfilledSessionStorage, popCookie, puts, sessionStorage, sprintf, sprintfWithFormattedArgs, stringifyArg, submitForm, u, url, whenConfirmed; u = up.util; /*** @method up.browser.navigate @param {string} url @param {string} [options.method='get'] @param {Object|Array} [options.data] @internal */ navigate = function(url, options) { var request; if (options == null) { options = {}; } request = new up.Request(u.merge(options, { url: url })); return request.navigate(); }; /*** For mocking in specs. @method submitForm */ submitForm = function($form) { return $form.submit(); }; /*** 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, stream; stream = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; return console[stream].apply(console, args); }; CONSOLE_PLACEHOLDERS = /\%[odisf]/g; stringifyArg = function(arg) { var $arg, attr, closer, j, len, maxLength, ref, string, value; maxLength = 200; closer = ''; if (u.isString(arg)) { string = arg.replace(/[\n\r\t ]+/g, ' '); string = string.replace(/^[\n\r\t ]+/, ''); string = string.replace(/[\n\r\t ]$/, ''); string = "\"" + string + "\""; closer = '"'; } else if (u.isUndefined(arg)) { string = 'undefined'; } else if (u.isNumber(arg) || u.isFunction(arg)) { string = arg.toString(); } else if (u.isArray(arg)) { string = "[" + (u.map(arg, stringifyArg).join(', ')) + "]"; closer = ']'; } else if (u.isJQuery(arg)) { string = "$(" + (u.map(arg, stringifyArg).join(', ')) + ")"; closer = ')'; } else if (u.isElement(arg)) { $arg = $(arg); string = "<" + (arg.tagName.toLowerCase()); ref = ['id', 'name', 'class']; for (j = 0, len = ref.length; j < len; j++) { attr = ref[j]; if (value = $arg.attr(attr)) { string += " " + attr + "=\"" + value + "\""; } } string += ">"; closer = '>'; } else { string = JSON.stringify(arg); } if (string.length > maxLength) { string = (string.substr(0, maxLength)) + " …"; string += closer; } return string; }; /*** See https://developer.mozilla.org/en-US/docs/Web/API/Console#Using_string_substitutions @function up.browser.sprintf @internal */ sprintf = function() { var args, message; message = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; return sprintfWithFormattedArgs.apply(null, [u.identity, message].concat(slice.call(args))); }; /*** @function up.browser.sprintfWithBounds @internal */ sprintfWithFormattedArgs = function() { var args, formatter, i, message; formatter = arguments[0], message = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : []; if (u.isBlank(message)) { return ''; } i = 0; return message.replace(CONSOLE_PLACEHOLDERS, function() { var arg; arg = args[i]; arg = formatter(stringifyArg(arg)); i += 1; return arg; }); }; url = function() { return location.href; }; isIE10OrWorse = u.memoize(function() { return !window.atob; }); /*** Returns whether this browser supports manipulation of the current URL via [`history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState). When `pushState` (e.g. through [`up.follow()`](/up.follow)), it will gracefully fall back to a full page load. Note that Unpoly will not use `pushState` if the initial page was loaded with a request method other than GET. @function up.browser.canPushState @return {boolean} @experimental */ canPushState = function() { return u.isDefined(history.pushState) && up.protocol.initialRequestMethod() === 'get'; }; /*** Returns whether this browser supports animation using [CSS transitions](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions). When Unpoly is asked to animate history on a browser that doesn't support CSS transitions (e.g. through [`up.animate()`](/up.animate)), it will skip the animation by instantly jumping to the last frame. @function up.browser.canCssTransition @return {boolean} @internal */ canCssTransition = u.memoize(function() { return 'transition' in document.documentElement.style; }); /*** Returns whether this browser supports the DOM event [`input`](https://developer.mozilla.org/de/docs/Web/Events/input). @function up.browser.canInputEvent @return {boolean} @internal */ canInputEvent = u.memoize(function() { return 'oninput' in document.createElement('input'); }); /*** Returns whether this browser supports promises. @function up.browser.canPromise @return {boolean} @internal */ canPromise = u.memoize(function() { return !!window.Promise; }); /*** Returns whether this browser supports the [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) interface. @function up.browser.canFormData @return {boolean} @experimental */ canFormData = u.memoize(function() { return !!window.FormData; }); /*** Returns whether this browser supports the [`DOMParser`](https://developer.mozilla.org/en-US/docs/Web/API/DOMParser) interface. @function up.browser.canDOMParser @return {boolean} @internal */ canDOMParser = u.memoize(function() { return !!window.DOMParser; }); /*** Returns whether this browser supports the [`debugging console`](https://developer.mozilla.org/en-US/docs/Web/API/Console). @function up.browser.canConsole @return {boolean} @internal */ canConsole = u.memoize(function() { return window.console && console.debug && console.info && console.warn && console.error && console.group && console.groupCollapsed && console.groupEnd; }); 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); }); /*** Returns and deletes a cookie with the given name Inspired by Turbolinks: https://github.com/rails/turbolinks/blob/83d4b3d2c52a681f07900c28adb28bc8da604733/lib/assets/javascripts/turbolinks.coffee#L292 @function up.browser.popCookie @internal */ 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; }; /*** @function up,browser.whenConfirmed @return {Promise} @param {string} options.confirm @param {boolean} options.preload @internal */ whenConfirmed = function(options) { if (options.preload || u.isBlank(options.confirm) || window.confirm(options.confirm)) { return Promise.resolve(); } else { return Promise.reject(new Error('User canceled action')); } }; /*** Returns whether Unpoly supports the current browser. If this returns `false` Unpoly 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. This is usually a better fallback than loading incompatible Javascript and causing many errors on load. @function up.browser.isSupported @stable */ isSupported = function() { return !isIE10OrWorse() && isRecentJQuery() && canConsole() && canDOMParser() && canFormData() && canCssTransition() && canInputEvent() && canPromise(); }; /*** @internal */ sessionStorage = u.memoize(function() { try { return window.sessionStorage; } catch (_error) { return polyfilledSessionStorage(); } }); /*** @internal */ polyfilledSessionStorage = function() { var data; data = {}; return { getItem: function(prop) { return data[prop]; }, setItem: function(prop, value) { return data[prop] = value; } }; }; /*** Returns `'foo'` if the hash is `'#foo'`. Returns undefined if the hash is `'#'`, `''` or `undefined`. @function up.browser.hash @internal */ hash = function(value) { value || (value = location.hash); value || (value = ''); if (value[0] === '#') { value = value.substr(1); } return u.presence(value); }; return { url: url, navigate: navigate, submitForm: submitForm, canPushState: canPushState, whenConfirmed: whenConfirmed, isSupported: isSupported, puts: puts, sprintf: sprintf, sprintfWithFormattedArgs: sprintfWithFormattedArgs, sessionStorage: sessionStorage, popCookie: popCookie, hash: hash }; })(jQuery); }).call(this); /*** Events ====== Most Unpoly interactions emit DOM events that are prefixed with `up:`. $(document).on('up:modal:opened', function(event) { console.log('A new modal has just opened!'); }); Events often have both present ([`up:modal:open`](/up:modal:open)) and past forms ([`up:modal:opened`](/up:modal:opened)). \#\#\# Preventing events You can prevent most present form events by calling `preventDefault()`: $(document).on('up:modal:open', function(event) { if (event.url == '/evil') { // Prevent the modal from opening event.preventDefault(); } }); \#\#\# A better way to bind event listeners Instead of using jQuery to bind an event handler to `document`, you can also use the more convenient [`up.on()`](/up.on): up.on('click', 'button', function(event, $button) { // $button is a jQuery collection containing // the clicked