dist/up.js in upjs-rails-0.4.2 vs dist/up.js in upjs-rails-0.4.3

- old
+ new

@@ -20,14 +20,14 @@ @protected @class up.util */ (function() { - var __slice = [].slice; + var slice = [].slice; up.util = (function() { - var $createElementFromSelector, ANIMATION_PROMISE_KEY, CONSOLE_PLACEHOLDERS, ajax, castsToFalse, castsToTrue, clientSize, contains, copy, copyAttributes, createElement, createElementFromHtml, createSelectorFromElement, cssAnimate, debug, detect, each, error, escapePressed, extend, findWithSelf, finishCssAnimate, forceCompositing, get, ifGiven, isArray, isBlank, isDeferred, isDefined, isFunction, isGiven, isHash, isJQuery, isMissing, isNull, isObject, isPresent, isPromise, isStandardPort, isString, isUndefined, keys, last, locationFromXhr, measure, merge, methodFromXhr, nextFrame, normalizeMethod, normalizeUrl, only, option, options, prependGhost, presence, presentAttr, resolvableWhen, resolvedDeferred, resolvedPromise, select, setMissingAttrs, stringSet, stringifyConsoleArgs, temporaryCss, toArray, trim, unwrap; + var $createElementFromSelector, ANIMATION_PROMISE_KEY, CONSOLE_PLACEHOLDERS, ajax, castsToFalse, castsToTrue, clientSize, contains, copy, copyAttributes, createElement, createElementFromHtml, createSelectorFromElement, cssAnimate, debug, detect, each, error, escapePressed, extend, findWithSelf, finishCssAnimate, forceCompositing, get, ifGiven, isArray, isBlank, isDeferred, isDefined, isFunction, isGiven, isHash, isJQuery, isMissing, isNull, isObject, isPresent, isPromise, isStandardPort, isString, isUndefined, isUnmodifiedKeyEvent, isUnmodifiedMouseEvent, keys, last, locationFromXhr, measure, merge, methodFromXhr, nextFrame, normalizeMethod, normalizeUrl, only, option, options, prependGhost, presence, presentAttr, resolvableWhen, resolvedDeferred, resolvedPromise, select, setMissingAttrs, stringSet, stringifyConsoleArgs, temporaryCss, toArray, trim, unwrap; get = function(url, options) { options = options || {}; options.url = url; return ajax(options); }; @@ -80,11 +80,11 @@ normalized += anchor.search; } return normalized; }; - /* + /** @method up.util.normalizeMethod @protected */ normalizeMethod = function(method) { if (method) { @@ -92,21 +92,21 @@ } else { return 'GET'; } }; $createElementFromSelector = function(selector) { - var $element, $parent, $root, classes, conjunction, depthSelector, expression, html, id, iteration, path, tag, _i, _j, _len, _len1; + var $element, $parent, $root, classes, conjunction, depthSelector, expression, html, id, iteration, j, k, len, len1, path, tag; path = selector.split(/[ >]/); $root = null; - for (iteration = _i = 0, _len = path.length; _i < _len; iteration = ++_i) { + 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 (_j = 0, _len1 = conjunction.length; _j < _len1; _j++) { - expression = conjunction[_j]; + for (k = 0, len1 = conjunction.length; k < len1; k++) { + expression = conjunction[k]; switch (expression[0]) { case ".": classes.push(expression.substr(1)); break; case "#": @@ -142,20 +142,20 @@ element.innerHTML = html; } return element; }; debug = function() { - var args, group, message, placeHolderCount, value, _ref; - args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + var args, group, message, placeHolderCount, ref, value; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; args = toArray(args); message = args.shift(); message = "[UP] " + message; - placeHolderCount = ((_ref = message.match(CONSOLE_PLACEHOLDERS)) != null ? _ref.length : void 0) || 0; + placeHolderCount = ((ref = message.match(CONSOLE_PLACEHOLDERS)) != null ? ref.length : void 0) || 0; if (isFunction(last(args)) && placeHolderCount < args.length) { group = args.pop(); } - value = console.debug.apply(console, [message].concat(__slice.call(args))); + value = console.debug.apply(console, [message].concat(slice.call(args))); if (group) { console.groupCollapsed(); try { value = group(); } finally { @@ -164,11 +164,11 @@ } return value; }; error = function() { var $error, args, asString; - args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; args[0] = "[UP] " + args[0]; console.error.apply(console, args); asString = stringifyConsoleArgs(args); $error = presence($('.up-error')) || $('<div class="up-error"></div>').prependTo('body'); $error.addClass('up-error'); @@ -198,20 +198,20 @@ return "(" + argType + ")"; } }); }; createSelectorFromElement = function($element) { - var classString, classes, id, klass, selector, _i, _len; + var classString, classes, id, j, klass, len, selector; debug("Creating selector from element %o", $element); classes = (classString = $element.attr("class")) ? classString.split(" ") : []; id = $element.attr("id"); selector = $element.prop("tagName").toLowerCase(); if (id) { selector += "#" + id; } - for (_i = 0, _len = classes.length; _i < _len; _i++) { - klass = classes[_i]; + for (j = 0, len = classes.length; j < len; j++) { + klass = classes[j]; selector += "." + klass; } return selector; }; createElementFromHtml = function(html) { @@ -244,28 +244,28 @@ } }; extend = $.extend; trim = $.trim; keys = Object.keys || function(object) { - var key, result, _i, _len; + var j, key, len, result; result = []; - for (_i = 0, _len = object.length; _i < _len; _i++) { - key = object[_i]; + for (j = 0, len = object.length; j < len; j++) { + key = object[j]; if (object.hasOwnProperty(key)) { result.push(key); } } return result; }; each = function(collection, block) { - var index, item, _i, _len, _results; - _results = []; - for (index = _i = 0, _len = collection.length; _i < _len; index = ++_i) { + var index, item, j, len, results; + results = []; + for (index = j = 0, len = collection.length; j < len; index = ++j) { item = collection[index]; - _results.push(block(item, index)); + results.push(block(item, index)); } - return _results; + return results; }; isNull = function(object) { return object === null; }; isUndefined = function(object) { @@ -372,15 +372,15 @@ @method up.util.option @param {Array} args... */ option = function() { - var arg, args, match, value, _i, _len; - args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + var arg, args, j, len, match, value; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; match = null; - for (_i = 0, _len = args.length; _i < _len; _i++) { - arg = args[_i]; + for (j = 0, len = args.length; j < len; j++) { + arg = args[j]; value = arg; if (isFunction(value)) { value = value(); } if (isPresent(value)) { @@ -389,14 +389,14 @@ } } return match; }; detect = function(array, tester) { - var element, match, _i, _len; + var element, j, len, match; match = null; - for (_i = 0, _len = array.length; _i < _len; _i++) { - element = array[_i]; + for (j = 0, len = array.length; j < len; j++) { + element = array[j]; if (tester(element)) { match = element; break; } } @@ -412,19 +412,19 @@ }); return matches; }; presentAttr = function() { var $element, attrName, attrNames, values; - $element = arguments[0], attrNames = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + $element = arguments[0], attrNames = 2 <= arguments.length ? slice.call(arguments, 1) : []; values = (function() { - var _i, _len, _results; - _results = []; - for (_i = 0, _len = attrNames.length; _i < _len; _i++) { - attrName = attrNames[_i]; - _results.push($element.attr(attrName)); + 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 results; })(); return detect(values, isPresent); }; nextFrame = function(block) { return setTimeout(block, 0); @@ -533,17 +533,21 @@ return resolvedPromise(); } }; ANIMATION_PROMISE_KEY = 'up-animation-promise'; - /* + /** Completes the animation for the given element by jumping to the last frame instantly. All callbacks chained to the original animation's promise will be called. Does nothing if the given element is not currently animating. + Also see [`up.motion.finish`](/up.motion#up.motion.finish). + + @method up.util.finishCssAnimate + @protected @param {Element|jQuery|String} elementOrSelector */ finishCssAnimate = function(elementOrSelector) { return $(elementOrSelector).each(function() { var existingAnimation; @@ -572,22 +576,22 @@ box.bottom = viewport.height - (box.top + box.height); } return box; }; copyAttributes = function($source, $target) { - var attr, _i, _len, _ref, _results; - _ref = $source.get(0).attributes; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - attr = _ref[_i]; + 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)); + results.push($target.attr(attr.name, attr.value)); } else { - _results.push(void 0); + results.push(void 0); } } - return _results; + return results; }; prependGhost = function($element) { var $ghost, dimensions; dimensions = measure($element, { relative: true, @@ -624,21 +628,27 @@ }; methodFromXhr = function(xhr) { return xhr.getResponseHeader('X-Up-Method'); }; only = function() { - var filtered, key, keys, object, _i, _len; - object = arguments[0], keys = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + var filtered, j, key, keys, len, object; + object = arguments[0], keys = 2 <= arguments.length ? slice.call(arguments, 1) : []; filtered = {}; - for (_i = 0, _len = keys.length; _i < _len; _i++) { - key = keys[_i]; + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; if (object.hasOwnProperty(key)) { filtered[key] = object[key]; } } return filtered; }; + isUnmodifiedKeyEvent = function(event) { + return !(event.metaKey || event.shiftKey || event.ctrlKey); + }; + isUnmodifiedMouseEvent = function(event) { + return event.button === 0 && isUnmodifiedKeyEvent(event); + }; resolvedDeferred = function() { var deferred; deferred = $.Deferred(); deferred.resolve(); return deferred; @@ -646,34 +656,34 @@ resolvedPromise = function() { return resolvedDeferred().promise(); }; resolvableWhen = function() { var deferreds, joined; - deferreds = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + deferreds = 1 <= arguments.length ? slice.call(arguments, 0) : []; joined = $.when.apply($, deferreds); joined.resolve = function() { return each(deferreds, function(deferred) { return typeof deferred.resolve === "function" ? deferred.resolve() : void 0; }); }; return joined; }; setMissingAttrs = function($element, attrs) { - var key, value, _results; - _results = []; + var key, results, value; + results = []; for (key in attrs) { value = attrs[key]; if (isMissing($element.attr(key))) { - _results.push($element.attr(key, value)); + results.push($element.attr(key, value)); } else { - _results.push(void 0); + results.push(void 0); } } - return _results; + return results; }; stringSet = function(array) { - var includes, includesAny, key, put, set, string, _i, _len; + var includes, includesAny, j, key, len, put, set, string; set = {}; includes = function(string) { return set[key(string)]; }; includesAny = function(strings) { @@ -683,12 +693,12 @@ return set[key(string)] = true; }; key = function(string) { return "_" + string; }; - for (_i = 0, _len = array.length; _i < _len; _i++) { - string = array[_i]; + for (j = 0, len = array.length; j < len; j++) { + string = array[j]; put(string); } return { put: put, includes: includes, @@ -730,10 +740,12 @@ isJQuery: isJQuery, isPromise: isPromise, isDeferred: isDeferred, isHash: isHash, ifGiven: ifGiven, + isUnmodifiedKeyEvent: isUnmodifiedKeyEvent, + isUnmodifiedMouseEvent: isUnmodifiedMouseEvent, unwrap: unwrap, nextFrame: nextFrame, measure: measure, temporaryCss: temporaryCss, cssAnimate: cssAnimate, @@ -770,11 +782,11 @@ @class up.browser */ (function() { - var __slice = [].slice; + var slice = [].slice; up.browser = (function() { var canCssAnimation, canInputEvent, canPushState, ensureConsoleExists, ensureRecentJquery, isSupported, loadPage, memoize, u, url; u = up.util; loadPage = function(url, options) { @@ -805,28 +817,28 @@ }; url = function() { return location.href; }; ensureConsoleExists = function() { - var noop, _base, _base1, _base2, _base3, _base4, _base5, _base6; + var base, base1, base2, base3, base4, base5, base6, noop; window.console || (window.console = {}); noop = function() {}; - (_base = window.console).log || (_base.log = noop); - (_base1 = window.console).info || (_base1.info = noop); - (_base2 = window.console).error || (_base2.error = noop); - (_base3 = window.console).debug || (_base3.debug = noop); - (_base4 = window.console).group || (_base4.group = noop); - (_base5 = window.console).groupCollapsed || (_base5.groupCollapsed = noop); - return (_base6 = window.console).groupEnd || (_base6.groupEnd = noop); + (base = window.console).log || (base.log = noop); + (base1 = window.console).info || (base1.info = noop); + (base2 = window.console).error || (base2.error = noop); + (base3 = window.console).debug || (base3.debug = noop); + (base4 = window.console).group || (base4.group = noop); + (base5 = window.console).groupCollapsed || (base5.groupCollapsed = noop); + return (base6 = window.console).groupEnd || (base6.groupEnd = noop); }; memoize = function(func) { var cache, cached; cache = void 0; cached = false; return function() { var args; - args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; if (cached) { return cache; } else { cached = true; return cache = func.apply(null, args); @@ -893,11 +905,11 @@ @class up.bus */ (function() { - var __slice = [].slice; + var slice = [].slice; up.bus = (function() { var callbacksByEvent, callbacksFor, defaultCallbacksByEvent, emit, listen, reset, snapshot, u; u = up.util; callbacksByEvent = {}; @@ -912,18 +924,18 @@ @private @method up.bus.snapshot */ snapshot = function() { - var callbacks, event, _results; + var callbacks, event, results; defaultCallbacksByEvent = {}; - _results = []; + results = []; for (event in callbacksByEvent) { callbacks = callbacksByEvent[event]; - _results.push(defaultCallbacksByEvent[event] = u.copy(callbacks)); + results.push(defaultCallbacksByEvent[event] = u.copy(callbacks)); } - return _results; + return results; }; /** Resets the list of registered event listeners to the moment when the framework was booted. @@ -958,11 +970,11 @@ @param args... The arguments that describe the event. */ emit = function() { var args, callbacks, eventName; - eventName = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + eventName = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; u.debug("Emitting event %o with args %o", eventName, args); callbacks = callbacksFor(eventName); return u.each(callbacks, function(callback) { return callback.apply(null, args); }); @@ -976,10 +988,143 @@ })(); }).call(this); /** +Viewport scrolling +================== + +This modules contains functions to scroll the viewport and reveal contained elements. + +By default Up.js will always scroll to an element before updating it. + +@class up.viewport + */ + +(function() { + up.viewport = (function() { + var SCROLL_PROMISE_KEY, config, defaults, finishScrolling, reveal, scroll, u; + u = up.util; + config = { + duration: 0, + view: 'body', + easing: 'swing' + }; + + /** + @method up.viewport.defaults + @param {Number} [options.duration] + @param {String} [options.easing] + @param {Number} [options.padding] + @param {String|Element|jQuery} [options.view] + */ + defaults = function(options) { + return u.extend(config, options); + }; + SCROLL_PROMISE_KEY = 'up-scroll-promise'; + + /** + @method up.scroll + @param {String|Element|jQuery} viewOrSelector + @param {Number} scrollPos + @param {String}[options.duration] + @param {String}[options.easing] + @returns {Deferred} + @protected + */ + scroll = function(viewOrSelector, scrollPos, options) { + var $view, deferred, duration, easing, targetProps; + $view = $(viewOrSelector); + options = u.options(options); + duration = u.option(options.duration, config.duration); + easing = u.option(options.easing, config.easing); + finishScrolling($view); + if (duration > 0) { + deferred = $.Deferred(); + $view.data(SCROLL_PROMISE_KEY, deferred); + deferred.then(function() { + $view.removeData(SCROLL_PROMISE_KEY); + return $view.finish(); + }); + targetProps = { + scrollTop: scrollPos + }; + $view.animate(targetProps, { + duration: duration, + easing: easing, + complete: function() { + return deferred.resolve(); + } + }); + return deferred; + } else { + $view.scrollTop(scrollPos); + return u.resolvedDeferred(); + } + }; + + /** + @method up.viewport.finishScrolling + @private + */ + finishScrolling = function(elementOrSelector) { + return $(elementOrSelector).each(function() { + var existingScrolling; + if (existingScrolling = $(this).data(SCROLL_PROMISE_KEY)) { + return existingScrolling.resolve(); + } + }); + }; + + /** + @method up.reveal + @param {String|Element|jQuery} element + @param {String|Element|jQuery} [options.view] + @param {Number} [options.duration] + @param {String} [options.easing] + @param {Number} [options.padding] + @returns {Deferred} + @protected + */ + reveal = function(elementOrSelector, options) { + var $element, $view, elementTooHigh, elementTooLow, elementTop, firstVisibleRow, lastVisibleRow, padding, scrollPos, view, viewHeight; + options = u.options(options); + view = u.option(options.view, config.view); + padding = u.option(options.padding, config.padding); + $element = $(elementOrSelector); + $view = $(view); + viewHeight = $view.height(); + scrollPos = $view.scrollTop(); + firstVisibleRow = scrollPos; + lastVisibleRow = scrollPos + viewHeight; + elementTop = $element.position().top; + elementTooHigh = elementTop - padding < firstVisibleRow; + elementTooLow = elementTop > lastVisibleRow - padding; + if (elementTooHigh || elementTooLow) { + scrollPos = elementTop - padding; + scrollPos = Math.max(scrollPos, 0); + scrollPos = Math.min(scrollPos, viewHeight - 1); + return scroll($view, scrollPos, options); + } else { + return u.resolvedDeferred(); + } + }; + return { + reveal: reveal, + scroll: scroll, + finishScrolling: finishScrolling, + defaults: defaults + }; + })(); + + up.scroll = up.viewport.scroll; + + up.reveal = up.viewport.reveal; + +}).call(this); + +/** Changing page fragments programmatically ======================================== This module contains Up's core functions to insert, change or destroy page fragments. @@ -995,11 +1140,11 @@ @class up.flow */ (function() { up.flow = (function() { - var autofocus, destroy, elementsInserted, implant, implantSteps, reload, replace, reset, setSource, source, swapElements, u; + var autofocus, destroy, elementsInserted, findOldFragment, implant, parseImplantSteps, parseResponse, prepareForReplacement, reload, replace, reset, reveal, setSource, source, swapElements, u; u = up.util; setSource = function(element, sourceUrl) { var $element; $element = $(element); if (u.isPresent(sourceUrl)) { @@ -1031,10 +1176,11 @@ If a `String` is given, it is used as the URL the browser's location bar and history. If omitted or true, the `url` argument will be used. If set to `false`, the history will remain unchanged. @param {String|Boolean} [options.source=true] @param {String} [options.transition] + @param {String} [options.scroll='body'] @param {String} [options.historyMethod='push'] */ replace = function(selectorOrElement, url, options) { var promise, request, selector; options = u.options(options); @@ -1085,38 +1231,73 @@ @param {String} selector @param {String} html @param {String} [options.title] @param {String} [options.source] @param {Object} [options.transition] + @param {String} [options.scroll='body'] @param {String} [options.history] @param {String} [options.historyMethod='push'] */ implant = function(selector, html, options) { - var $new, $old, fragment, htmlElement, step, _i, _len, _ref, _ref1, _results; + var $new, $old, j, len, ref, response, results, step; options = u.options(options, { historyMethod: 'push' }); - if (options.history === 'false') { + if (u.castsToFalse(options.history)) { options.history = null; } + if (u.castsToFalse(options.scroll)) { + options.scroll = null; + } options.source = u.option(options.source, options.history); + response = parseResponse(html); + options.title || (options.title = response.title()); + ref = parseImplantSteps(selector, options); + results = []; + for (j = 0, len = ref.length; j < len; j++) { + step = ref[j]; + $old = findOldFragment(step.selector); + $new = response.find(step.selector); + results.push(prepareForReplacement($old, options).then(function() { + return swapElements($old, $new, step.pseudoClass, step.transition, options); + })); + } + return results; + }; + findOldFragment = function(selector) { + return u.presence($(".up-popup " + selector)) || u.presence($(".up-modal " + selector)) || u.presence($(selector)) || u.error('Could not find selector %o in current body HTML', selector); + }; + parseResponse = function(html) { + var htmlElement; htmlElement = u.createElementFromHtml(html); - options.title || (options.title = (_ref = htmlElement.querySelector("title")) != null ? _ref.textContent : void 0); - _ref1 = implantSteps(selector, options); - _results = []; - for (_i = 0, _len = _ref1.length; _i < _len; _i++) { - step = _ref1[_i]; - up.motion.finish(step.selector); - $old = u.presence($(".up-popup " + step.selector)) || u.presence($(".up-modal " + step.selector)) || u.presence($(step.selector)) || u.error('Could not find selector %o in current body HTML', step.selector); - if (fragment = htmlElement.querySelector(step.selector)) { - $new = $(fragment); - _results.push(swapElements($old, $new, step.pseudoClass, step.transition, options)); - } else { - _results.push(u.error("Could not find selector %o in response %o", step.selector, html)); + return { + title: function() { + var ref; + return (ref = htmlElement.querySelector("title")) != null ? ref.textContent : void 0; + }, + find: function(selector) { + var child; + if (child = htmlElement.querySelector(selector)) { + return $(child); + } else { + return u.error("Could not find selector %o in response %o", selector, html); + } } + }; + }; + prepareForReplacement = function($element, options) { + up.motion.finish($element); + return reveal($element, options.scroll); + }; + reveal = function($element, view) { + if (view) { + return up.reveal($element, { + view: view + }); + } else { + return u.resolvedDeferred(); } - return _results; }; elementsInserted = function($new, options) { if (typeof options.insert === "function") { options.insert($new); } @@ -1141,40 +1322,40 @@ elementsInserted($addedChildren, options); return up.animate($new, transition); } else { return destroy($old, { animation: function() { - $new.insertAfter($old); + $new.insertBefore($old); elementsInserted($new, options); if ($old.is('body') && transition !== 'none') { u.error('Cannot apply transitions to body-elements (%o)', transition); } return up.morph($old, $new, transition); } }); } }; - implantSteps = function(selector, options) { - var comma, disjunction, i, selectorAtom, selectorParts, transition, transitionString, transitions, _i, _len, _results; + parseImplantSteps = function(selector, options) { + var comma, disjunction, i, j, len, results, selectorAtom, selectorParts, transition, transitionString, transitions; transitionString = options.transition || options.animation || 'none'; comma = /\ *,\ */; disjunction = selector.split(comma); if (u.isPresent(transitionString)) { transitions = transitionString.split(comma); } - _results = []; - for (i = _i = 0, _len = disjunction.length; _i < _len; i = ++_i) { + results = []; + for (i = j = 0, len = disjunction.length; j < len; i = ++j) { selectorAtom = disjunction[i]; selectorParts = selectorAtom.match(/^(.+?)(?:\:(before|after))?$/); transition = transitions[i] || u.last(transitions); - _results.push({ + results.push({ selector: selectorParts[1], pseudoClass: selectorParts[2], transition: transition }); } - return _results; + return results; }; autofocus = function($element) { var $control, selector; selector = '[autofocus]:last'; $control = u.findWithSelf($element, selector); @@ -1282,11 +1463,11 @@ @class up.magic */ (function() { - var __slice = [].slice; + var slice = [].slice; up.magic = (function() { var DESTROYABLE_CLASS, DESTROYER_KEY, applyAwakener, awaken, awakeners, compile, data, defaultAwakeners, defaultLiveDescriptions, destroy, live, liveDescriptions, onEscape, ready, reset, snapshot, u; u = up.util; DESTROYABLE_CLASS = 'up-destroyable'; @@ -1309,21 +1490,21 @@ and passed as a second argument. */ liveDescriptions = []; defaultLiveDescriptions = null; live = function(events, selector, behavior) { - var description, _ref; + var description, ref; if (!up.browser.isSupported()) { return; } description = [ events, selector, function(event) { return behavior.apply(this, [event, $(this), data(this)]); } ]; liveDescriptions.push(description); - return (_ref = $(document)).on.apply(_ref, description); + return (ref = $(document)).on.apply(ref, description); }; /** Registers a function to be called whenever an element with the given selector is inserted into the DOM through Up.js. @@ -1347,11 +1528,11 @@ */ awakeners = []; defaultAwakeners = null; awaken = function() { var args, awakener, options, selector; - selector = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + selector = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; if (!up.browser.isSupported()) { return; } awakener = args.pop(); options = u.options(args[0], { @@ -1371,40 +1552,40 @@ $jqueryElement.addClass(DESTROYABLE_CLASS); return $jqueryElement.data(DESTROYER_KEY, destroyer); } }; compile = function($fragment) { - var $matches, awakener, _i, _len, _results; + var $matches, awakener, i, len, results; u.debug("Compiling fragment %o", $fragment); - _results = []; - for (_i = 0, _len = awakeners.length; _i < _len; _i++) { - awakener = awakeners[_i]; + results = []; + for (i = 0, len = awakeners.length; i < len; i++) { + awakener = awakeners[i]; $matches = u.findWithSelf($fragment, awakener.selector); if ($matches.length) { if (awakener.batch) { - _results.push(applyAwakener(awakener, $matches, $matches.get())); + results.push(applyAwakener(awakener, $matches, $matches.get())); } else { - _results.push($matches.each(function() { + results.push($matches.each(function() { return applyAwakener(awakener, $(this), this); })); } } else { - _results.push(void 0); + results.push(void 0); } } - return _results; + return results; }; destroy = function($fragment) { return u.findWithSelf($fragment, "." + DESTROYABLE_CLASS).each(function() { var $element, destroyer; $element = $(this); destroyer = $element.data(DESTROYER_KEY); return destroyer(); }); }; - /* + /** Checks if the given element has an `up-data` attribute. If yes, parses the attribute value as JSON and returns the parsed object. Returns an empty object if the element has no `up-data` attribute. @@ -1413,10 +1594,26 @@ @protected @method up.magic.data @param {String|Element|jQuery} elementOrSelector */ + + /* + Stores a JSON-string with the element. + + If an element annotated with [`up-data`] is inserted into the DOM, + Up will parse the JSON and pass the resulting object to any matching + [`up.awaken`](/up.magic#up.magic.awaken) handlers. + + Similarly, when an event is triggered on an element annotated with + [`up-data`], the parsed object will be passed to any matching + [`up.on`](/up.magic#up.on) handlers. + + @ujs + @method [up-data] + @param {JSON} [up-data] + */ data = function(elementOrSelector) { var $element, json; $element = $(elementOrSelector); json = $element.attr('up-data'); if (u.isString(json) && u.trim(json) !== '') { @@ -1444,15 +1641,15 @@ @private @method up.magic.reset */ reset = function() { - var description, _i, _len, _ref; - for (_i = 0, _len = liveDescriptions.length; _i < _len; _i++) { - description = liveDescriptions[_i]; + var description, i, len, ref; + for (i = 0, len = liveDescriptions.length; i < len; i++) { + description = liveDescriptions[i]; if (!u.contains(defaultLiveDescriptions, description)) { - (_ref = $(document)).off.apply(_ref, description); + (ref = $(document)).off.apply(ref, description); } } liveDescriptions = u.copy(defaultLiveDescriptions); return awakeners = u.copy(defaultAwakeners); }; @@ -1620,25 +1817,25 @@ (function() { up.motion = (function() { var GHOSTING_PROMISE_KEY, animate, animation, animations, assertIsDeferred, config, defaultAnimations, defaultTransitions, defaults, findAnimation, finish, finishGhosting, morph, none, reset, resolvableWhen, snapshot, transition, transitions, u, withGhosts; u = up.util; + animations = {}; + defaultAnimations = {}; + transitions = {}; + defaultTransitions = {}; config = { duration: 300, delay: 0, easing: 'ease' }; - animations = {}; - defaultAnimations = {}; - transitions = {}; - defaultTransitions = {}; /** @method up.modal.defaults - @param {Number} options.duration - @param {Number} options.delay - @param {String} options.easing + @param {Number} [options.duration] + @param {Number} [options.delay] + @param {String} [options.easing] */ defaults = function(options) { return u.extend(config, options); }; @@ -1724,17 +1921,18 @@ return newCssMemo(); }); return promise; }; - /* + /** Completes all animations and transitions for the given element by jumping to the last animation frame instantly. All callbacks chained to the original animation's promise will be called. Does nothing if the given element is not currently animating. + @method up.motion.finish @param {Element|jQuery|String} elementOrSelector */ finish = function(elementOrSelector) { return $(elementOrSelector).each(function() { var $element; @@ -2024,14 +2222,16 @@ /** Caching and preloading ====================== All HTTP requests go through the Up.js proxy. -It caches a limited number +It caches a [limited](/up.proxy#up.proxy.defaults) number of server responses + for a [limited](/up.proxy#up.proxy.defaults) amount of time, +making requests to these URLs return insantly. -The cache is cleared whenever the user makes a non-´GET` request -(like `POST`, `PUT`, `DELETE`). +The cache is cleared whenever the user makes a non-`GET` request +(like `POST`, `PUT` or `DELETE`). The proxy can also used to speed up reaction times by preloading links when the user hovers over the click area (or puts the mouse/finger down before releasing). This way the response will already be cached when the user performs the click. @@ -2041,20 +2241,20 @@ (function() { up.proxy = (function() { var $waitingLink, SAFE_HTTP_METHODS, ajax, alias, cache, cacheKey, cancelDelay, checkPreload, clear, config, defaults, delayTimer, ensureIsIdempotent, get, isFresh, isIdempotent, normalizeRequest, preload, remove, reset, set, startDelay, timestamp, touch, trim, u; config = { - preloadDelay: 50, + preloadDelay: 75, cacheSize: 70, cacheExpiry: 1000 * 60 * 5 }; /** @method up.proxy.defaults - @param {Number} [preloadDelay] - @param {Number} [cacheSize] - @param {Number} [cacheExpiry] + @param {Number} [options.preloadDelay] + @param {Number} [options.cacheSize] + @param {Number} [options.cacheExpiry] The number of milliseconds until a cache entry expires. */ defaults = function(options) { return u.extend(config, options); }; @@ -2109,15 +2309,23 @@ if (promise = get(oldRequest)) { return set(newRequest, promise); } }; - /* + /** + Makes a request to the given URL and caches the response. + If the response was already cached, returns the HTML instantly. + + If requesting a URL that is not read-only, the response will + not be cached and the entire cache will be cleared. + Only requests with a method of `GET`, `OPTIONS` and `HEAD` + are considered to be read-only. + @method up.proxy.ajax - @param {String} options.url - @param {String} [options.method='GET'] - @param {String} [options.selector] + @param {String} request.url + @param {String} [request.method='GET'] + @param {String} [request.selector] */ ajax = function(request) { var promise; if (!isIdempotent(request)) { clear(); @@ -2209,18 +2417,22 @@ cancelDelay(); return cache = {}; }; up.bus.on('framework:reset', reset); - /* + /** Links with an `up-preload` attribute will silently fetch their target when the user hovers over the click area, or when the user puts her mouse/finger down (before releasing). This way the response will already be cached when the user performs the click, making the interaction feel instant. @method [up-preload] + @param [[up-delay]=50] + The number of milliseconds to wait between hovering + and preloading. Increasing this will lower the load in your server, + but will also make the interaction feel less instant. @ujs */ up.on('mouseover mousedown touchstart', '[up-preload]', function(event, $element) { if (!up.link.childClicked(event, $element)) { return checkPreload(up.link.resolve($element)); @@ -2314,11 +2526,11 @@ @class up.link */ (function() { up.link = (function() { - var childClicked, follow, resolve, u, visit; + var activeInstantLink, childClicked, follow, resolve, u, visit; u = up.util; /** Visits the given URL without a full page load. This is done by fetching `url` through an AJAX request @@ -2349,19 +2561,23 @@ The selector to replace. Defaults to the `up-target` attribute on `link`, or to `body` if such an attribute does not exist. @param {Function|String} [options.transition] A transition function or name. + @param {Element|jQuery|String} scroll + An element or selector that will be scrolled to the top in + case the replaced element is not visible in the viewport. */ follow = function(link, options) { var $link, selector, url; $link = $(link); options = u.options(options); url = u.option($link.attr('href'), $link.attr('up-follow')); selector = u.option(options.target, $link.attr('up-target'), 'body'); options.transition = u.option(options.transition, $link.attr('up-transition'), $link.attr('up-animation')); options.history = u.option(options.history, $link.attr('up-history')); + options.scroll = u.option(options.history, $link.attr('up-scroll'), 'body'); return up.replace(selector, url, options); }; resolve = function(element) { var $element, followAttr; $element = $(element); @@ -2402,26 +2618,29 @@ if (!$link.is('[up-instant]')) { return follow($link); } }); up.on('mousedown', 'a[up-target][up-instant]', function(event, $link) { - if (event.which === 1) { + if (activeInstantLink(event, $link)) { event.preventDefault(); - return follow($link); + return up.follow($link); } }); - /* + /** @method up.link.childClicked @private */ childClicked = function(event, $link) { var $target, $targetLink; $target = $(event.target); $targetLink = $target.closest('a, [up-follow]'); return $targetLink.length && $link.find($targetLink).length; }; + activeInstantLink = function(event, $link) { + return u.isUnmodifiedMouseEvent(event) && !childClicked(event, $link); + }; /** If applied on a link, Follows this link via AJAX and replaces the current `<body>` element with the response's `<body>` element @@ -2452,26 +2671,26 @@ @ujs @param {String} [up-follow] @param up-instant If set, fetches the element on `mousedown` instead of `click`. */ - up.on('click', '[up-follow]', function(event, $element) { - if (!childClicked(event, $element)) { + up.on('click', '[up-follow]', function(event, $link) { + if (!childClicked(event, $link)) { event.preventDefault(); - if (!$element.is('[up-instant]')) { - return follow(resolve($element)); + if (!$link.is('[up-instant]')) { + return follow(resolve($link)); } } }); - up.on('mousedown', '[up-follow][up-instant]', function(event, $element) { - if (!childClicked(event, $element) && event.which === 1) { + up.on('mousedown', '[up-follow][up-instant]', function(event, $link) { + if (activeInstantLink(event, $link)) { event.preventDefault(); - return follow(resolve($element)); + return up.follow(resolve($link)); } }); - /* + /** Marks up the current link to be followed *as fast as possible*. This is done by: - [Following the link through AJAX](/up.link#up-target) instead of a full page load - [Preloading the link's destination URL](/up.proxy#up-preload) @@ -2481,11 +2700,11 @@ <a href="/users" up-dash=".main">User list</a> Note that this is shorthand for: - <a href="/users" up-target=".main" up-instant up-preload>User list</a> + <a href="/users" up-target=".main" up-instant up-preload>User list</a> You can also apply `[up-dash]` to any element that contains a link in order to enlarge the link's click area: <div class="notification" up-dash> @@ -2607,11 +2826,11 @@ data: $form.serialize(), selector: successSelector }; successUrl = function(xhr) { var currentLocation; - url = historyOption ? historyOption === 'false' ? false : u.isString(historyOption) ? historyOption : (currentLocation = u.locationFromXhr(xhr)) ? currentLocation : request.type === 'GET' ? request.url + '?' + request.data : void 0 : void 0; + url = historyOption ? u.castsToFalse(historyOption) ? false : u.isString(historyOption) ? historyOption : (currentLocation = u.locationFromXhr(xhr)) ? currentLocation : request.type === 'GET' ? request.url + '?' + request.data : void 0 : void 0; return u.option(url, false); }; return u.ajax(request).always(function() { return $form.removeClass('up-active'); }).done(function(html, textStatus, xhr) { @@ -2680,11 +2899,11 @@ } }; check = function() { var skipCallback, value; value = $field.val(); - skipCallback = _.isNull(knownValue); + skipCallback = u.isNull(knownValue); if (knownValue !== value) { knownValue = value; if (!skipCallback) { clearTimer(); nextCallback = function() { @@ -2794,11 +3013,11 @@ @class up.popup */ (function() { up.popup = (function() { - var autoclose, close, config, createHiddenPopup, defaults, ensureInViewport, open, position, source, u, updated; + var autoclose, close, config, createHiddenPopup, defaults, discardHistory, ensureInViewport, open, position, rememberHistory, source, u, updated; u = up.util; config = { openAnimation: 'fade-in', closeAnimation: 'fade-out', origin: 'bottom-right' @@ -2879,21 +3098,32 @@ } else if (bottom = parseInt($popup.css('bottom'))) { return $popup.css('bottom', bottom + errorY); } } }; + rememberHistory = function() { + var $popup; + $popup = $('.up-popup'); + $popup.attr('up-previous-url', up.browser.url()); + return $popup.attr('up-previous-title', document.title); + }; + discardHistory = function() { + var $popup; + $popup = $('.up-popup'); + $popup.removeAttr('up-previous-url'); + return $popup.removeAttr('up-previous-title'); + }; createHiddenPopup = function($link, selector, sticky) { var $placeholder, $popup; $popup = u.$createElementFromSelector('.up-popup'); if (sticky) { $popup.attr('up-sticky', ''); } - $popup.attr('up-previous-url', up.browser.url()); - $popup.attr('up-previous-title', document.title); $placeholder = u.$createElementFromSelector(selector); $placeholder.appendTo($popup); $popup.appendTo(document.body); + rememberHistory(); $popup.hide(); return $popup; }; updated = function($link, $popup, origin, animation) { $popup.show(); @@ -3007,10 +3237,11 @@ return close(); } }); up.bus.on('fragment:ready', function($fragment) { if (!$fragment.closest('.up-popup').length) { + discardHistory(); return autoclose(); } }); up.magic.onEscape(function() { return close(); @@ -3059,11 +3290,11 @@ @class up.modal */ (function() { up.modal = (function() { - var autoclose, close, config, createHiddenModal, defaults, open, source, templateHtml, u, updated; + var autoclose, close, config, createHiddenModal, defaults, discardHistory, open, rememberHistory, source, templateHtml, u, updated; u = up.util; config = { width: 'auto', height: 'auto', openAnimation: 'fade-in', @@ -3093,10 +3324,22 @@ return template(config); } else { return template; } }; + rememberHistory = function() { + var $popup; + $popup = $('.up-modal'); + $popup.attr('up-previous-url', up.browser.url()); + return $popup.attr('up-previous-title', document.title); + }; + discardHistory = function() { + var $popup; + $popup = $('.up-modal'); + $popup.removeAttr('up-previous-url'); + return $popup.removeAttr('up-previous-title'); + }; createHiddenModal = function(selector, width, height, sticky) { var $content, $dialog, $modal, $placeholder; $modal = $(templateHtml()); if (sticky) { $modal.attr('up-sticky', ''); @@ -3112,10 +3355,11 @@ } $content = $dialog.find('.up-modal-content'); $placeholder = u.$createElementFromSelector(selector); $placeholder.appendTo($content); $modal.appendTo(document.body); + rememberHistory(); $modal.hide(); return $modal; }; updated = function($modal, animation) { $modal.show(); @@ -3194,10 +3438,11 @@ return up.destroy($modal, options); } }; autoclose = function() { if (!$('.up-modal').is('[up-sticky]')) { + discardHistory(); return close(); } }; /** @@ -3414,31 +3659,41 @@ @class up.navigation */ (function() { up.navigation = (function() { - var CLASS_ACTIVE, CLASS_CURRENT, SELECTOR_ACTIVE, SELECTOR_SECTION, enlargeClickArea, locationChanged, normalizeUrl, sectionClicked, sectionUrls, u, unmarkActive; + var CLASS_ACTIVE, CLASS_CURRENT, SELECTORS_SECTION, SELECTOR_ACTIVE, SELECTOR_SECTION, SELECTOR_SECTION_INSTANT, enlargeClickArea, locationChanged, normalizeUrl, sectionClicked, sectionUrls, selector, u, unmarkActive; u = up.util; CLASS_ACTIVE = 'up-active'; CLASS_CURRENT = 'up-current'; - SELECTOR_SECTION = 'a[href], a[up-target], [up-follow], [up-modal], [up-popup], [up-source]'; + SELECTORS_SECTION = ['a[href]', 'a[up-target]', '[up-follow]', '[up-modal]', '[up-popup]', '[up-href]']; + SELECTOR_SECTION = SELECTORS_SECTION.join(', '); + SELECTOR_SECTION_INSTANT = ((function() { + var i, len, results; + results = []; + for (i = 0, len = SELECTORS_SECTION.length; i < len; i++) { + selector = SELECTORS_SECTION[i]; + results.push(selector + "[up-instant]"); + } + return results; + })()).join(', '); SELECTOR_ACTIVE = "." + CLASS_ACTIVE; normalizeUrl = function(url) { if (u.isPresent(url)) { return u.normalizeUrl(url, { search: false, stripTrailingSlash: true }); } }; sectionUrls = function($section) { - var $link, attr, url, urls, _i, _len, _ref; + var $link, attr, i, len, ref, url, urls; urls = []; if ($link = up.link.resolve($section)) { - _ref = ['href', 'up-follow', 'up-source']; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - attr = _ref[_i]; + ref = ['href', 'up-follow', 'up-href']; + for (i = 0, len = ref.length; i < len; i++) { + attr = ref[i]; if (url = u.presentAttr($link, attr)) { url = normalizeUrl(url); urls.push(url); } } @@ -3469,10 +3724,17 @@ }; unmarkActive = function() { return $(SELECTOR_ACTIVE).removeClass(CLASS_ACTIVE); }; up.on('click', SELECTOR_SECTION, function(event, $section) { - return sectionClicked($section); + if (!$section.is('[up-instant]')) { + return sectionClicked($section); + } + }); + up.on('mousedown', SELECTOR_SECTION_INSTANT, function(event, $section) { + if (u.isUnmodifiedMouseEvent(event)) { + return sectionClicked($section); + } }); up.bus.on('fragment:ready', function() { unmarkActive(); return locationChanged(); });