//
// When you click the link "Foo", it will:
// - Find an ancestor element matching the selector ".container".
// - Perform a GET request to /foo.
// - If the request is successful, replace the ancestor with the response body.
//
// It also works for forms:
//
//
//
//
//
// Notes:
// - It is possible to replace the same element that was clicked/submitted. A
// convenient selector for doing so is "*".
// - If a link has the attribute "data-pushstate" and the browser supports it,
// the address bar will be updated on success using history.pushState.
// - The container will have the class ".replace-active" while a replace
// operation is in progress. This can be useful for styling purposes.
// - Clicks inside a container with a replace operation in progress are ignored.
// This obviates the need to prevent double submissions of forms.
(function($, undefined) {
// Apply callback to every descendent matching selector that exists at page
// load or that is added later. This frees you as the developer from having to
// find every place in the codebase where a given widget might be added to the
// page and attaching its initialization logic there.
//
// Example:
// $(document).initializeEach('[data-background-url]', function() {
// $(this).waypoint(function() {
// $(this).css('backgroundUrl', $(this).data('backgroundUrl'));
// }, { offset: '100%', triggerOnce: true });
// });
//
// It is also possible to nest these calls.
//
// $(document).initializeEach('.discussion-board', function() {
// $(this).initializeEach('.discussion-post', function() {
// // ...
// });
// });
//
$.fn.initializeEach = function(selector, callback) {
this
.on('replace:done', function(event) {
$(event.target).find(selector).addBack(selector).each(callback);
})
.find(selector).each(callback);
};
// Replace an element with the result of an AJAX request.
function ajaxReplace($elem, url, options) {
if ($elem.closest('.replace-active').length) {
return;
}
options = options || {};
var $container = $elem.closest(options.selector || '*');
$container.addClass('replace-active').trigger('replace:start');
$.ajax(url, { type: options.method, data: options.data })
.always(function() {
$container.removeClass('replace-active');
})
.done(function(data) {
if (options.success) {
options.success($container, data);
}
$(data).replaceAll($container).trigger('replace:done');
})
.fail(function() {
$container.trigger('replace:fail');
});
}
$(window).on('popstate', function(event) {
var state = event.originalEvent.state;
if (state && state.selector) {
$(state.data).replaceAll(state.selector)
.trigger('replace:done');
}
});
$(document).on('click', 'a[data-replace]', function(event) {
var $link = $(this);
if ($link.data('pushstate')) {
if (!window.history || !history.pushState) {
// If a link would like to use pushState but that is not supported by
// the browser, we'll let it do a full page load. Affects IE 8/9 users.
return;
}
if (event.shiftKey || event.metaKey) {
// User is trying to open link in a new tab or window. Allow default.
return;
}
}
event.preventDefault();
ajaxReplace($link, $link.attr('href'), {
selector: $link.data('replace'),
success: function($container, data) {
if ($link.data('pushstate')) {
history.replaceState({
selector: $link.data('replace'),
data: $container[0].outerHTML
}, null);
history.pushState({
selector: $link.data('replace'),
data: data
}, null, $link.attr('href'));
}
}
});
});
$(document).on('submit', 'form[data-replace]', function(event) {
event.preventDefault();
var $form = $(this);
ajaxReplace($form, $form.attr('action'), {
selector: $form.data('replace'),
method: $form.attr('method'),
data: $form.serialize()
});
});
// When you submit a form using the built-in click handler for buttons, the
// clicked button's name/value will be added to the query string. This can be
// useful for knowing which button was clicked in a form with several of
// them. When we submit the form programatically, the clicked button is no
// longer active and we have no way of knowing which one it was. This click
// handler emulates the built-in functionality.
$(document).on('click', 'form[data-replace] button', function(event) {
var $button = $(this);
$('')
.prop('name', $button.prop('name'))
.val($button.val())
.appendTo($button.closest('form'));
});
if (window.releaseClicks) {
$.each(releaseClicks(), function(_, event) {
$(event.target).trigger(event.type);
});
}
// Allows sections of the page to be lazily loaded just before they come into
// view. Implementation is simple: just add a 'lazy-url' data attribute like so
//
//
//
// The rest will be handled by this code, in conjunction with the jQuery
// waypoints plug-in (http://imakewebthings.com/jquery-waypoints/); if the
// plug-in is not available, we degrade gracefully and revert to eager
// loading.
//
// To have the lazy url trigger based on horizontal offset, set the
// 'lazy-url-horizontal' data attribute to 'true'.
//
// To set a custom context set the 'lazy-url-context' data attribute to a CSS
// selector. A custom context is an element which acts as the relative point
// from which the offset is considered. This defaults to the window.
//
// Note that we support adding yet-more lazy content onto the page after page
// initialization, but only when using Replace.js.
$(document).initializeEach('[data-lazy-url]', function() {
var $this = $(this);
if (typeof $.fn.waypoint === 'function') {
var waypointOptions = {
offset: '120%',
triggerOnce: true,
handler: function() {
ajaxReplace($this, $this.data('lazy-url'));
}
};
if ($this.data('lazyUrlHorizontal')) {
waypointOptions.horizontal = true;
waypointOptions.direction = 'right';
}
var dataContext = $this.data('lazyUrlContext');
if (dataContext) {
waypointOptions.context = dataContext;
}
$this.waypoint(waypointOptions);
} else {
ajaxReplace($this, $this.data('lazy-url'));
}
});
})(jQuery);