dist/up.js in upjs-rails-0.9.1 vs dist/up.js in upjs-rails-0.10.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, config, contains, copy, copyAttributes, createElement, createElementFromHtml, createSelectorFromElement, cssAnimate, debug, detect, each, endsWith, error, escapePressed, extend, findWithSelf, finishCssAnimate, forceCompositing, get, identity, ifGiven, isArray, isBlank, isDeferred, isDefined, isElement, isFunction, isGiven, isHash, isJQuery, isMissing, isNull, isObject, isPresent, isPromise, isStandardPort, isString, isUndefined, isUnmodifiedKeyEvent, isUnmodifiedMouseEvent, keys, last, locationFromXhr, map, measure, memoize, merge, methodFromXhr, nextFrame, normalizeMethod, normalizeUrl, nullJquery, once, only, option, options, presence, presentAttr, remove, resolvableWhen, resolvedDeferred, resolvedPromise, scrollbarWidth, select, setMissingAttrs, startsWith, stringifyConsoleArgs, temporaryCss, times, toArray, trim, unJquery, uniq, unwrapElement, warn;
+ var $createElementFromSelector, ANIMATION_PROMISE_KEY, CONSOLE_PLACEHOLDERS, ajax, cache, castedAttr, clientSize, compact, config, contains, copy, copyAttributes, createElement, createElementFromHtml, createSelectorFromElement, cssAnimate, debug, detect, each, endsWith, error, escapePressed, extend, findWithSelf, finishCssAnimate, forceCompositing, get, identity, ifGiven, isArray, isBlank, isDeferred, isDefined, isElement, isFunction, isGiven, isHash, isJQuery, isMissing, isNull, isNumber, isObject, isPresent, isPromise, isStandardPort, isString, isUndefined, isUnmodifiedKeyEvent, isUnmodifiedMouseEvent, keys, last, locationFromXhr, map, measure, memoize, merge, methodFromXhr, nextFrame, normalizeMethod, normalizeUrl, nullJquery, once, only, option, options, presence, presentAttr, remove, resolvableWhen, resolvedDeferred, resolvedPromise, scrollbarWidth, select, setMissingAttrs, startsWith, stringifyConsoleArgs, temporaryCss, times, toArray, trim, unJquery, uniq, unwrapElement, warn;
memoize = function(func) {
var cache, cached;
cache = void 0;
cached = false;
return function() {
@@ -333,10 +333,13 @@
return typeof object === 'function';
};
isString = function(object) {
return typeof object === 'string';
};
+ isNumber = function(object) {
+ return typeof object === 'number';
+ };
isHash = function(object) {
return typeof object === 'object' && !!object;
};
isObject = function(object) {
return isHash(object) || (typeof object === 'function');
@@ -689,16 +692,24 @@
return string.indexOf(element) === string.length - element.length;
};
contains = function(stringOrArray, element) {
return stringOrArray.indexOf(element) >= 0;
};
- castsToTrue = function(object) {
- return String(object) === "true";
+ castedAttr = function($element, attrName) {
+ var value;
+ value = $element.attr(attrName);
+ switch (value) {
+ case 'false':
+ return false;
+ case 'true':
+ return true;
+ case '':
+ return true;
+ default:
+ return value;
+ }
};
- castsToFalse = function(object) {
- return String(object) === "false";
- };
locationFromXhr = function(xhr) {
return xhr.getResponseHeader('X-Up-Location');
};
methodFromXhr = function(xhr) {
return xhr.getResponseHeader('X-Up-Method');
@@ -773,16 +784,157 @@
if (index >= 0) {
array.splice(index, 1);
return element;
}
};
+
+ /**
+ @method up.util.cache
+ @param {Number|Function} [config.size]
+ Maximum number of cache entries.
+ Set to `undefined` to not limit the cache size.
+ @param {Number|Function} [config.expiry]
+ The number of milliseconds after which a cache entry
+ will be discarded.
+ @param {String} [config.log]
+ A prefix for log entries printed by this cache object.
+ */
+ cache = function(config) {
+ var alias, clear, expiryMilis, isFresh, log, maxSize, normalizeStoreKey, set, store, timestamp;
+ store = void 0;
+ clear = function() {
+ return store = {};
+ };
+ clear();
+ log = function() {
+ var args;
+ args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+ if (config.log) {
+ args[0] = "[" + config.log + "] " + args[0];
+ return debug.apply(null, args);
+ }
+ };
+ maxSize = function() {
+ if (isMissing(config.size)) {
+ return void 0;
+ } else if (isFunction(config.size)) {
+ return config.size();
+ } else if (isNumber(config.size)) {
+ return config.size;
+ } else {
+ return error("Invalid size config: %o", config.size);
+ }
+ };
+ expiryMilis = function() {
+ if (isMissing(config.expiry)) {
+ return void 0;
+ } else if (isFunction(config.expiry)) {
+ return config.expiry();
+ } else if (isNumber(config.expiry)) {
+ return config.expiry;
+ } else {
+ return error("Invalid expiry config: %o", config.expiry);
+ }
+ };
+ normalizeStoreKey = function(key) {
+ if (config.key) {
+ return config.key(key);
+ } else {
+ return key.toString();
+ }
+ };
+ trim = function() {
+ var oldestKey, oldestTimestamp, size, storeKeys;
+ storeKeys = copy(keys(store));
+ size = maxSize();
+ if (size && storeKeys.length > size) {
+ oldestKey = null;
+ oldestTimestamp = null;
+ each(storeKeys, function(key) {
+ var promise, timestamp;
+ promise = store[key];
+ timestamp = promise.timestamp;
+ if (!oldestTimestamp || oldestTimestamp > timestamp) {
+ oldestKey = key;
+ return oldestTimestamp = timestamp;
+ }
+ });
+ if (oldestKey) {
+ return delete store[oldestKey];
+ }
+ }
+ };
+ alias = function(oldKey, newKey) {
+ var value;
+ value = get(oldKey);
+ if (isDefined(value)) {
+ return set(newKey, value);
+ }
+ };
+ timestamp = function() {
+ return (new Date()).valueOf();
+ };
+ set = function(key, value) {
+ var storeKey;
+ storeKey = normalizeStoreKey(key);
+ return store[storeKey] = {
+ timestamp: timestamp(),
+ value: value
+ };
+ };
+ remove = function(key) {
+ var storeKey;
+ storeKey = normalizeStoreKey(key);
+ return delete store[storeKey];
+ };
+ isFresh = function(entry) {
+ var expiry, timeSinceTouch;
+ expiry = expiryMilis();
+ if (expiry) {
+ timeSinceTouch = timestamp() - entry.timestamp;
+ return timeSinceTouch < expiryMilis();
+ } else {
+ return true;
+ }
+ };
+ get = function(key, fallback) {
+ var entry, storeKey;
+ if (fallback == null) {
+ fallback = void 0;
+ }
+ storeKey = normalizeStoreKey(key);
+ if (entry = store[storeKey]) {
+ if (!isFresh(entry)) {
+ log("Discarding stale cache entry for %o", key);
+ remove(key);
+ return fallback;
+ } else {
+ log("Cache hit for %o", key);
+ return entry.value;
+ }
+ } else {
+ log("Cache miss for %o", key);
+ return fallback;
+ }
+ };
+ return {
+ alias: alias,
+ get: get,
+ set: set,
+ remove: remove,
+ clear: clear
+ };
+ };
config = function(factoryOptions) {
var apiKeys, hash;
if (factoryOptions == null) {
factoryOptions = {};
}
hash = {
+ ensureKeyExists: function(key) {
+ return factoryOptions.hasOwnProperty(key) || error("Unknown setting %o", key);
+ },
reset: function() {
var j, key, len, ownKeys;
ownKeys = copy(Object.getOwnPropertyNames(hash));
for (j = 0, len = ownKeys.length; j < len; j++) {
key = ownKeys[j];
@@ -791,23 +943,27 @@
}
}
return hash.update(copy(factoryOptions));
},
update: function(options) {
- var key, value;
- if (options == null) {
- options = {};
- }
- for (key in options) {
- value = options[key];
- if (factoryOptions.hasOwnProperty(key)) {
- hash[key] = value;
+ var key, results, value;
+ if (options) {
+ if (isString(options)) {
+ hash.ensureKeyExists(options);
+ return hash[options];
} else {
- error("Unknown setting %o", key);
+ results = [];
+ for (key in options) {
+ value = options[key];
+ hash.ensureKeyExists(key);
+ results.push(hash[key] = value);
+ }
+ return results;
}
+ } else {
+ return hash;
}
- return hash;
}
};
apiKeys = Object.getOwnPropertyNames(hash);
hash.reset();
return hash;
@@ -882,12 +1038,11 @@
contains: contains,
startsWith: startsWith,
endsWith: endsWith,
isArray: isArray,
toArray: toArray,
- castsToTrue: castsToTrue,
- castsToFalse: castsToFalse,
+ castedAttr: castedAttr,
locationFromXhr: locationFromXhr,
methodFromXhr: methodFromXhr,
clientSize: clientSize,
only: only,
trim: trim,
@@ -898,10 +1053,11 @@
setMissingAttrs: setMissingAttrs,
remove: remove,
memoize: memoize,
scrollbarWidth: scrollbarWidth,
config: config,
+ cache: cache,
unwrapElement: unwrapElement
};
})();
}).call(this);
@@ -1152,11 +1308,10 @@
The arguments that describe the event.
*/
emit = function() {
var args, callbacks, eventName;
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);
});
};
@@ -1170,10 +1325,627 @@
})();
}).call(this);
/**
+Registering behavior and custom elements
+========================================
+
+Up.js keeps a persistent Javascript environment during page transitions.
+To prevent memory leaks it is important to cleanly set up and tear down
+event handlers and custom elements.
+
+\#\#\# Incomplete documentation!
+
+We need to work on this page:
+
+- Better class-level introduction for this module
+
+@class up.magic
+ */
+
+(function() {
+ var slice = [].slice;
+
+ up.magic = (function() {
+ var DESTROYABLE_CLASS, DESTROYER_KEY, applyCompiler, compile, compiler, compilers, data, defaultCompilers, defaultLiveDescriptions, destroy, live, liveDescriptions, onEscape, ready, reset, snapshot, u;
+ u = up.util;
+ DESTROYABLE_CLASS = 'up-destroyable';
+ DESTROYER_KEY = 'up-destroyer';
+
+ /**
+ Binds an event handler to the document, which will be executed whenever the
+ given event is triggered on the given selector:
+
+ up.on('click', '.button', function(event, $element) {
+ console.log("Someone clicked the button %o", $element);
+ });
+
+ This is roughly equivalent to binding a jQuery element to `document`.
+
+
+ \#\#\#\# Attaching structured data
+
+ In case you want to attach structured data to the event you're observing,
+ you can serialize the data to JSON and put it into an `[up-data]` attribute:
+
+ <span class="person" up-data="{ age: 18, name: 'Bob' }">Bob</span>
+ <span class="person" up-data="{ age: 22, name: 'Jim' }">Jim</span>
+
+ The JSON will parsed and handed to your event handler as a third argument:
+
+ up.on('click', '.person', function(event, $element, data) {
+ console.log("This is %o who is %o years old", data.name, data.age);
+ });
+
+
+ \#\#\#\# Migrating jQuery event handlers to `up.on`
+
+ Within the event handler, Up.js will bind `this` to the
+ native DOM element to help you migrate your existing jQuery code to
+ this new syntax.
+
+ So if you had this before:
+
+ $(document).on('click', '.button', function() {
+ $(this).something();
+ });
+
+ ... you can simply copy the event handler to `up.on`:
+
+ up.on('click', '.button', function() {
+ $(this).something();
+ });
+
+
+ @method up.on
+ @param {String} events
+ A space-separated list of event names to bind.
+ @param {String} selector
+ The selector an on which the event must be triggered.
+ @param {Function(event, $element, data)} behavior
+ The handler that should be called.
+ The function takes the affected element as the first argument (as a jQuery object).
+ If the element has an `up-data` attribute, its value is parsed as JSON
+ and passed as a second argument.
+ */
+ liveDescriptions = [];
+ defaultLiveDescriptions = null;
+ live = function(events, selector, behavior) {
+ 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);
+ };
+
+ /**
+ Registers a function to be called whenever an element with
+ the given selector is inserted into the DOM through Up.js.
+
+ This is a great way to integrate jQuery plugins.
+ Let's say your Javascript plugin wants you to call `lightboxify()`
+ on links that should open a lightbox. You decide to
+ do this for all links with an `[rel=lightbox]` attribute:
+
+ <a href="river.png" rel="lightbox">River</a>
+ <a href="ocean.png" rel="lightbox">Ocean</a>
+
+ This Javascript will do exactly that:
+
+ up.compiler('a[rel=lightbox]', function($element) {
+ $element.lightboxify();
+ });
+
+ Note that within the compiler, Up.js will bind `this` to the
+ native DOM element to help you migrate your existing jQuery code to
+ this new syntax.
+
+
+ \#\#\#\# Custom elements
+
+ You can also use `up.compiler` to implement custom elements like this:
+
+ <clock></clock>
+
+ Here is the Javascript that inserts the current time into to these elements:
+
+ up.compiler('clock', function($element) {
+ var now = new Date();
+ $element.text(now.toString()));
+ });
+
+
+ \#\#\#\# Cleaning up after yourself
+
+ If your compiler returns a function, Up.js will use this as a *destructor* to
+ clean up if the element leaves the DOM. Note that in Up.js the same DOM ad Javascript environment
+ will persist through many page loads, so it's important to not create
+ [memory leaks](https://makandracards.com/makandra/31325-how-to-create-memory-leaks-in-jquery).
+
+ You should clean up after yourself whenever your compilers have global
+ side effects, like a [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval)
+ or event handlers bound to the document root.
+
+ Here is a version of `<clock>` that updates
+ the time every second, and cleans up once it's done:
+
+ up.compiler('clock', function($element) {
+
+ function update() {
+ var now = new Date();
+ $element.text(now.toString()));
+ }
+
+ setInterval(update, 1000);
+
+ return function() {
+ clearInterval(update);
+ };
+
+ });
+
+ If we didn't clean up after ourselves, we would have many ticking intervals
+ operating on detached DOM elements after we have created and removed a couple
+ of `<clock>` elements.
+
+
+ \#\#\#\# Attaching structured data
+
+ In case you want to attach structured data to the event you're observing,
+ you can serialize the data to JSON and put it into an `[up-data]` attribute.
+ For instance, a container for a [Google Map](https://developers.google.com/maps/documentation/javascript/tutorial)
+ might attach the location and names of its marker pins:
+
+ <div class="google-map" up-data="[
+ { lat: 48.36, lng: 10.99, title: 'Friedberg' },
+ { lat: 48.75, lng: 11.45, title: 'Ingolstadt' }
+ ]"></div>
+
+ The JSON will parsed and handed to your event handler as a second argument:
+
+ up.compiler('.google-map', function($element, pins) {
+
+ var map = new google.maps.Map($element);
+
+ pins.forEach(function(pin) {
+ var position = new google.maps.LatLng(pin.lat, pin.lng);
+ new google.maps.Marker({
+ position: position,
+ map: map,
+ title: pin.title
+ });
+ });
+
+ });
+
+
+ \#\#\#\# Migrating jQuery event handlers to `up.on`
+
+ Within the compiler, Up.js will bind `this` to the
+ native DOM element to help you migrate your existing jQuery code to
+ this new syntax.
+
+
+ @method up.compiler
+ @param {String} selector
+ The selector to match.
+ @param {Boolean} [options.batch=false]
+ If set to `true` and a fragment insertion contains multiple
+ elements matching the selector, `compiler` is only called once
+ with a jQuery collection containing all matching elements.
+ @param {Function($element, data)} compiler
+ The function to call when a matching element is inserted.
+ The function takes the new element as the first argument (as a jQuery object).
+ If the element has an `up-data` attribute, its value is parsed as JSON
+ and passed as a second argument.
+
+ The function may return a destructor function that destroys the compiled
+ object before it is removed from the DOM. The destructor is supposed to
+ clear global state such as time-outs and event handlers bound to the document.
+ The destructor is *not* expected to remove the element from the DOM, which
+ is already handled by [`up.destroy`](/up.flow#up.destroy).
+ */
+ compilers = [];
+ defaultCompilers = null;
+ compiler = function() {
+ var args, options, selector;
+ selector = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
+ if (!up.browser.isSupported()) {
+ return;
+ }
+ compiler = args.pop();
+ options = u.options(args[0], {
+ batch: false
+ });
+ return compilers.push({
+ selector: selector,
+ callback: compiler,
+ batch: options.batch
+ });
+ };
+ applyCompiler = function(compiler, $jqueryElement, nativeElement) {
+ var destroyer;
+ u.debug("Applying compiler %o on %o", compiler.selector, nativeElement);
+ destroyer = compiler.callback.apply(nativeElement, [$jqueryElement, data($jqueryElement)]);
+ if (u.isFunction(destroyer)) {
+ $jqueryElement.addClass(DESTROYABLE_CLASS);
+ return $jqueryElement.data(DESTROYER_KEY, destroyer);
+ }
+ };
+ compile = function($fragment) {
+ var $matches, i, len, results;
+ u.debug("Compiling fragment %o", $fragment);
+ results = [];
+ for (i = 0, len = compilers.length; i < len; i++) {
+ compiler = compilers[i];
+ $matches = u.findWithSelf($fragment, compiler.selector);
+ if ($matches.length) {
+ if (compiler.batch) {
+ results.push(applyCompiler(compiler, $matches, $matches.get()));
+ } else {
+ results.push($matches.each(function() {
+ return applyCompiler(compiler, $(this), this);
+ }));
+ }
+ } else {
+ results.push(void 0);
+ }
+ }
+ 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.
+
+ The API of this method is likely to change in the future, so
+ we can support getting or setting individual keys.
+
+ @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.compiler`](/up.magic#up.magic.compiler) 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) !== '') {
+ return JSON.parse(json);
+ } else {
+ return {};
+ }
+ };
+
+ /**
+ Makes a snapshot of the currently registered event listeners,
+ to later be restored through [`up.bus.reset`](/up.bus#up.bus.reset).
+
+ @private
+ @method up.magic.snapshot
+ */
+ snapshot = function() {
+ defaultLiveDescriptions = u.copy(liveDescriptions);
+ return defaultCompilers = u.copy(compilers);
+ };
+
+ /**
+ Resets the list of registered event listeners to the
+ moment when the framework was booted.
+
+ @private
+ @method up.magic.reset
+ */
+ reset = function() {
+ 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);
+ }
+ }
+ liveDescriptions = u.copy(defaultLiveDescriptions);
+ return compilers = u.copy(defaultCompilers);
+ };
+
+ /**
+ Sends a notification that the given element has been inserted
+ into the DOM. This causes Up.js to compile the fragment (apply
+ event listeners, etc.).
+
+ This method is called automatically if you change elements through
+ other Up.js methods. You will only need to call this if you
+ manipulate the DOM without going through Up.js.
+
+ @method up.ready
+ @param {String|Element|jQuery} selectorOrFragment
+ */
+ ready = function(selectorOrFragment) {
+ var $fragment;
+ $fragment = $(selectorOrFragment);
+ up.bus.emit('fragment:ready', $fragment);
+ return $fragment;
+ };
+ onEscape = function(handler) {
+ return live('keydown', 'body', function(event) {
+ if (u.escapePressed(event)) {
+ return handler(event);
+ }
+ });
+ };
+ up.bus.on('app:ready', (function() {
+ return ready(document.body);
+ }));
+ up.bus.on('fragment:ready', compile);
+ up.bus.on('fragment:destroy', destroy);
+ up.bus.on('framework:ready', snapshot);
+ up.bus.on('framework:reset', reset);
+ return {
+ compiler: compiler,
+ on: live,
+ ready: ready,
+ onEscape: onEscape,
+ data: data
+ };
+ })();
+
+ up.compiler = up.magic.compiler;
+
+ up.on = up.magic.on;
+
+ up.ready = up.magic.ready;
+
+ up.awaken = function() {
+ var args;
+ args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+ up.util.warn("up.awaken has been renamed to up.compiler and will be removed in a future version");
+ return up.compiler.apply(up, args);
+ };
+
+}).call(this);
+
+/**
+Manipulating the browser history
+=======
+
+\#\#\# Incomplete documentation!
+
+We need to work on this page:
+
+- Explain how the other modules manipulate history
+- Decide whether we want to expose these methods as public API
+- Document methods and parameters
+
+@class up.history
+ */
+
+(function() {
+ up.history = (function() {
+ var buildState, config, currentUrl, isCurrentUrl, manipulate, nextPreviousUrl, normalizeUrl, observeNewUrl, pop, previousUrl, push, register, replace, reset, restoreStateOnPop, u;
+ u = up.util;
+
+ /**
+ @method up.history.defaults
+ @param {Array<String>} [options.popTargets=['body']]
+ An array of CSS selectors to replace when the user goes
+ back in history.
+ @param {Boolean} [options.restoreScroll=true]
+ Whether to restore the known scroll positions
+ when the user goes back or forward in history.
+ */
+ config = u.config({
+ popTargets: ['body'],
+ restoreScroll: true
+ });
+
+ /**
+ Returns the previous URL in the browser history.
+
+ Note that this will only work reliably for history changes that
+ were applied by [`up.history.push`](#up.history.replace) or
+ [`up.history.replace`](#up.history.replace).
+
+ @method up.history.previousUrl
+ @protected
+ */
+ previousUrl = void 0;
+ nextPreviousUrl = void 0;
+ reset = function() {
+ config.reset();
+ previousUrl = void 0;
+ return nextPreviousUrl = void 0;
+ };
+ normalizeUrl = function(url) {
+ return u.normalizeUrl(url, {
+ hash: true
+ });
+ };
+
+ /**
+ Returns a normalized URL for the current history entry.
+
+ @method up.history.url
+ @protected
+ */
+ currentUrl = function() {
+ return normalizeUrl(up.browser.url());
+ };
+ isCurrentUrl = function(url) {
+ return normalizeUrl(url) === currentUrl();
+ };
+ observeNewUrl = function(url) {
+ console.log("observing new url %o", url);
+ if (nextPreviousUrl) {
+ previousUrl = nextPreviousUrl;
+ nextPreviousUrl = void 0;
+ }
+ return nextPreviousUrl = url;
+ };
+
+ /**
+ @method up.history.replace
+ @param {String} url
+ @param {Boolean} [options.force=false]
+ @protected
+ */
+ replace = function(url, options) {
+ return manipulate('replace', url, options);
+ };
+
+ /**
+ @method up.history.push
+ @param {String} url
+ @protected
+ */
+ push = function(url, options) {
+ return manipulate('push', url, options);
+ };
+ manipulate = function(method, url, options) {
+ var fullMethod, state;
+ options = u.options(options, {
+ force: false
+ });
+ if (options.force || !isCurrentUrl(url)) {
+ if (up.browser.canPushState()) {
+ fullMethod = method + "State";
+ state = buildState();
+ u.debug("Changing history to URL %o (%o)", url, method);
+ window.history[fullMethod](state, '', url);
+ return observeNewUrl(currentUrl());
+ } else {
+ return u.error("This browser doesn't support history.pushState");
+ }
+ }
+ };
+ buildState = function() {
+ return {
+ fromUp: true
+ };
+ };
+ restoreStateOnPop = function(state) {
+ var popSelector, url;
+ url = currentUrl();
+ u.debug("Restoring state %o (now on " + url + ")", state);
+ popSelector = config.popTargets.join(', ');
+ return up.replace(popSelector, url, {
+ history: false,
+ reveal: false,
+ transition: 'none',
+ saveScroll: false,
+ restoreScroll: config.restoreScroll
+ });
+ };
+ pop = function(event) {
+ var state;
+ u.debug("History state popped to URL %o", currentUrl());
+ observeNewUrl(currentUrl());
+ up.layout.saveScroll({
+ url: previousUrl
+ });
+ state = event.originalEvent.state;
+ if (state != null ? state.fromUp : void 0) {
+ return restoreStateOnPop(state);
+ } else {
+ return u.debug('Discarding unknown state %o', state);
+ }
+ };
+ if (up.browser.canPushState()) {
+ register = function() {
+ $(window).on("popstate", pop);
+ return replace(currentUrl(), {
+ force: true
+ });
+ };
+ if (typeof jasmine !== "undefined" && jasmine !== null) {
+ register();
+ } else {
+ setTimeout(register, 100);
+ }
+ }
+
+ /**
+ Changes the link's destination so it points to the previous URL.
+
+ Note that this will *not* call `location.back()`, but will set
+ the link's `up-href` attribute to the actual, previous URL.
+
+ \#\#\#\# Under the hood
+
+ This link ...
+
+ <a href="/default" up-back>
+ Go back
+ </a>
+
+ ... will be transformed to:
+
+ <a href="/default" up-href="/previous-page" up-restore-scroll up-follow>
+ Goback
+ </a>
+
+ @ujs
+ @method [up-back]
+ */
+ up.compiler('[up-back]', function($link) {
+ console.log("up-back", $link, previousUrl);
+ if (u.isPresent(previousUrl)) {
+ u.setMissingAttrs($link, {
+ 'up-href': previousUrl,
+ 'up-restore-scroll': ''
+ });
+ $link.removeAttr('up-back');
+ return up.link.makeFollowable($link);
+ }
+ });
+ up.bus.on('framework:reset', reset);
+ return {
+ defaults: config.update,
+ push: push,
+ replace: replace,
+ url: currentUrl,
+ previousUrl: function() {
+ return previousUrl;
+ },
+ normalizeUrl: normalizeUrl
+ };
+ })();
+
+}).call(this);
+
+/**
Viewport scrolling
==================
This modules contains functions to scroll the viewport and reveal contained elements.
@@ -1182,34 +1954,52 @@
(function() {
var slice = [].slice;
up.layout = (function() {
- var SCROLL_PROMISE_KEY, config, findViewport, finishScrolling, measureObstruction, reset, reveal, scroll, u;
+ var SCROLL_PROMISE_KEY, config, finishScrolling, lastScrollTops, measureObstruction, reset, restoreScroll, reveal, saveScroll, scroll, scrollTops, u, viewportOf, viewportSelector, viewports, viewportsIn;
u = up.util;
/**
+ Configures the application layout.
-
@method up.layout.defaults
- @param {String} [options.viewport]
- @param {String} [options.fixedTop]
- @param {String} [options.fixedBottom]
+ @param {Array<String>} [options.viewports]
+ An array of CSS selectors that find viewports
+ (containers that scroll their contents).
+ @param {Array<String>} [options.fixedTop]
+ An array of CSS selectors that find elements fixed to the
+ top edge of the screen (using `position: fixed`).
+ @param {Array<String>} [options.fixedBottom]
+ An array of CSS selectors that find elements fixed to the
+ bottom edge of the screen (using `position: fixed`).
@param {Number} [options.duration]
+ The duration of the scrolling animation in milliseconds.
+ Setting this to `0` will disable scrolling animations.
@param {String} [options.easing]
+ The timing function that controls the animation's acceleration.
+ See [W3C documentation](http://www.w3.org/TR/css3-transitions/#transition-timing-function)
+ for a list of pre-defined timing functions.
@param {Number} [options.snap]
+ When [revealing](#up.reveal) elements, Up.js will scroll an viewport
+ to the top when the revealed element is closer to the top than `options.snap`.
*/
config = u.config({
duration: 0,
- viewport: 'body, .up-modal, [up-viewport]',
- fixedTop: '[up-fixed~=top]',
- fixedBottom: '[up-fixed~=bottom]',
+ viewports: ['body', '.up-modal', '[up-viewport]'],
+ fixedTop: ['[up-fixed~=top]'],
+ fixedBottom: ['[up-fixed~=bottom]'],
snap: 50,
easing: 'swing'
});
+ lastScrollTops = u.cache({
+ size: 30,
+ key: up.history.normalizeUrl
+ });
reset = function() {
- return config.reset();
+ config.reset();
+ return lastScrollTops.clear();
};
SCROLL_PROMISE_KEY = 'up-scroll-promise';
/**
Scrolls the given viewport to the given Y-position.
@@ -1248,36 +2038,36 @@
The timing function that controls the acceleration for the scrolling's animation.
@return {Deferred}
A promise that will be resolved when the scrolling ends.
*/
scroll = function(viewport, scrollTop, options) {
- var $view, deferred, duration, easing, targetProps;
- $view = $(viewport);
+ var $viewport, deferred, duration, easing, targetProps;
+ $viewport = $(viewport);
options = u.options(options);
duration = u.option(options.duration, config.duration);
easing = u.option(options.easing, config.easing);
- finishScrolling($view);
+ finishScrolling($viewport);
if (duration > 0) {
deferred = $.Deferred();
- $view.data(SCROLL_PROMISE_KEY, deferred);
+ $viewport.data(SCROLL_PROMISE_KEY, deferred);
deferred.then(function() {
- $view.removeData(SCROLL_PROMISE_KEY);
- return $view.finish();
+ $viewport.removeData(SCROLL_PROMISE_KEY);
+ return $viewport.finish();
});
targetProps = {
scrollTop: scrollTop
};
- $view.animate(targetProps, {
+ $viewport.animate(targetProps, {
duration: duration,
easing: easing,
complete: function() {
return deferred.resolve();
}
});
return deferred;
} else {
- $view.scrollTop(scrollTop);
+ $viewport.scrollTop(scrollTop);
return u.resolvedDeferred();
}
};
/**
@@ -1303,21 +2093,21 @@
}
return parseInt(anchorPosition) + $obstructor.height();
};
fixedTopBottoms = (function() {
var i, len, ref, results;
- ref = $(config.fixedTop);
+ ref = $(config.fixedTop.join(', '));
results = [];
for (i = 0, len = ref.length; i < len; i++) {
obstructor = ref[i];
results.push(measurePosition(obstructor, 'top'));
}
return results;
})();
fixedBottomTops = (function() {
var i, len, ref, results;
- ref = $(config.fixedBottom);
+ ref = $(config.fixedBottom.join(', '));
results = [];
for (i = 0, len = ref.length; i < len; i++) {
obstructor = ref[i];
results.push(measurePosition(obstructor, 'bottom'));
}
@@ -1368,11 +2158,11 @@
*/
reveal = function(elementOrSelector, options) {
var $element, $viewport, elementDims, firstElementRow, lastElementRow, newScrollPos, obstruction, offsetShift, originalScrollPos, predictFirstVisibleRow, predictLastVisibleRow, snap, viewportHeight, viewportIsBody;
options = u.options(options);
$element = $(elementOrSelector);
- $viewport = findViewport($element, options.viewport);
+ $viewport = viewportOf($element, options.viewport);
snap = u.option(options.snap, config.snap);
viewportIsBody = $viewport.is('body');
viewportHeight = viewportIsBody ? u.clientSize().height : $viewport.height();
originalScrollPos = $viewport.scrollTop();
newScrollPos = originalScrollPos;
@@ -1412,29 +2202,137 @@
return scroll($viewport, newScrollPos, options);
} else {
return u.resolvedDeferred();
}
};
+ viewportSelector = function() {
+ return config.viewports.join(', ');
+ };
/**
- @private
- @method up.viewport.findViewport
+ Returns the viewport for the given element.
+
+ Throws an error if no viewport could be found.
+
+ @protected
+ @method up.layout.viewportOf
+ @param {String|Element|jQuery} selectorOrElement
*/
- findViewport = function($element, viewportSelectorOrElement) {
- var $viewport, vieportSelector;
+ viewportOf = function(selectorOrElement, viewportSelectorOrElement) {
+ var $element, $viewport, vieportSelector;
+ $element = $(selectorOrElement);
$viewport = void 0;
if (u.isJQuery(viewportSelectorOrElement)) {
$viewport = viewportSelectorOrElement;
} else {
- vieportSelector = u.presence(viewportSelectorOrElement) || config.viewport;
+ vieportSelector = u.presence(viewportSelectorOrElement) || viewportSelector();
$viewport = $element.closest(vieportSelector);
}
$viewport.length || u.error("Could not find viewport for %o", $element);
return $viewport;
};
/**
+ Returns a jQuery collection of all the viewports contained within the
+ given selector or element.
+
+ @protected
+ @method up.layout.viewportsIn
+ @param {String|Element|jQuery} selectorOrElement
+ @return jQuery
+ */
+ viewportsIn = function(selectorOrElement) {
+ var $element;
+ $element = $(selectorOrElement);
+ return u.findWithSelf($element, viewportSelector());
+ };
+
+ /**
+ Returns a jQuery collection of all the viewports on the screen.
+
+ @protected
+ @method up.layout.viewports
+ */
+ viewports = function() {
+ return $(viewportSelector());
+ };
+
+ /**
+ Returns a hash with scroll positions.
+
+ Each key in the hash is a viewport selector. The corresponding
+ value is the viewport's top scroll position:
+
+ up.layout.scrollTops()
+ => { '.main': 0, '.sidebar': 73 }
+
+ @protected
+ @method up.layout.scrollTops
+ @return Object<String, Number>
+ */
+ scrollTops = function() {
+ var $viewport, i, len, ref, topsBySelector, viewport;
+ topsBySelector = {};
+ ref = config.viewports;
+ for (i = 0, len = ref.length; i < len; i++) {
+ viewport = ref[i];
+ $viewport = $(viewport);
+ if ($viewport.length) {
+ topsBySelector[viewport] = $viewport.scrollTop();
+ }
+ }
+ return topsBySelector;
+ };
+
+ /**
+ Saves the top scroll positions of all the
+ viewports configured in `up.layout.defaults('viewports').
+ The saved scroll positions can be restored by calling
+ [`up.layout.restoreScroll()`](#up.layout.restoreScroll).
+
+ @method up.layout.saveScroll
+ @param {String} [options.url]
+ @param {Object<String, Number>} [options.tops]
+ @protected
+ */
+ saveScroll = function(options) {
+ var tops, url;
+ if (options == null) {
+ options = {};
+ }
+ url = u.option(options.url, up.history.url());
+ tops = u.option(options.tops, scrollTops());
+ return lastScrollTops.set(url, tops);
+ };
+
+ /**
+ Restores the top scroll positions of all the
+ viewports configured in `up.layout.defaults('viewports')`.
+
+ @method up.layout.restoreScroll
+ @param {String} [options.within]
+ @protected
+ */
+ restoreScroll = function(options) {
+ var $matchingViewport, $viewports, results, scrollTop, selector, tops;
+ if (options == null) {
+ options = {};
+ }
+ $viewports = options.within ? viewportsIn(options.within) : viewports();
+ tops = lastScrollTops.get(up.history.url());
+ results = [];
+ for (selector in tops) {
+ scrollTop = tops[selector];
+ $matchingViewport = $viewports.filter(selector);
+ results.push(up.scroll($matchingViewport, scrollTop, {
+ duration: 0
+ }));
+ }
+ return results;
+ };
+
+ /**
Marks this element as a scrolling container. Apply this ttribute if your app uses
a custom panel layout with fixed positioning instead of scrolling `<body>`.
[`up.reveal`](/up.reveal) will always try to scroll the viewport closest
to the element that is being revealed. By default this is the `<body>` element.
@@ -1514,11 +2412,17 @@
up.bus.on('framework:reset', reset);
return {
reveal: reveal,
scroll: scroll,
finishScrolling: finishScrolling,
- defaults: config.update
+ defaults: config.update,
+ viewportOf: viewportOf,
+ viewportsIn: viewportsIn,
+ viewports: viewports,
+ scrollTops: scrollTops,
+ saveScroll: saveScroll,
+ restoreScroll: restoreScroll
};
})();
up.scroll = up.layout.scroll;
@@ -1580,26 +2484,32 @@
@param {String|Boolean} [options.history=true]
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.scroll]
+ @param {String} [options.reveal]
Up.js will try to [reveal](/up.layout#up.reveal) the element being updated, by
scrolling its containing viewport. Set this option to `false` to prevent any scrolling.
If omitted, this will use the [default from `up.layout`](/up.layout#up.layout.defaults).
+ @param {Boolean} [options.restoreScroll=`false`]
+ If set to true, Up.js will try to restore the scroll position
+ of all the viewports within the updated element. The position
+ will be reset to the last known top position before a previous
+ history change for the current URL.
@param {Boolean} [options.cache]
Whether to use a [cached response](/up.proxy) if available.
@param {String} [options.historyMethod='push']
@return {Promise}
A promise that will be resolved when the page has been updated.
*/
replace = function(selectorOrElement, url, options) {
var promise, request, selector;
+ u.debug("Replace %o with %o", selectorOrElement, url);
options = u.options(options);
selector = u.presence(selectorOrElement) ? selectorOrElement : u.createSelectorFromElement($(selectorOrElement));
- if (!up.browser.canPushState() && !u.castsToFalse(options.history)) {
+ if (!up.browser.canPushState() && options.history !== false) {
if (!options.preload) {
up.browser.loadPage(url, u.only(options, 'method'));
}
return u.resolvedPromise();
}
@@ -1621,14 +2531,14 @@
selector: selector
};
up.proxy.alias(request, newRequest);
url = currentLocation;
}
- if (u.isMissing(options.history) || u.castsToTrue(options.history)) {
+ if (options.history !== false) {
options.history = url;
}
- if (u.isMissing(options.source) || u.castsToTrue(options.source)) {
+ if (options.source !== false) {
options.source = url;
}
if (!options.preload) {
return implant(selector, html, options);
}
@@ -1651,31 +2561,24 @@
@method up.flow.implant
@protected
@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']
+ @param {Object} [options]
+ See options for [`up.replace`](#up.replace).
*/
implant = function(selector, html, options) {
var $new, $old, j, len, ref, response, results, step;
options = u.options(options, {
historyMethod: 'push'
});
- if (u.castsToFalse(options.history)) {
- options.history = null;
- }
- if (u.castsToFalse(options.scroll)) {
- options.scroll = false;
- }
options.source = u.option(options.source, options.history);
response = parseResponse(html);
options.title || (options.title = response.title());
+ if (options.saveScroll !== false) {
+ up.layout.saveScroll();
+ }
ref = parseImplantSteps(selector, options);
results = [];
for (j = 0, len = ref.length; j < len; j++) {
step = ref[j];
$old = findOldFragment(step.selector);
@@ -1712,16 +2615,12 @@
}
}
};
};
reveal = function($element, options) {
- var viewport;
- viewport = options.scroll;
- if (viewport !== false) {
- return up.reveal($element, {
- viewport: viewport
- });
+ if (options.reveal !== false) {
+ return up.reveal($element);
} else {
return u.resolvedDeferred();
}
};
elementsInserted = function($new, options) {
@@ -1732,11 +2631,18 @@
if (options.title) {
document.title = options.title;
}
up.history[options.historyMethod](options.history);
}
- setSource($new, options.source);
+ if (options.source !== false) {
+ setSource($new, options.source);
+ }
+ if (options.restoreScroll) {
+ up.layout.restoreScroll({
+ within: $new
+ });
+ }
autofocus($new);
return up.ready($new);
};
swapElements = function($old, $new, pseudoClass, transition, options) {
var $wrapper, insertionMethod;
@@ -1928,508 +2834,10 @@
up.first = up.flow.first;
}).call(this);
/**
-Registering behavior and custom elements
-========================================
-
-Up.js keeps a persistent Javascript environment during page transitions.
-To prevent memory leaks it is important to cleanly set up and tear down
-event handlers and custom elements.
-
-\#\#\# Incomplete documentation!
-
-We need to work on this page:
-
-- Better class-level introduction for this module
-
-@class up.magic
- */
-
-(function() {
- var slice = [].slice;
-
- up.magic = (function() {
- var DESTROYABLE_CLASS, DESTROYER_KEY, applyCompiler, compile, compiler, compilers, data, defaultCompilers, defaultLiveDescriptions, destroy, live, liveDescriptions, onEscape, ready, reset, snapshot, u;
- u = up.util;
- DESTROYABLE_CLASS = 'up-destroyable';
- DESTROYER_KEY = 'up-destroyer';
-
- /**
- Binds an event handler to the document, which will be executed whenever the
- given event is triggered on the given selector:
-
- up.on('click', '.button', function(event, $element) {
- console.log("Someone clicked the button %o", $element);
- });
-
- This is roughly equivalent to binding a jQuery element to `document`.
-
-
- \#\#\#\# Attaching structured data
-
- In case you want to attach structured data to the event you're observing,
- you can serialize the data to JSON and put it into an `[up-data]` attribute:
-
- <span class="person" up-data="{ age: 18, name: 'Bob' }">Bob</span>
- <span class="person" up-data="{ age: 22, name: 'Jim' }">Jim</span>
-
- The JSON will parsed and handed to your event handler as a third argument:
-
- up.on('click', '.person', function(event, $element, data) {
- console.log("This is %o who is %o years old", data.name, data.age);
- });
-
-
- \#\#\#\# Migrating jQuery event handlers to `up.on`
-
- Within the event handler, Up.js will bind `this` to the
- native DOM element to help you migrate your existing jQuery code to
- this new syntax.
-
- So if you had this before:
-
- $(document).on('click', '.button', function() {
- $(this).something();
- });
-
- ... you can simply copy the event handler to `up.on`:
-
- up.on('click', '.button', function() {
- $(this).something();
- });
-
-
- @method up.on
- @param {String} events
- A space-separated list of event names to bind.
- @param {String} selector
- The selector an on which the event must be triggered.
- @param {Function(event, $element, data)} behavior
- The handler that should be called.
- The function takes the affected element as the first argument (as a jQuery object).
- If the element has an `up-data` attribute, its value is parsed as JSON
- and passed as a second argument.
- */
- liveDescriptions = [];
- defaultLiveDescriptions = null;
- live = function(events, selector, behavior) {
- 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);
- };
-
- /**
- Registers a function to be called whenever an element with
- the given selector is inserted into the DOM through Up.js.
-
- This is a great way to integrate jQuery plugins.
- Let's say your Javascript plugin wants you to call `lightboxify()`
- on links that should open a lightbox. You decide to
- do this for all links with an `[rel=lightbox]` attribute:
-
- <a href="river.png" rel="lightbox">River</a>
- <a href="ocean.png" rel="lightbox">Ocean</a>
-
- This Javascript will do exactly that:
-
- up.compiler('a[rel=lightbox]', function($element) {
- $element.lightboxify();
- });
-
- Note that within the compiler, Up.js will bind `this` to the
- native DOM element to help you migrate your existing jQuery code to
- this new syntax.
-
-
- \#\#\#\# Custom elements
-
- You can also use `up.compiler` to implement custom elements like this:
-
- <clock></clock>
-
- Here is the Javascript that inserts the current time into to these elements:
-
- up.compiler('clock', function($element) {
- var now = new Date();
- $element.text(now.toString()));
- });
-
-
- \#\#\#\# Cleaning up after yourself
-
- If your compiler returns a function, Up.js will use this as a *destructor* to
- clean up if the element leaves the DOM. Note that in Up.js the same DOM ad Javascript environment
- will persist through many page loads, so it's important to not create
- [memory leaks](https://makandracards.com/makandra/31325-how-to-create-memory-leaks-in-jquery).
-
- You should clean up after yourself whenever your compilers have global
- side effects, like a [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval)
- or event handlers bound to the document root.
-
- Here is a version of `<clock>` that updates
- the time every second, and cleans up once it's done:
-
- up.compiler('clock', function($element) {
-
- function update() {
- var now = new Date();
- $element.text(now.toString()));
- }
-
- setInterval(update, 1000);
-
- return function() {
- clearInterval(update);
- };
-
- });
-
- If we didn't clean up after ourselves, we would have many ticking intervals
- operating on detached DOM elements after we have created and removed a couple
- of `<clock>` elements.
-
-
- \#\#\#\# Attaching structured data
-
- In case you want to attach structured data to the event you're observing,
- you can serialize the data to JSON and put it into an `[up-data]` attribute.
- For instance, a container for a [Google Map](https://developers.google.com/maps/documentation/javascript/tutorial)
- might attach the location and names of its marker pins:
-
- <div class="google-map" up-data="[
- { lat: 48.36, lng: 10.99, title: 'Friedberg' },
- { lat: 48.75, lng: 11.45, title: 'Ingolstadt' }
- ]"></div>
-
- The JSON will parsed and handed to your event handler as a second argument:
-
- up.compiler('.google-map', function($element, pins) {
-
- var map = new google.maps.Map($element);
-
- pins.forEach(function(pin) {
- var position = new google.maps.LatLng(pin.lat, pin.lng);
- new google.maps.Marker({
- position: position,
- map: map,
- title: pin.title
- });
- });
-
- });
-
-
- \#\#\#\# Migrating jQuery event handlers to `up.on`
-
- Within the compiler, Up.js will bind `this` to the
- native DOM element to help you migrate your existing jQuery code to
- this new syntax.
-
-
- @method up.compiler
- @param {String} selector
- The selector to match.
- @param {Boolean} [options.batch=false]
- If set to `true` and a fragment insertion contains multiple
- elements matching the selector, `compiler` is only called once
- with a jQuery collection containing all matching elements.
- @param {Function($element, data)} compiler
- The function to call when a matching element is inserted.
- The function takes the new element as the first argument (as a jQuery object).
- If the element has an `up-data` attribute, its value is parsed as JSON
- and passed as a second argument.
-
- The function may return a destructor function that destroys the compiled
- object before it is removed from the DOM. The destructor is supposed to
- clear global state such as time-outs and event handlers bound to the document.
- The destructor is *not* expected to remove the element from the DOM, which
- is already handled by [`up.destroy`](/up.flow#up.destroy).
- */
- compilers = [];
- defaultCompilers = null;
- compiler = function() {
- var args, options, selector;
- selector = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
- if (!up.browser.isSupported()) {
- return;
- }
- compiler = args.pop();
- options = u.options(args[0], {
- batch: false
- });
- return compilers.push({
- selector: selector,
- callback: compiler,
- batch: options.batch
- });
- };
- applyCompiler = function(compiler, $jqueryElement, nativeElement) {
- var destroyer;
- u.debug("Applying compiler %o on %o", compiler.selector, nativeElement);
- destroyer = compiler.callback.apply(nativeElement, [$jqueryElement, data($jqueryElement)]);
- if (u.isFunction(destroyer)) {
- $jqueryElement.addClass(DESTROYABLE_CLASS);
- return $jqueryElement.data(DESTROYER_KEY, destroyer);
- }
- };
- compile = function($fragment) {
- var $matches, i, len, results;
- u.debug("Compiling fragment %o", $fragment);
- results = [];
- for (i = 0, len = compilers.length; i < len; i++) {
- compiler = compilers[i];
- $matches = u.findWithSelf($fragment, compiler.selector);
- if ($matches.length) {
- if (compiler.batch) {
- results.push(applyCompiler(compiler, $matches, $matches.get()));
- } else {
- results.push($matches.each(function() {
- return applyCompiler(compiler, $(this), this);
- }));
- }
- } else {
- results.push(void 0);
- }
- }
- 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.
-
- The API of this method is likely to change in the future, so
- we can support getting or setting individual keys.
-
- @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.compiler`](/up.magic#up.magic.compiler) 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) !== '') {
- return JSON.parse(json);
- } else {
- return {};
- }
- };
-
- /**
- Makes a snapshot of the currently registered event listeners,
- to later be restored through [`up.bus.reset`](/up.bus#up.bus.reset).
-
- @private
- @method up.magic.snapshot
- */
- snapshot = function() {
- defaultLiveDescriptions = u.copy(liveDescriptions);
- return defaultCompilers = u.copy(compilers);
- };
-
- /**
- Resets the list of registered event listeners to the
- moment when the framework was booted.
-
- @private
- @method up.magic.reset
- */
- reset = function() {
- 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);
- }
- }
- liveDescriptions = u.copy(defaultLiveDescriptions);
- return compilers = u.copy(defaultCompilers);
- };
-
- /**
- Sends a notification that the given element has been inserted
- into the DOM. This causes Up.js to compile the fragment (apply
- event listeners, etc.).
-
- This method is called automatically if you change elements through
- other Up.js methods. You will only need to call this if you
- manipulate the DOM without going through Up.js.
-
- @method up.ready
- @param {String|Element|jQuery} selectorOrFragment
- */
- ready = function(selectorOrFragment) {
- var $fragment;
- $fragment = $(selectorOrFragment);
- up.bus.emit('fragment:ready', $fragment);
- return $fragment;
- };
- onEscape = function(handler) {
- return live('keydown', 'body', function(event) {
- if (u.escapePressed(event)) {
- return handler(event);
- }
- });
- };
- up.bus.on('app:ready', (function() {
- return ready(document.body);
- }));
- up.bus.on('fragment:ready', compile);
- up.bus.on('fragment:destroy', destroy);
- up.bus.on('framework:ready', snapshot);
- up.bus.on('framework:reset', reset);
- return {
- compiler: compiler,
- on: live,
- ready: ready,
- onEscape: onEscape,
- data: data
- };
- })();
-
- up.compiler = up.magic.compiler;
-
- up.on = up.magic.on;
-
- up.ready = up.magic.ready;
-
- up.awaken = function() {
- var args;
- args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
- up.util.warn("up.awaken has been renamed to up.compiler and will be removed in a future version");
- return up.compiler.apply(up, args);
- };
-
-}).call(this);
-
-/**
-Manipulating the browser history
-=======
-
-\#\#\# Incomplete documentation!
-
-We need to work on this page:
-
-- Explain how the other modules manipulate history
-- Decide whether we want to expose these methods as public API
-- Document methods and parameters
-
-@class up.history
- */
-
-(function() {
- up.history = (function() {
- var isCurrentUrl, manipulate, pop, push, replace, u;
- u = up.util;
- isCurrentUrl = function(url) {
- return u.normalizeUrl(url, {
- hash: true
- }) === u.normalizeUrl(up.browser.url(), {
- hash: true
- });
- };
-
- /**
- @method up.history.replace
- @param {String} url
- @protected
- */
- replace = function(url, options) {
- options = u.options(options, {
- force: false
- });
- if (options.force || !isCurrentUrl(url)) {
- return manipulate("replace", url);
- }
- };
-
- /**
- @method up.history.push
- @param {String} url
- @protected
- */
- push = function(url) {
- if (!isCurrentUrl(url)) {
- return manipulate("push", url);
- }
- };
- manipulate = function(method, url) {
- if (up.browser.canPushState()) {
- method += "State";
- return window.history[method]({
- fromUp: true
- }, '', url);
- } else {
- return u.error("This browser doesn't support history.pushState");
- }
- };
- pop = function(event) {
- var state;
- state = event.originalEvent.state;
- if (state != null ? state.fromUp : void 0) {
- u.debug("Restoring state %o (now on " + (up.browser.url()) + ")", state);
- return up.visit(up.browser.url(), {
- historyMethod: 'replace'
- });
- } else {
- return u.debug('Discarding unknown state %o', state);
- }
- };
- if (up.browser.canPushState()) {
- setTimeout((function() {
- $(window).on("popstate", pop);
- return replace(up.browser.url(), {
- force: true
- });
- }), 200);
- }
- return {
- push: push,
- replace: replace
- };
- })();
-
-}).call(this);
-
-/**
Animation and transitions
=========================
Any fragment change in Up.js can be animated.
@@ -2553,10 +2961,13 @@
animate = function(elementOrSelector, animation, options) {
var $element;
$element = $(elementOrSelector);
finish($element);
options = animateOptions(options);
+ if (animation === 'none' || animation === false) {
+ none();
+ }
if (u.isFunction(animation)) {
return assertIsDeferred(animation($element, options), animation);
} else if (u.isString(animation)) {
return animate($element, findAnimation(animation), options);
} else if (u.isHash(animation)) {
@@ -2709,11 +3120,11 @@
options = animateOptions(options);
$old = $(source);
$new = $(target);
finish($old);
finish($new);
- if (transitionOrName === 'none') {
+ if (transitionOrName === 'none' || transitionOrName === false) {
return none();
} else if (transition = u.presence(transitionOrName, u.isFunction) || transitions[transitionOrName]) {
return withGhosts($old, $new, function($oldGhost, $newGhost) {
return assertIsDeferred(transition($oldGhost, $newGhost, options), transitionOrName);
});
@@ -3078,13 +3489,12 @@
@class up.proxy
*/
(function() {
up.proxy = (function() {
- var $waitingLink, SAFE_HTTP_METHODS, ajax, alias, busy, busyDelayTimer, busyEventEmitted, cache, cacheKey, cancelBusyDelay, cancelPreloadDelay, checkPreload, clear, config, get, idle, isFresh, isIdempotent, load, loadEnded, loadStarted, normalizeRequest, pendingCount, preload, preloadDelayTimer, remove, reset, set, startPreloadDelay, timestamp, trim, u;
+ var $waitingLink, SAFE_HTTP_METHODS, ajax, alias, busy, busyDelayTimer, busyEventEmitted, cache, cacheKey, cancelBusyDelay, cancelPreloadDelay, checkPreload, clear, config, get, idle, isIdempotent, load, loadEnded, loadStarted, normalizeRequest, pendingCount, preload, preloadDelayTimer, remove, reset, set, startPreloadDelay, u;
u = up.util;
- cache = void 0;
$waitingLink = void 0;
preloadDelayTimer = void 0;
busyDelayTimer = void 0;
pendingCount = void 0;
busyEventEmitted = void 0;
@@ -3108,55 +3518,67 @@
busyDelay: 300,
preloadDelay: 75,
cacheSize: 70,
cacheExpiry: 1000 * 60 * 5
});
+ cacheKey = function(request) {
+ normalizeRequest(request);
+ return [request.url, request.method, request.data, request.selector].join('|');
+ };
+ cache = u.cache({
+ size: function() {
+ return config.cacheSize;
+ },
+ expiry: function() {
+ return config.cacheExpiry;
+ },
+ key: cacheKey,
+ log: 'up.proxy'
+ });
+
+ /**
+ @protected
+ @method up.proxy.get
+ */
+ get = cache.get;
+
+ /**
+ @protected
+ @method up.proxy.set
+ */
+ set = cache.set;
+
+ /**
+ @protected
+ @method up.proxy.remove
+ */
+ remove = cache.remove;
+
+ /**
+ @protected
+ @method up.proxy.clear
+ */
+ clear = cache.clear;
cancelPreloadDelay = function() {
clearTimeout(preloadDelayTimer);
return preloadDelayTimer = null;
};
cancelBusyDelay = function() {
clearTimeout(busyDelayTimer);
return busyDelayTimer = null;
};
reset = function() {
- cache = {};
$waitingLink = null;
cancelPreloadDelay();
cancelBusyDelay();
pendingCount = 0;
config.reset();
- return busyEventEmitted = false;
+ busyEventEmitted = false;
+ return cache.clear();
};
reset();
- cacheKey = function(request) {
- normalizeRequest(request);
- return [request.url, request.method, request.data, request.selector].join('|');
- };
- trim = function() {
- var keys, oldestKey, oldestTimestamp;
- keys = u.keys(cache);
- if (keys.length > config.cacheSize) {
- oldestKey = null;
- oldestTimestamp = null;
- u.each(keys, function(key) {
- var promise, timestamp;
- promise = cache[key];
- timestamp = promise.timestamp;
- if (!oldestTimestamp || oldestTimestamp > timestamp) {
- oldestKey = key;
- return oldestTimestamp = timestamp;
- }
- });
- if (oldestKey) {
- return delete cache[oldestKey];
- }
- }
- };
- timestamp = function() {
- return (new Date()).valueOf();
- };
+ alias = cache.alias;
normalizeRequest = function(request) {
if (!request._normalized) {
request.method = u.normalizeMethod(request.method);
if (request.url) {
request.url = u.normalizeUrl(request.url);
@@ -3164,17 +3586,10 @@
request.selector || (request.selector = 'body');
request._normalized = true;
}
return request;
};
- alias = function(oldRequest, newRequest) {
- var promise;
- u.debug("Aliasing %o to %o", oldRequest, newRequest);
- 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.
@@ -3196,12 +3611,12 @@
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, pending, promise, request;
- forceCache = u.castsToTrue(options.cache);
- ignoreCache = u.castsToFalse(options.cache);
+ forceCache = options.cache === true;
+ ignoreCache = options.cache === false;
request = u.only(options, 'url', 'method', 'data', 'selector', '_normalized');
pending = true;
if (!isIdempotent(request) && !forceCache) {
clear();
promise = load(request);
@@ -3282,68 +3697,10 @@
};
isIdempotent = function(request) {
normalizeRequest(request);
return u.contains(SAFE_HTTP_METHODS, request.method);
};
- isFresh = function(promise) {
- var timeSinceTouch;
- timeSinceTouch = timestamp() - promise.timestamp;
- return timeSinceTouch < config.cacheExpiry;
- };
-
- /**
- @protected
- @method up.proxy.get
- */
- get = function(request) {
- var key, promise;
- key = cacheKey(request);
- if (promise = cache[key]) {
- if (!isFresh(promise)) {
- u.debug("Discarding stale cache entry for %o (%o)", request.url, request);
- remove(request);
- return void 0;
- } else {
- u.debug("Cache hit for %o (%o)", request.url, request);
- return promise;
- }
- } else {
- u.debug("Cache miss for %o (%o)", request.url, request);
- return void 0;
- }
- };
-
- /**
- @protected
- @method up.proxy.set
- */
- set = function(request, promise) {
- var key;
- trim();
- key = cacheKey(request);
- promise.timestamp = timestamp();
- cache[key] = promise;
- return promise;
- };
-
- /**
- @protected
- @method up.proxy.remove
- */
- remove = function(request) {
- var key;
- key = cacheKey(request);
- return delete cache[key];
- };
-
- /**
- @protected
- @method up.proxy.clear
- */
- clear = function() {
- return cache = {};
- };
checkPreload = function($link) {
var curriedPreload, delay;
delay = parseInt(u.presentAttr($link, 'up-delay')) || config.preloadDelay;
if (!$link.is($waitingLink)) {
$waitingLink = $link;
@@ -3501,11 +3858,11 @@
@class up.link
*/
(function() {
up.link = (function() {
- var childClicked, follow, followMethod, shouldProcessLinkEvent, u, visit;
+ var childClicked, follow, followMethod, makeFollowable, shouldProcessLinkEvent, u, visit;
u = up.util;
/**
Visits the given URL without a full page load.
This is done by fetching `url` through an AJAX request
@@ -3552,13 +3909,12 @@
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} [options.scroll]
- An element or selector that will be scrolled to the top in
- case the replaced element is not visible in the viewport.
+ @param {Element|jQuery|String} [options.reveal]
+ Whether to reveal the followed element within its viewport.
@param {Number} [options.duration]
The duration of the transition. See [`up.morph`](/up.motion#up.morph).
@param {Number} [options.delay]
The delay before the transition starts. See [`up.morph`](/up.motion#up.morph).
@param {String} [options.easing]
@@ -3568,14 +3924,15 @@
var $link, selector, url;
$link = $(link);
options = u.options(options);
url = u.option($link.attr('up-href'), $link.attr('href'));
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.scroll, $link.attr('up-scroll'), 'body');
- options.cache = u.option(options.cache, $link.attr('up-cache'));
+ options.transition = u.option(options.transition, u.castedAttr($link, 'up-transition'), u.castedAttr($link, 'up-animation'));
+ options.history = u.option(options.history, u.castedAttr($link, 'up-history'));
+ options.reveal = u.option(options.reveal, u.castedAttr($link, 'up-reveal'));
+ options.cache = u.option(options.cache, u.castedAttr($link, 'up-cache'));
+ options.restoreScroll = u.option(options.restoreScroll, u.castedAttr($link, 'up-restore-scroll'));
options.method = followMethod($link, options);
options = u.merge(options, up.motion.animateOptions(options, $link));
return up.replace(selector, url, options);
};
@@ -3640,10 +3997,13 @@
@param {String} up-target
The CSS selector to replace
@param [up-href]
The destination URL to follow.
If omitted, the the link's `href` attribute will be used.
+ @param [up-restore-scroll='false']
+ Whether to restore the scroll position of all viewports
+ within the target selector.
*/
up.on('click', 'a[up-target], [up-href][up-target]', function(event, $link) {
if (shouldProcessLinkEvent(event, $link)) {
if ($link.is('[up-instant]')) {
return event.preventDefault();
@@ -3693,10 +4053,27 @@
shouldProcessLinkEvent = function(event, $link) {
return u.isUnmodifiedMouseEvent(event) && !childClicked(event, $link);
};
/**
+ Makes sure that the given link is handled by Up.js.
+
+ This is done by giving the link an `up-follow` attribute
+ if it doesn't already have it an `up-target` or `up-follow` attribute.
+
+ @method up.link.makeFollowable
+ @protected
+ */
+ makeFollowable = function(link) {
+ var $link;
+ $link = $(link);
+ if (u.isMissing($link.attr('up-target')) && u.isMissing($link.attr('up-follow'))) {
+ return $link.attr('up-follow', '');
+ }
+ };
+
+ /**
If applied on a link, Follows this link via AJAX and replaces the
current `<body>` element with the response's `<body>` element.
Example:
@@ -3718,10 +4095,13 @@
@method a[up-follow]
@ujs
@param [up-href]
The destination URL to follow.
If omitted, the the link's `href` attribute will be used.
+ @param [up-restore-scroll='false']
+ Whether to restore the scroll position of all viewports
+ within the response.
*/
up.on('click', 'a[up-follow], [up-href][up-follow]', function(event, $link) {
if (shouldProcessLinkEvent(event, $link)) {
if ($link.is('[up-instant]')) {
return event.preventDefault();
@@ -3748,14 +4128,14 @@
(`up-target`, `up-instant`, `up-preload`, etc.).
@ujs
@method [up-expand]
*/
- up.compiler('[up-expand]', function($fragment) {
+ up.compiler('[up-expand]', function($area) {
var attribute, i, len, link, name, newAttrs, ref, upAttributePattern;
- link = $fragment.find('a, [up-href]').get(0);
- link || u.error('No link to expand within %o', $fragment);
+ link = $area.find('a, [up-href]').get(0);
+ link || u.error('No link to expand within %o', $area);
upAttributePattern = /^up-/;
newAttrs = {};
newAttrs['up-href'] = $(link).attr('href');
ref = link.attributes;
for (i = 0, len = ref.length; i < len; i++) {
@@ -3763,13 +4143,13 @@
name = attribute.name;
if (name.match(upAttributePattern)) {
newAttrs[name] = attribute.value;
}
}
- u.isGiven(newAttrs['up-target']) || (newAttrs['up-follow'] = '');
- u.setMissingAttrs($fragment, newAttrs);
- return $fragment.removeAttr('up-expand');
+ u.setMissingAttrs($area, newAttrs);
+ $area.removeAttr('up-expand');
+ return makeFollowable($area);
});
/**
Marks up the current link to be followed *as fast as possible*.
This is done by:
@@ -3789,16 +4169,16 @@
@method [up-dash]
@ujs
*/
up.compiler('[up-dash]', function($element) {
var newAttrs, target;
- target = $element.attr('up-dash');
+ target = u.castedAttr($element, 'up-dash');
newAttrs = {
'up-preload': 'true',
'up-instant': 'true'
};
- if (u.isBlank(target) || u.castsToTrue(target)) {
+ if (target === true) {
newAttrs['up-follow'] = '';
} else {
newAttrs['up-target'] = target;
}
u.setMissingAttrs($element, newAttrs);
@@ -3806,10 +4186,11 @@
});
return {
knife: eval(typeof Knife !== "undefined" && Knife !== null ? Knife.point : void 0),
visit: visit,
follow: follow,
+ makeFollowable: makeFollowable,
childClicked: childClicked,
followMethod: followMethod
};
})();
@@ -3905,19 +4286,19 @@
options = u.options(options);
successSelector = u.option(options.target, $form.attr('up-target'), 'body');
failureSelector = u.option(options.failTarget, $form.attr('up-fail-target'), function() {
return u.createSelectorFromElement($form);
});
- historyOption = u.option(options.history, $form.attr('up-history'), true);
- successTransition = u.option(options.transition, $form.attr('up-transition'));
- failureTransition = u.option(options.failTransition, $form.attr('up-fail-transition'), successTransition);
+ historyOption = u.option(options.history, u.castedAttr($form, 'up-history'), true);
+ successTransition = u.option(options.transition, u.castedAttr($form, 'up-transition'));
+ failureTransition = u.option(options.failTransition, u.castedAttr($form, 'up-fail-transition'), successTransition);
httpMethod = u.option(options.method, $form.attr('up-method'), $form.attr('data-method'), $form.attr('method'), 'post').toUpperCase();
animateOptions = up.motion.animateOptions(options, $form);
- useCache = u.option(options.cache, $form.attr('up-cache'));
+ useCache = u.option(options.cache, u.castedAttr($form, 'up-cache'));
url = u.option(options.url, $form.attr('action'), up.browser.url());
$form.addClass('up-active');
- if (!up.browser.canPushState() && !u.castsToFalse(historyOption)) {
+ if (!up.browser.canPushState() && historyOption !== false) {
$form.get(0).submit();
return;
}
request = {
url: url,
@@ -3926,11 +4307,20 @@
selector: successSelector,
cache: useCache
};
successUrl = function(xhr) {
var currentLocation;
- 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;
+ url = void 0;
+ if (u.isGiven(historyOption)) {
+ if (historyOption === false || u.isString(historyOption)) {
+ url = historyOption;
+ } else if (currentLocation = u.locationFromXhr(xhr)) {
+ url = currentLocation;
+ } else if (request.type === 'GET') {
+ url = request.url + '?' + request.data;
+ }
+ }
return u.option(url, false);
};
return up.proxy.ajax(request).always(function() {
return $form.removeClass('up-active');
}).done(function(html, textStatus, xhr) {
@@ -4296,12 +4686,12 @@
options = u.options(options);
url = u.option(options.url, $link.attr('href'));
selector = u.option(options.target, $link.attr('up-popup'), 'body');
position = u.option(options.position, $link.attr('up-position'), config.position);
animation = u.option(options.animation, $link.attr('up-animation'), config.openAnimation);
- sticky = u.option(options.sticky, $link.is('[up-sticky]'));
- history = up.browser.canPushState() ? u.option(options.history, $link.attr('up-history'), false) : false;
+ sticky = u.option(options.sticky, u.castedAttr($link, 'up-sticky'));
+ history = up.browser.canPushState() ? u.option(options.history, u.castedAttr($link, 'up-history'), false) : false;
animateOptions = up.motion.animateOptions(options, $link);
close();
$popup = createHiddenPopup($link, selector, sticky);
return up.replace(selector, url, {
history: history,
@@ -4657,12 +5047,12 @@
selector = u.option(options.target, $link.attr('up-modal'), 'body');
width = u.option(options.width, $link.attr('up-width'), config.width);
maxWidth = u.option(options.maxWidth, $link.attr('up-max-width'), config.maxWidth);
height = u.option(options.height, $link.attr('up-height'), config.height);
animation = u.option(options.animation, $link.attr('up-animation'), config.openAnimation);
- sticky = u.option(options.sticky, $link.is('[up-sticky]'));
- history = up.browser.canPushState() ? u.option(options.history, $link.attr('up-history'), true) : false;
+ sticky = u.option(options.sticky, u.castedAttr($link, 'up-sticky'));
+ history = up.browser.canPushState() ? u.option(options.history, u.castedAttr($link, 'up-history'), true) : false;
animateOptions = up.motion.animateOptions(options, $link);
close();
$modal = createHiddenModal({
selector: selector,
width: width,
@@ -4936,11 +5326,11 @@
options = {};
}
$link = $(linkOrSelector);
html = u.option(options.html, $link.attr('up-tooltip'), $link.attr('title'));
position = u.option(options.position, $link.attr('up-position'), 'top');
- animation = u.option(options.animation, $link.attr('up-animation'), 'fade-in');
+ animation = u.option(options.animation, u.castedAttr($link, 'up-animation'), 'fade-in');
animateOptions = up.motion.animateOptions(options, $link);
close();
$tooltip = createElement(html);
setPosition($link, $tooltip, position);
return up.animate($tooltip, animation, animateOptions);
@@ -5026,21 +5416,20 @@
@method up.navigation.defaults
@param {Number} [options.currentClass]
The class to set on [links that point the current location](#up-current).
*/
config = u.config({
- currentClass: 'up-current'
+ currentClasses: ['up-current']
});
reset = function() {
return config.reset();
};
currentClass = function() {
- var klass;
- klass = config.currentClass;
- if (!u.contains(klass, 'up-current')) {
- klass += ' up-current';
- }
- return klass;
+ var classes;
+ classes = config.currentClasses;
+ classes = classes.concat(['up-current']);
+ classes = u.uniq(classes);
+ return classes.join(' ');
};
CLASS_ACTIVE = 'up-active';
SELECTORS_SECTION = ['a', '[up-href]', '[up-alias]'];
SELECTOR_SECTION = SELECTORS_SECTION.join(', ');
SELECTOR_SECTION_INSTANT = ((function() {