Load more tasks
\#\#\#\# Following on mousedown
By also adding an `up-instant` attribute, the page will be fetched
on `mousedown` instead of `click`, making the interaction even faster:
User list
Note that using `[up-instant]` will prevent a user from canceling a link
click by moving the mouse away from the interaction area. However, for
navigation actions this isn't needed. E.g. popular operation
systems switch tabs on `mousedown`.
@method a[up-target]
@ujs
@param {String} up-target
The CSS selector to replace
@param up-instant
If set, fetches the element on `mousedown` instead of `click`.
This makes the interaction faster.
*/
up.on('click', 'a[up-target]', function(event, $link) {
event.preventDefault();
if (!$link.is('[up-instant]')) {
return follow($link);
}
});
up.on('mousedown', 'a[up-target][up-instant]', function(event, $link) {
if (activeInstantLink(event, $link)) {
event.preventDefault();
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 `` element with the response's `` element
User list
\#\#\#\# Following on mousedown
By also adding an `up-instant` attribute, the page will be fetched
on `mousedown` instead of `click`, making the interaction even faster:
User list
Note that using `[up-instant]` will prevent a user from canceling a link
click by moving the mouse away from the interaction area. However, for
navigation actions this isn't needed. E.g. popular operation
systems switch tabs on `mousedown`.
\#\#\#\# Enlarging the click area
You can also apply `[up-follow]` to any element that contains a link
in order to enlarge the link's click area:
In the example above, clicking anywhere within `.notification` element
would follow the *Close* link.
@method [up-follow]
@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, $link) {
if (!childClicked(event, $link)) {
event.preventDefault();
if (!$link.is('[up-instant]')) {
return follow(resolve($link));
}
}
});
up.on('mousedown', '[up-follow][up-instant]', function(event, $link) {
if (activeInstantLink(event, $link)) {
event.preventDefault();
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)
- [Triggering the link on `mousedown`](/up.link#up-instant) instead of on `click`
Use `up-dash` like this:
User list
Note that this is shorthand for:
User list
You can also apply `[up-dash]` to any element that contains a link
in order to enlarge the link's click area:
@method [up-dash]
@ujs
*/
up.awaken('[up-dash]', function($element) {
var newAttrs, target;
target = $element.attr('up-dash');
newAttrs = {
'up-preload': 'true',
'up-instant': 'true'
};
if (u.isBlank(target) || u.castsToTrue(target)) {
newAttrs['up-follow'] = '';
} else {
newAttrs['up-target'] = target;
}
u.setMissingAttrs($element, newAttrs);
return $element.removeAttr('up-dash');
});
return {
visit: visit,
follow: follow,
resolve: resolve,
childClicked: childClicked
};
})();
up.visit = up.link.visit;
up.follow = up.link.follow;
}).call(this);
/**
Forms and controls
==================
Up.js comes with functionality to submit forms without
leaving the current page. This means you can replace page fragments,
open dialogs with sub-forms, etc. all without losing form state.
\#\#\# Incomplete documentation!
We need to work on this page:
- Explain how to display form errors
- Explain that the server needs to send 2xx or 5xx status codes so
Up.js can decide whether the form submission was successful
- Explain that the server needs to send `X-Up-Location` and `X-Up-Method` headers
if an successful form submission resulted in a redirect
- Examples
@class up.form
*/
(function() {
up.form = (function() {
var observe, submit, u;
u = up.util;
/**
Submits a form using the Up.js flow:
up.submit('form.new_user')
Instead of loading a new page, the form is submitted via AJAX.
The response is parsed for a CSS selector and the matching elements will
replace corresponding elements on the current page.
@method up.submit
@param {Element|jQuery|String} formOrSelector
A reference or selector for the form to submit.
If the argument points to an element that is not a form,
Up.js will search its ancestors for the closest form.
@param {String} [options.url]
The URL where to submit the form.
Defaults to the form's `action` attribute, or to the current URL of the browser window.
@param {String} [options.method]
The HTTP method used for the form submission.
Defaults to the form's `up-method`, `data-method` or `method` attribute, or to `'post'`
if none of these attributes are given.
@param {String} [options.target]
The selector to update when the form submission succeeds (server responds with status 200).
Defaults to the form's `up-target` attribute, or to `'body'`.
@param {String} [options.failTarget]
The selector to update when the form submission fails (server responds with non-200 status).
Defaults to the form's `up-fail-target` attribute, or to an auto-generated
selector that matches the form itself.
@param {Boolean|String} [options.history=true]
Successful form submissions will add a history entry and change the browser's
location bar if the form either uses the `GET` method or the response redirected
to another page (this requires the `upjs-rails` gem).
If want to prevent history changes in any case, set this to `false`.
If you pass a `String`, it is used as the URL for the browser history.
@param {String} [options.transition='none']
The transition to use when a successful form submission updates the `options.target` selector.
Defaults to the form's `up-transition` attribute, or to `'none'`.
@param {String} [options.failTransition='none']
The transition to use when a failed form submission updates the `options.failTarget` selector.
Defaults to the form's `up-fail-transition` attribute, or to `options.transition`, or to `'none'`.
@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]
The timing function that controls the transition's acceleration. [`up.morph`](/up.motion#up.morph).
@param {Boolean} [options.cache]
Whether to accept a cached response.
@return {Promise}
A promise for the AJAX response
*/
submit = function(formOrSelector, options) {
var $form, animateOptions, failureSelector, failureTransition, historyOption, httpMethod, request, successSelector, successTransition, successUrl, url, useCache;
$form = $(formOrSelector).closest('form');
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);
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'));
url = u.option(options.url, $form.attr('action'), up.browser.url());
$form.addClass('up-active');
if (!up.browser.canPushState() && !u.castsToFalse(historyOption)) {
$form.get(0).submit();
return;
}
request = {
url: url,
type: httpMethod,
data: $form.serialize(),
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;
return u.option(url, false);
};
return u.ajax(request).always(function() {
return $form.removeClass('up-active');
}).done(function(html, textStatus, xhr) {
var successOptions;
successOptions = u.merge(animateOptions, {
history: successUrl(xhr),
transition: successTransition
});
return up.flow.implant(successSelector, html, successOptions);
}).fail(function(xhr, textStatus, errorThrown) {
var failureOptions, html;
html = xhr.responseText;
failureOptions = u.merge(animateOptions, {
transition: failureTransition
});
return up.flow.implant(failureSelector, html, failureOptions);
});
};
/**
Observes a form field and runs a callback when its value changes.
This is useful for observing text fields while the user is typing.
For instance, the following would submit the form whenever the
text field value changes:
up.observe('input', { change: function(value, $input) {
up.submit($input)
} });
\#\#\#\# Preventing concurrency
Firing asynchronous code after a form field can cause
[concurrency issues](https://makandracards.com/makandra/961-concurrency-issues-with-find-as-you-type-boxes).
To mitigate this, `up.observe` will try to never run a callback
before the previous callback has completed.
To take advantage of this, your callback code must return a promise.
Note that all asynchronous Up.js functions return promises.
\#\#\#\# Throttling
If you are concerned about fast typists causing too much
load on your server, you can use a `delay` option to wait before
executing the callback:
up.observe('input', {
delay: 100,
change: function(value, $input) { up.submit($input) }
});
@method up.observe
@param {Element|jQuery|String} fieldOrSelector
@param {Function(value, $field)|String} options.change
The callback to execute when the field's value changes.
If given as a function, it must take two arguments (`value`, `$field`).
If given as a string, it will be evaled as Javascript code in a context where
(`value`, `$field`) are set.
@param {Number} [options.delay=0]
The number of miliseconds to wait before executing the callback
after the input value changes. Use this to limit how often the callback
will be invoked for a fast typist.
*/
observe = function(fieldOrSelector, options) {
var $field, callback, callbackPromise, callbackTimer, changeEvents, check, clearTimer, codeOnChange, delay, knownValue, nextCallback, runNextCallback;
$field = $(fieldOrSelector);
options = u.options(options);
delay = u.option($field.attr('up-delay'), options.delay, 0);
delay = parseInt(delay);
knownValue = null;
callback = null;
callbackTimer = null;
if (codeOnChange = $field.attr('up-observe')) {
callback = function(value, $field) {
return eval(codeOnChange);
};
} else if (options.change) {
callback = options.change;
} else {
u.error('up.observe: No change callback given');
}
callbackPromise = u.resolvedPromise();
nextCallback = null;
runNextCallback = function() {
var returnValue;
if (nextCallback) {
returnValue = nextCallback();
nextCallback = null;
return returnValue;
}
};
check = function() {
var skipCallback, value;
value = $field.val();
skipCallback = u.isNull(knownValue);
if (knownValue !== value) {
knownValue = value;
if (!skipCallback) {
clearTimer();
nextCallback = function() {
return callback.apply($field.get(0), [value, $field]);
};
return callbackTimer = setTimeout(function() {
return callbackPromise.then(function() {
var returnValue;
returnValue = runNextCallback();
if (u.isPromise(returnValue)) {
return callbackPromise = returnValue;
} else {
return callbackPromise = u.resolvedPromise();
}
});
}, delay);
}
}
};
clearTimer = function() {
return clearTimeout(callbackTimer);
};
changeEvents = up.browser.canInputEvent() ? 'input change' : 'input change keypress paste cut click propertychange';
$field.on(changeEvents, check);
check();
return clearTimer;
};
/**
Submits the form through AJAX, searches the response for the selector
given in `up-target` and replaces the selector content in the current page:
@method form[up-target]
@ujs
@param {String} up-target
The selector to replace if the form submission is successful (200 status code).
@param {String} [up-fail-target]
@param {String} [up-transition]
@param {String} [up-fail-transition]
@param {String} [up-history]
@param {String} [up-method]
The HTTP method to be used to submit the form (`get`, `post`, `put`, `delete`, `patch`).
Alternately you can use an attribute `data-method`
([Rails UJS](https://github.com/rails/jquery-ujs/wiki/Unobtrusive-scripting-support-for-jQuery))
or `method` (vanilla HTML) for the same purpose.
*/
up.on('submit', 'form[up-target]', function(event, $form) {
event.preventDefault();
return submit($form);
});
/**
Observes this form field and runs the given script
when its value changes. This is useful for observing text fields
while the user is typing.
For instance, the following would submit the form whenever the
text field value changes:
The script given with `up-observe` runs with the following context:
| Name | Type | Description |
| -------- | --------- | ------------------------------------- |
| `value` | `String` | The current value of the field |
| `this` | `Element` | The form field |
| `$field` | `jQuery` | The form field as a jQuery collection |
See up.observe.
@method input[up-observe]
The code to run when the field's value changes.
@ujs
@param {String} up-observe
*/
up.awaken('[up-observe]', function($field) {
return observe($field);
});
return {
submit: submit,
observe: observe
};
})();
up.submit = up.form.submit;
up.observe = up.form.observe;
}).call(this);
/**
Pop-up overlays
===============
Instead of linking to another page fragment, you can also choose
to "roll up" any target CSS selector in a popup overlay.
Popup overlays close themselves if the user clicks somewhere outside the
popup area.
For modal dialogs see [up.modal](/up.modal) instead.
\#\#\# Incomplete documentation!
We need to work on this page:
- Show the HTML structure of the popup elements, and how to style them via CSS
- Explain how to position popup using `up-origin`
- Explain how dialogs auto-close themselves when a fragment changes behind the popup layer
- Document method parameters
@class up.popup
*/
(function() {
up.popup = (function() {
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'
};
/**
@method up.popup.defaults
@param {String} options.animation
@param {String} options.origin
*/
defaults = function(options) {
return u.extend(config, options);
};
position = function($link, $popup, origin) {
var css, linkBox;
linkBox = u.measure($link, {
full: true
});
css = (function() {
switch (origin) {
case "bottom-right":
return {
right: linkBox.right,
top: linkBox.top + linkBox.height
};
case "bottom-left":
return {
left: linkBox.left,
top: linkBox.bottom + linkBox.height
};
case "top-right":
return {
right: linkBox.right,
bottom: linkBox.top
};
case "top-left":
return {
left: linkBox.left,
bottom: linkBox.top
};
default:
return u.error("Unknown origin %o", origin);
}
})();
$popup.attr('up-origin', origin);
$popup.css(css);
return ensureInViewport($popup);
};
ensureInViewport = function($popup) {
var bottom, box, errorX, errorY, left, right, top;
box = u.measure($popup, {
full: true
});
errorX = null;
errorY = null;
if (box.right < 0) {
errorX = -box.right;
}
if (box.bottom < 0) {
errorY = -box.bottom;
}
if (box.left < 0) {
errorX = box.left;
}
if (box.top < 0) {
errorY = box.top;
}
if (errorX) {
if (left = parseInt($popup.css('left'))) {
$popup.css('left', left - errorX);
} else if (right = parseInt($popup.css('right'))) {
$popup.css('right', right + errorX);
}
}
if (errorY) {
if (top = parseInt($popup.css('top'))) {
return $popup.css('top', top - errorY);
} 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', '');
}
$placeholder = u.$createElementFromSelector(selector);
$placeholder.appendTo($popup);
$popup.appendTo(document.body);
rememberHistory();
$popup.hide();
return $popup;
};
updated = function($link, $popup, origin, animation, animateOptions) {
$popup.show();
position($link, $popup, origin);
return up.animate($popup, animation, animateOptions);
};
/**
Opens a popup overlay.
@method up.popup.open
@param {Element|jQuery|String} elementOrSelector
@param {String} [options.url]
@param {String} [options.origin='bottom-right']
@param {String} [options.animation]
The animation to use when opening the popup.
@param {Number} [options.duration]
The duration of the animation. See [`up.animate`](/up.motion#up.animate).
@param {Number} [options.delay]
The delay before the animation starts. See [`up.animate`](/up.motion#up.animate).
@param {String} [options.easing]
The timing function that controls the animation's acceleration. [`up.animate`](/up.motion#up.animate).
@param {Boolean} [options.sticky=false]
If set to `true`, the popup remains
open even if the page changes in the background.
@param {Object} [options.history=false]
*/
open = function(linkOrSelector, options) {
var $link, $popup, animateOptions, animation, history, origin, selector, sticky, url;
$link = $(linkOrSelector);
options = u.options(options);
url = u.option(options.url, $link.attr('href'));
selector = u.option(options.target, $link.attr('up-popup'), 'body');
origin = u.option(options.origin, $link.attr('up-origin'), config.origin);
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;
animateOptions = up.motion.animateOptions(options, $link);
close();
$popup = createHiddenPopup($link, selector, sticky);
return up.replace(selector, url, {
history: history,
insert: function() {
return updated($link, $popup, origin, animation, animateOptions);
}
});
};
/**
Returns the source URL for the fragment displayed
in the current popup overlay, or `undefined` if no
popup is open.
@method up.popup.source
@return {String}
the source URL
*/
source = function() {
var $popup;
$popup = $('.up-popup');
if (!$popup.is('.up-destroying')) {
return $popup.find('[up-source]').attr('up-source');
}
};
/**
Closes a currently opened popup overlay.
Does nothing if no popup is currently open.
@method up.popup.close
@param {Object} options
See options for [`up.animate`](/up.motion#up.animate).
*/
close = function(options) {
var $popup;
$popup = $('.up-popup');
if ($popup.length) {
options = u.options(options, {
animation: config.closeAnimation,
url: $popup.attr('up-previous-url'),
title: $popup.attr('up-previous-title')
});
return up.destroy($popup, options);
}
};
autoclose = function() {
if (!$('.up-popup').is('[up-sticky]')) {
return close();
}
};
/**
Opens the target of this link in a popup overlay:
Switch deck
If the `up-sticky` attribute is set, the dialog does not auto-close
if a page fragment below the popup overlay updates:
Switch deckSettings
@method a[up-popup]
@ujs
@param [up-sticky]
@param [up-origin]
*/
up.on('click', 'a[up-popup]', function(event, $link) {
event.preventDefault();
if ($link.is('.up-current')) {
return close();
} else {
return open($link);
}
});
up.on('click', 'body', function(event, $body) {
var $target;
$target = $(event.target);
if (!($target.closest('.up-popup').length || $target.closest('[up-popup]').length)) {
return close();
}
});
up.bus.on('fragment:ready', function($fragment) {
if (!$fragment.closest('.up-popup').length) {
discardHistory();
return autoclose();
}
});
up.magic.onEscape(function() {
return close();
});
/**
When an element with this attribute is clicked,
a currently open popup is closed.
@method [up-close]
@ujs
*/
up.on('click', '[up-close]', function(event, $element) {
if ($element.closest('.up-popup')) {
return close();
}
});
up.bus.on('framework:reset', close);
return {
open: open,
close: close,
source: source,
defaults: defaults
};
})();
}).call(this);
/**
Modal dialogs
=============
Instead of linking to another page fragment, you can also choose
to open any target CSS selector in a modal dialog.
For small popup overlays ("dropdowns") see [up.popup](/up.popup) instead.
@class up.modal
*/
(function() {
var __slice = [].slice;
up.modal = (function() {
var autoclose, close, config, createHiddenModal, defaults, discardHistory, open, rememberHistory, source, templateHtml, u, updated;
u = up.util;
config = {
width: 'auto',
height: 'auto',
openAnimation: 'fade-in',
closeAnimation: 'fade-out',
closeLabel: 'X',
template: function(config) {
return "
\n
\n
" + config.closeLabel + "
\n \n
\n
";
}
};
/**
Sets default options for future modals.
@method up.modal.defaults
@param {Number} [options.width='auto']
The width of the dialog in pixels.
Defaults to `'auto'`, meaning that the dialog will grow to fit its contents.
@param {Number} [options.height='auto']
The height of the dialog in pixels.
Defaults to `'auto'`, meaning that the dialog will grow to fit its contents.
@param {String|Function(config)} [options.template]
A string containing the HTML structure of the modal.
You can supply an alternative template string, but make sure that it
contains tags with the classes `up-modal`, `up-modal-dialog` and `up-modal-content`.
You can also supply a function that returns a HTML string.
The function will be called with the modal options (merged from these defaults
and any per-open overrides) whenever a modal opens.
@param {String} [options.closeLabel='X']
The label of the button that closes the dialog.
@param {String} [options.openAnimation='fade-in']
The animation used to open the modal. The animation will be applied
to both the dialog box and the overlay dimming the page.
@param {String} [options.closeAnimation='fade-out']
The animation used to close the modal. The animation will be applied
to both the dialog box and the overlay dimming the page.
*/
defaults = function(options) {
return u.extend(config, options);
};
templateHtml = function() {
var template;
template = config.template;
if (u.isFunction(template)) {
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', '');
}
$modal.attr('up-previous-url', up.browser.url());
$modal.attr('up-previous-title', document.title);
$dialog = $modal.find('.up-modal-dialog');
if (u.isPresent(width)) {
$dialog.css('width', width);
}
if (u.isPresent(height)) {
$dialog.css('height', height);
}
$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, animateOptions) {
$modal.show();
return up.animate($modal, animation, animateOptions);
};
/**
Opens the given link's destination in a modal overlay:
var $link = $('...');
up.modal.open($link);
Any option attributes for [`a[up-modal]`](#a.up-modal) will be honored.
You can also open a URL directly like this:
up.modal.open({ url: '/foo', target: '.list' })
This will request `/foo`, extract the `.list` selector from the response
and open the selected container in a modal dialog.
@method up.modal.open
@param {Element|jQuery|String} [elementOrSelector]
The link to follow.
Can be omitted if you give `options.url` instead.
@param {String} [options.url]
The URL to open.
Can be omitted if you give `elementOrSelector` instead.
@param {String} [options.target]
The selector to extract from the response and open in a modal dialog.
@param {Number} [options.width]
The width of the dialog in pixels.
By [default](#up.modal.defaults) the dialog will grow to fit its contents.
@param {Number} [options.height]
The width of the dialog in pixels.
By [default](#up.modal.defaults) the dialog will grow to fit its contents.
@param {Boolean} [options.sticky=false]
If set to `true`, the modal remains
open even if the page changes in the background.
@param {Object} [options.history=true]
Whether to add a browser history entry for the modal's source URL.
@param {String} [options.animation]
The animation to use when opening the modal.
@param {Number} [options.duration]
The duration of the animation. See [`up.animate`](/up.motion#up.animate).
@param {Number} [options.delay]
The delay before the animation starts. See [`up.animate`](/up.motion#up.animate).
@param {String} [options.easing]
The timing function that controls the animation's acceleration. [`up.animate`](/up.motion#up.animate).
@return {Promise}
A promise that will be resolved when the modal has finished loading.
*/
open = function() {
var $link, $modal, animateOptions, animation, args, height, history, options, selector, sticky, url, width;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
if (u.isObject(args[0]) && !u.isElement(args[0]) && !u.isJQuery(args[0])) {
$link = u.nullJquery();
options = args[0];
} else {
$link = $(args[0]);
options = args[1];
}
options = u.options(options);
url = u.option(options.url, $link.attr('href'), $link.attr('up-href'));
selector = u.option(options.target, $link.attr('up-modal'), 'body');
width = u.option(options.width, $link.attr('up-width'), config.width);
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;
animateOptions = up.motion.animateOptions(options, $link);
close();
$modal = createHiddenModal(selector, width, height, sticky);
return up.replace(selector, url, {
history: history,
insert: function() {
return updated($modal, animation, animateOptions);
}
});
};
/**
Returns the source URL for the fragment displayed in the current modal overlay,
or `undefined` if no modal is currently open.
@method up.modal.source
@return {String}
the source URL
*/
source = function() {
var $modal;
$modal = $('.up-modal');
if (!$modal.is('.up-destroying')) {
return $modal.find('[up-source]').attr('up-source');
}
};
/**
Closes a currently opened modal overlay.
Does nothing if no modal is currently open.
@method up.modal.close
@param {Object} options
See options for [`up.animate`](/up.motion#up.animate)
*/
close = function(options) {
var $modal;
$modal = $('.up-modal');
if ($modal.length) {
options = u.options(options, {
animation: config.closeAnimation,
url: $modal.attr('up-previous-url'),
title: $modal.attr('up-previous-title')
});
return up.destroy($modal, options);
}
};
autoclose = function() {
if (!$('.up-modal').is('[up-sticky]')) {
discardHistory();
return close();
}
};
/**
Clicking this link will load the destination via AJAX and open
the given selector in a modal dialog.
Example:
Switch blog
Clicking would request the path `/blog` and select `.blog-list` from
the HTML response. Up.js will dim the page with an overlay
and place the matching `.blog-list` tag will be placed in
a modal dialog.
\#\#\#\# Customizing the dialog design
Loading the Up.js stylesheet will give you a minimal dialog design:
- Dialog contents are displayed in a white box that is centered vertically and horizontally.
- There is a a subtle box shadow around the dialog
- The box will grow to fit the dialog contents, but never grow larger than the screen
- The box is placed over a semi-transparent background to dim the rest of the page
- There is a button to close the dialog in the top-right corner
The easiest way to change how the dialog looks is by overriding the [default CSS styles](https://github.com/makandra/upjs/blob/master/lib/assets/stylesheets/up/modal.css.sass).
By default the dialog uses the following DOM structure (continuing the blog-switcher example from above):
X
...
If you want to change the design beyond CSS, you can
configure Up.js to [use a different HTML structure](#up.modal.defaults).
\#\#\#\# Closing behavior
By default the dialog automatically closes
*whenever a page fragment below the dialog is updated*.
This is useful to have the dialog interact with the page that
opened it, e.g. by updating parts of a larger form or by signing in a user
and revealing additional information.
To disable this behavior, give the opening link an `up-sticky` attribute:
Settings
@method a[up-modal]
@ujs
@param [up-sticky]
@param [up-animation]
@param [up-height]
@param [up-width]
@param [up-history]
*/
up.on('click', 'a[up-modal]', function(event, $link) {
event.preventDefault();
if ($link.is('.up-current')) {
return close();
} else {
return open($link);
}
});
up.on('click', 'body', function(event, $body) {
var $target;
$target = $(event.target);
if (!($target.closest('.up-modal-dialog').length || $target.closest('[up-modal]').length)) {
return close();
}
});
up.bus.on('fragment:ready', function($fragment) {
if (!$fragment.closest('.up-modal').length) {
return autoclose();
}
});
up.magic.onEscape(function() {
return close();
});
/**
When this element is clicked, closes a currently open dialog.
Does nothing if no modal is currently open.
@method [up-close]
@ujs
*/
up.on('click', '[up-close]', function(event, $element) {
if ($element.closest('.up-modal')) {
return close();
}
});
up.bus.on('framework:reset', close);
return {
open: open,
close: close,
source: source,
defaults: defaults
};
})();
}).call(this);
/**
Tooltips
========
Elements that have an `up-tooltip` attribute will show the attribute
value in a tooltip when a user hovers over the element.
\#\#\# Incomplete documentation!
We need to work on this page:
- Show the tooltip's HTML structure and how to style the elements
- Explain how to position tooltips using `up-origin`
- We should have a position about tooltips that contain HTML.
@class up.tooltip
*/
(function() {
up.tooltip = (function() {
var close, createElement, open, position, u;
u = up.util;
position = function($link, $tooltip, origin) {
var css, linkBox, tooltipBox;
linkBox = u.measure($link);
tooltipBox = u.measure($tooltip);
css = (function() {
switch (origin) {
case "top":
return {
left: linkBox.left + 0.5 * (linkBox.width - tooltipBox.width),
top: linkBox.top - tooltipBox.height
};
case "bottom":
return {
left: linkBox.left + 0.5 * (linkBox.width - tooltipBox.width),
top: linkBox.top + linkBox.height
};
default:
return u.error("Unknown origin %o", origin);
}
})();
$tooltip.attr('up-origin', origin);
return $tooltip.css(css);
};
createElement = function(html) {
return u.$createElementFromSelector('.up-tooltip').html(html).appendTo(document.body);
};
/**
Opens a tooltip.
@method up.tooltip.open
@param {Element|jQuery|String} elementOrSelector
@param {String} html
@param {String} [options.origin='top']
@param {String} [options.animation]
*/
open = function(linkOrSelector, options) {
var $link, $tooltip, animation, html, origin;
if (options == null) {
options = {};
}
$link = $(linkOrSelector);
html = u.option(options.html, $link.attr('up-tooltip'), $link.attr('title'));
origin = u.option(options.origin, $link.attr('up-origin'), 'top');
animation = u.option(options.animation, $link.attr('up-animation'), 'fade-in');
close();
$tooltip = createElement(html);
position($link, $tooltip, origin);
return up.animate($tooltip, animation, options);
};
/**
Closes a currently shown tooltip.
Does nothing if no tooltip is currently shown.
@method up.tooltip.close
@param {Object} options
See options for See options for [`up.animate`](/up.motion#up.animate).
*/
close = function(options) {
var $tooltip;
$tooltip = $('.up-tooltip');
if ($tooltip.length) {
options = u.options(options, {
animation: 'fade-out'
});
return up.destroy($tooltip, options);
}
};
/**
Displays a tooltip when hovering the mouse over this element:
Decks
You can also make an existing `title` attribute appear as a tooltip:
Decks
@method [up-tooltip]
@ujs
*/
up.awaken('[up-tooltip]', function($link) {
$link.on('mouseover', function() {
return open($link);
});
return $link.on('mouseout', function() {
return close();
});
});
up.on('click', 'body', function(event, $body) {
return close();
});
up.bus.on('framework:reset', close);
up.magic.onEscape(function() {
return close();
});
return {
open: open,
close: close
};
})();
}).call(this);
/**
Fast interaction feedback
=========================
This module marks up link elements with classes indicating that
they are currently loading (class `up-active`) or linking
to the current location (class `up-current`).
This dramatically improves the perceived speed of your user interface
by providing instant feedback for user interactions.
The classes are added and removed automatically whenever
a page fragment is added, changed or destroyed through Up.js.
How Up.js computes the current location
---------------------------------------
From Up's point of view the "current" location is either:
- the URL displayed in the browser window's location bar
- the source URL of a currently opened [modal dialog](/up.modal)
- the source URL of a currently opened [popup overlay](/up.popup)
@class up.navigation
*/
(function() {
up.navigation = (function() {
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';
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;
urls = [];
if ($link = up.link.resolve($section)) {
_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);
}
}
}
return urls;
};
locationChanged = function() {
var currentUrls;
currentUrls = u.stringSet([normalizeUrl(up.browser.url()), normalizeUrl(up.modal.source()), normalizeUrl(up.popup.source())]);
return u.each($(SELECTOR_SECTION), function(section) {
var $section, urls;
$section = $(section);
urls = sectionUrls($section);
if (currentUrls.includesAny(urls)) {
return $section.addClass(CLASS_CURRENT);
} else {
return $section.removeClass(CLASS_CURRENT);
}
});
};
sectionClicked = function($section) {
unmarkActive();
$section = enlargeClickArea($section);
return $section.addClass(CLASS_ACTIVE);
};
enlargeClickArea = function($section) {
return u.presence($section.parents(SELECTOR_SECTION)) || $section;
};
unmarkActive = function() {
return $(SELECTOR_ACTIVE).removeClass(CLASS_ACTIVE);
};
up.on('click', SELECTOR_SECTION, function(event, $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();
});
return up.bus.on('fragment:destroy', function($fragment) {
if ($fragment.is('.up-modal, .up-popup')) {
return locationChanged();
}
});
})();
}).call(this);
/**
Content slots
=============
It can be useful to mark "slots" in your page layout where you expect
content to appear in the future.
For example, you might have
Seeing that the `.alerts` container is empty, Up.js will hide it:
As soon as you
Meeting at 11:30 AM
TODO: Write some documentation
@class up.slot
*/
(function() {
up.slot = (function() {
var check, hasContent, u;
u = up.util;
hasContent = function($slot) {
return u.trim($slot.html()) !== '';
};
check = function($element) {
return u.findWithSelf($element, '[up-slot]').each(function() {
var $slot;
$slot = $(this);
if (!hasContent($slot)) {
return $slot.hide();
}
});
};
/**
Use this attribute to mark up empty element containers that
you plan to update with content in the future.
An element with this attribute is automatically hidden
if it has no content, and is re-shown if it is updated with
content.
This is useful to prevent the element from applying unwanted
margins to the surrounding page flow.
@method [up-slot]
@ujs
*/
return up.bus.on('fragment:ready', check);
})();
}).call(this);
(function() {
up.browser.ensureRecentJquery();
if (up.browser.isSupported()) {
up.browser.ensureConsoleExists();
up.bus.emit('framework:ready');
$(document).on('ready', function() {
return up.bus.emit('app:ready');
});
}
}).call(this);