dist/up.js in upjs-rails-0.7.8 vs dist/up.js in upjs-rails-0.8.0
- old
+ new
@@ -23,11 +23,11 @@
(function() {
var slice = [].slice;
up.util = (function() {
- var $createElementFromSelector, ANIMATION_PROMISE_KEY, CONSOLE_PLACEHOLDERS, ajax, castsToFalse, castsToTrue, clientSize, compact, contains, copy, copyAttributes, createElement, createElementFromHtml, createSelectorFromElement, cssAnimate, debug, detect, each, error, escapePressed, extend, findWithSelf, finishCssAnimate, forceCompositing, get, ifGiven, isArray, isBlank, isDeferred, isDefined, isElement, isFunction, isGiven, isHash, isJQuery, isMissing, isNull, isObject, isPresent, isPromise, isStandardPort, isString, isUndefined, isUnmodifiedKeyEvent, isUnmodifiedMouseEvent, keys, last, locationFromXhr, measure, merge, methodFromXhr, nextFrame, normalizeMethod, normalizeUrl, nullJquery, only, option, options, prependGhost, presence, presentAttr, resolvableWhen, resolvedDeferred, resolvedPromise, select, setMissingAttrs, stringifyConsoleArgs, temporaryCss, times, toArray, trim, unwrap, warn;
+ var $createElementFromSelector, ANIMATION_PROMISE_KEY, CONSOLE_PLACEHOLDERS, ajax, castsToFalse, castsToTrue, clientSize, compact, contains, copy, copyAttributes, createElement, createElementFromHtml, createSelectorFromElement, cssAnimate, debug, detect, each, error, escapePressed, extend, findWithSelf, finishCssAnimate, forceCompositing, get, ifGiven, isArray, isBlank, isDeferred, isDefined, isElement, isFunction, isGiven, isHash, isJQuery, isMissing, isNull, isObject, isPresent, isPromise, isStandardPort, isString, isUndefined, isUnmodifiedKeyEvent, isUnmodifiedMouseEvent, keys, last, locationFromXhr, measure, merge, methodFromXhr, nextFrame, normalizeMethod, normalizeUrl, nullJquery, only, option, options, prependGhost, presence, presentAttr, remove, resolvableWhen, resolvedDeferred, resolvedPromise, select, setMissingAttrs, stringifyConsoleArgs, temporaryCss, times, toArray, trim, unwrap, warn;
get = function(url, options) {
options = options || {};
options.url = url;
return ajax(options);
};
@@ -707,10 +707,18 @@
results.push(void 0);
}
}
return results;
};
+ remove = function(array, element) {
+ var index;
+ index = array.indexOf(element);
+ if (index >= 0) {
+ array.splice(index, 1);
+ return element;
+ }
+ };
return {
presentAttr: presentAttr,
createElement: createElement,
normalizeUrl: normalizeUrl,
normalizeMethod: normalizeMethod,
@@ -776,11 +784,12 @@
trim: trim,
keys: keys,
resolvedPromise: resolvedPromise,
resolvedDeferred: resolvedDeferred,
resolvableWhen: resolvableWhen,
- setMissingAttrs: setMissingAttrs
+ setMissingAttrs: setMissingAttrs,
+ remove: remove
};
})();
}).call(this);
@@ -930,20 +939,20 @@
We need to work on this page:
- Decide whether to refactor this into document events
- Decide whether `fragment:enter` and `fragment:leave` would be better names
+- Decide if we wouldn't rather document events in the respective module (e.g. proxy).
-
@class up.bus
*/
(function() {
var slice = [].slice;
up.bus = (function() {
- var callbacksByEvent, callbacksFor, defaultCallbacksByEvent, emit, listen, reset, snapshot, u;
+ var callbacksByEvent, callbacksFor, defaultCallbacksByEvent, emit, listen, reset, snapshot, stopListen, u;
u = up.util;
callbacksByEvent = {};
defaultCallbacksByEvent = {};
callbacksFor = function(event) {
return callbacksByEvent[event] || (callbacksByEvent[event] = []);
@@ -981,20 +990,50 @@
/**
Registers an event handler to be called when the given
event is triggered.
@method up.bus.on
- @param {String} eventName
- The event name to match.
+ @param {String} eventNames
+ A space-separated list of event names to match.
@param {Function} handler
The event handler to be called with the event arguments.
+ @return {Function}
+ A function that unregisters the given handlers
*/
- listen = function(eventName, handler) {
- return callbacksFor(eventName).push(handler);
+ listen = function(eventNames, handler) {
+ var eventName, i, len, ref;
+ ref = eventNames.split(' ');
+ for (i = 0, len = ref.length; i < len; i++) {
+ eventName = ref[i];
+ callbacksFor(eventName).push(handler);
+ }
+ return function() {
+ return stopListen(eventNames, handler);
+ };
};
/**
+ Unregisters the given handler from the given events.
+
+ @method up.bus.off
+ @param {String} eventNames
+ A space-separated list of event names .
+ @param {Function} handler
+ The event handler that should stop listening.
+ */
+ stopListen = function(eventNames, handler) {
+ var eventName, i, len, ref, results;
+ ref = eventNames.split(' ');
+ results = [];
+ for (i = 0, len = ref.length; i < len; i++) {
+ eventName = ref[i];
+ results.push(u.remove(callbacksFor(eventName), handler));
+ }
+ return results;
+ };
+
+ /**
Triggers an event over the framework bus.
All arguments will be passed as arguments to event listeners:
up.bus.on('foo:bar', function(x, y) {
@@ -1026,10 +1065,11 @@
};
listen('framework:ready', snapshot);
listen('framework:reset', reset);
return {
on: listen,
+ off: stopListen,
emit: emit
};
})();
}).call(this);
@@ -1242,11 +1282,12 @@
}
request = {
url: url,
method: options.method,
selector: selector,
- cache: options.cache
+ cache: options.cache,
+ preload: options.preload
};
promise = up.proxy.ajax(request);
promise.done(function(html, textStatus, xhr) {
var currentLocation, newRequest;
if (currentLocation = u.locationFromXhr(xhr)) {
@@ -2619,32 +2660,91 @@
Caching and preloading
======================
All HTTP requests go through the Up.js proxy.
It caches a [limited](/up.proxy#up.proxy.defaults) number of server responses
- for a [limited](/up.proxy#up.proxy.defaults) amount of time,
+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` 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.
+response will already be cached when the user performs the click.
+Spinners
+---------
+
+You can listen to [framework events](/up.bus) to implement a spinner
+(progress indicator) that appears during a long-running request,
+and disappears once the response has been received:
+
+ <div class="spinner">Please wait!</div>
+
+Here is the Javascript to make it alive:
+
+ up.compiler('.spinner', function($element) {
+
+ show = function() { $element.show() };
+ hide = function() { $element.hide() };
+
+ up.bus.on('proxy:busy', show);
+ up.bus.on('proxy:idle', hide);
+
+ return function() {
+ up.bus.off('proxy:busy', show);
+ up.bus.off('proxy:idle', hide);
+ };
+
+ });
+
+The `proxy:busy` event will be emitted after a delay of 300 ms
+to prevent the spinner from flickering on and off.
+You can change (or remove) this delay like this:
+
+ up.proxy.defaults({ busyDelay: 150 });
+
@class up.proxy
*/
(function() {
up.proxy = (function() {
- var $waitingLink, SAFE_HTTP_METHODS, ajax, alias, cache, cacheKey, cancelDelay, checkPreload, clear, config, defaults, delayTimer, get, isFresh, isIdempotent, normalizeRequest, preload, remove, reset, set, startDelay, timestamp, trim, u;
- config = {
+ var $waitingLink, FACTORY_CONFIG, SAFE_HTTP_METHODS, ajax, alias, busy, busyDelayTimer, busyEventEmitted, cache, cacheKey, cancelBusyDelay, cancelPreloadDelay, checkPreload, clear, config, defaults, get, idle, isFresh, isIdempotent, load, loadEnded, loadStarted, normalizeRequest, pendingCount, preload, preloadDelayTimer, remove, reset, set, startPreloadDelay, timestamp, trim, u;
+ u = up.util;
+ cache = void 0;
+ $waitingLink = void 0;
+ preloadDelayTimer = void 0;
+ busyDelayTimer = void 0;
+ pendingCount = void 0;
+ config = void 0;
+ busyEventEmitted = void 0;
+ FACTORY_CONFIG = {
+ busyDelay: 300,
preloadDelay: 75,
cacheSize: 70,
cacheExpiry: 1000 * 60 * 5
};
+ cancelPreloadDelay = function() {
+ clearTimeout(preloadDelayTimer);
+ return preloadDelayTimer = null;
+ };
+ cancelBusyDelay = function() {
+ clearTimeout(busyDelayTimer);
+ return busyDelayTimer = null;
+ };
+ reset = function() {
+ cache = {};
+ $waitingLink = null;
+ cancelPreloadDelay();
+ cancelBusyDelay();
+ pendingCount = 0;
+ config = u.copy(FACTORY_CONFIG);
+ return busyEventEmitted = false;
+ };
+ reset();
/**
@method up.proxy.defaults
@param {Number} [options.preloadDelay=75]
The number of milliseconds to wait before [`[up-preload]`](#up-preload)
@@ -2653,18 +2753,17 @@
The maximum number of responses to cache.
If the size is exceeded, the oldest items will be dropped from the cache.
@param {Number} [options.cacheExpiry=300000]
The number of milliseconds until a cache entry expires.
Defaults to 5 minutes.
+ @param {Number} [options.busyDelay=300]
+ How long the proxy waits until emitting the `proxy:busy` [event](/up.bus).
+ Use this to prevent flickering of spinners.
*/
defaults = function(options) {
return u.extend(config, options);
};
- cache = {};
- u = up.util;
- $waitingLink = null;
- delayTimer = null;
cacheKey = function(request) {
normalizeRequest(request);
return [request.url, request.method, request.data, request.selector].join('|');
};
trim = function() {
@@ -2716,35 +2815,107 @@
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.
+ If a network connection is attempted, the proxy will emit
+ a `proxy:load` event with the `request` as its argument.
+ Once the response is received, a `proxy:receive` event will
+ be emitted.
+
@method up.proxy.ajax
@param {String} request.url
@param {String} [request.method='GET']
@param {String} [request.selector]
@param {Boolean} [request.cache]
Whether to use a cached response, if available.
If set to `false` a network connection will always be attempted.
*/
ajax = function(options) {
- var forceCache, ignoreCache, promise, request;
+ var forceCache, ignoreCache, pending, promise, request;
forceCache = u.castsToTrue(options.cache);
ignoreCache = u.castsToFalse(options.cache);
request = u.only(options, 'url', 'method', 'data', 'selector', '_normalized');
+ pending = true;
if (!isIdempotent(request) && !forceCache) {
clear();
- promise = u.ajax(request);
+ promise = load(request);
} else if ((promise = get(request)) && !ignoreCache) {
- promise;
+ pending = promise.state() === 'pending';
} else {
- promise = u.ajax(request);
+ promise = load(request);
set(request, promise);
}
+ if (pending && !options.preload) {
+ loadStarted();
+ promise.then(loadEnded);
+ }
return promise;
};
SAFE_HTTP_METHODS = ['GET', 'OPTIONS', 'HEAD'];
+
+ /**
+ Returns `true` if the proxy is not currently waiting
+ for a request to finish. Returns `false` otherwise.
+
+ The proxy will also emit an `proxy:idle` [event](/up.bus) if it
+ used to busy, but is now idle.
+
+ @method up.proxy.idle
+ @return {Boolean} Whether the proxy is idle
+ */
+ idle = function() {
+ return pendingCount === 0;
+ };
+
+ /**
+ Returns `true` if the proxy is currently waiting
+ for a request to finish. Returns `false` otherwise.
+
+ The proxy will also emit an `proxy:busy` [event](/up.bus) if it
+ used to idle, but is now busy.
+
+ @method up.proxy.busy
+ @return {Boolean} Whether the proxy is busy
+ */
+ busy = function() {
+ return pendingCount > 0;
+ };
+ loadStarted = function() {
+ var emission, wasIdle;
+ wasIdle = idle();
+ pendingCount += 1;
+ if (wasIdle) {
+ emission = function() {
+ if (busy()) {
+ up.bus.emit('proxy:busy');
+ return busyEventEmitted = true;
+ }
+ };
+ if (config.busyDelay > 0) {
+ return busyDelayTimer = setTimeout(emission, config.busyDelay);
+ } else {
+ return emission();
+ }
+ }
+ };
+ loadEnded = function() {
+ pendingCount -= 1;
+ if (idle() && busyEventEmitted) {
+ up.bus.emit('proxy:idle');
+ return busyEventEmitted = false;
+ }
+ };
+ load = function(request) {
+ var promise;
+ up.bus.emit('proxy:load', request);
+ promise = u.ajax(request);
+ promise.then(function() {
+ return up.bus.emit('proxy:receive', request);
+ });
+ return promise;
+ };
isIdempotent = function(request) {
normalizeRequest(request);
return u.contains(SAFE_HTTP_METHODS, request.method);
};
isFresh = function(promise) {
@@ -2808,24 +2979,20 @@
checkPreload = function($link) {
var curriedPreload, delay;
delay = parseInt(u.presentAttr($link, 'up-delay')) || config.preloadDelay;
if (!$link.is($waitingLink)) {
$waitingLink = $link;
- cancelDelay();
+ cancelPreloadDelay();
curriedPreload = function() {
return preload($link);
};
- return startDelay(curriedPreload, delay);
+ return startPreloadDelay(curriedPreload, delay);
}
};
- startDelay = function(block, delay) {
- return delayTimer = setTimeout(block, delay);
+ startPreloadDelay = function(block, delay) {
+ return preloadDelayTimer = setTimeout(block, delay);
};
- cancelDelay = function() {
- clearTimeout(delayTimer);
- return delayTimer = null;
- };
/**
@protected
@method up.proxy.preload
@return
@@ -2845,14 +3012,10 @@
} else {
u.debug("Won't preload %o due to unsafe method %o", $link, method);
return u.resolvedPromise();
}
};
- reset = function() {
- 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
@@ -2878,9 +3041,11 @@
get: get,
set: set,
alias: alias,
clear: clear,
remove: remove,
+ idle: idle,
+ busy: busy,
defaults: defaults
};
})();
}).call(this);