1
0
Fork 0
You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

173 lines
5.4 KiB
JavaScript

//=require element-closest/closest.js
//=require whatwg-fetch/fetch.js
//=require flexismooth/flexismooth.js
(function(app) {
'use strict';
// Cache for successful requests
var cache = {};
var hasOwnProperty = cache.hasOwnProperty;
/**
* Navigates to another page using AJAX
*
* @param {Element/string} target Anchor element or URL string to navigate to
* @param {string} mode Either 'normal' or 'popstate', defaults to 'normal'
* @return {boolean} Whether the event will be handled by this function
* May be used to prevent default browser behavior
*/
app.go = function(target, mode) {
if(mode === undefined) mode = 'normal';
// Refuse to do anything if the History API is not supported
if(!history.pushState) return false;
// Validate params
if(typeof target !== 'string' && target.nodeType !== 1) {
throw new TypeError('Invalid parameter target.');
}
if(mode !== 'normal' && mode !== 'popstate') {
throw new TypeError('Invalid parameter mode.');
}
// Convert to anchor element if param is a string
if(typeof target === 'string') {
var link = document.createElement('a');
link.href = target;
target = link;
}
// Only do something if:
// - The user clicked on a link
// - The link is internal
// - The link doesn't contain a file extension
var targetOrigin, windowOrigin;
if(target.origin) {
targetOrigin = target.origin;
windowOrigin = window.location.origin;
} else {
targetOrigin = target.protocol + '//' + target.hostname;
windowOrigin = window.location.protocol + '//' + window.location.hostname;
}
if(target.nodeName !== 'A' ||
targetOrigin !== windowOrigin ||
target.pathname.indexOf('.') !== -1) return false;
// Just scroll to the top/element if the link points to the current page
// In this case, we also don't want to reload, so we return true
if(mode === 'normal' && target.pathname === window.location.pathname) {
var element;
if(target.hash) {
if(target.hash !== '#top') {
history.pushState({}, document.title, target.href);
}
element = document.getElementById(target.hash.substr(1));
}
Flexismooth(element || 0, 500);
return true;
}
// Success, we will navigate using AJAX
// Hide the content using CSS and trigger event
document.body.classList.add('ajax-transition');
app.emit('ajax.before', target);
// Scroll to the top of the page and ignore interruption
var scrollPromise = Flexismooth(0, 500).catch(function() {});
// Check if the resource is in cache
var dataPromise;
if(hasOwnProperty.call(cache, target.href)) {
console.info('Loading new page ' + target.href + ' (cache)');
dataPromise = Promise.resolve(cache[target.href]);
} else {
console.info('Loading new page ' + target.href + ' (ajax)');
var options = {
credentials: 'same-origin',
headers: {'Accept': 'application/json'}
};
dataPromise = fetch(target.href, options)
.then(app.validateHttpStatus)
.then(app.parseJsonResponse)
.then(function(data) {
// Cache data if the page explicitly agrees
if(data.cachable === true) {
cache[data.url] = data;
}
return data;
});
}
// Prefetch external resources
dataPromise = dataPromise.then(function(data) {
var container = document.createElement('div');
container.innerHTML = data.main;
return data;
});
// Wait until both data fetching and scrolling are done
// Wait at least 600 ms for the page transition fading even if scrolling is interrupted
Promise.all([dataPromise, scrollPromise, app.timerPromise(600)])
.then(function(values) {
// Get the result of the data promise
var data = values[0];
// Set browser metadata
if(mode === 'normal') history.pushState({}, data.title, data.url + target.hash);
document.title = data.title;
// Update content blocks
document.querySelector('.main').innerHTML = data.main;
// Trigger event
app.emit('ajax.after', target, data);
// Display the content again
document.body.classList.remove('ajax-transition');
// If a hash was given, scroll to that element
if(target.hash) {
var element = document.getElementById(target.hash.substr(1));
if(element) Flexismooth(element, 500);
}
})
.catch(function(err) {
console.error(err);
// Fall back to normal browser behavior
window.location.href = target.href;
});
return true;
};
// Listen on click events on body (events from links will bubble up)
document.body.addEventListener('click', function(e) {
// Only continue if no modifier was used (open in new tab etc.)
if(e.ctrlKey || e.shiftKey || e.metaKey || (e.button && e.button === 1)) return;
// Find closest link parent
var link = e.target.closest('a');
// Only continue if the link was found
if(!link) return;
// Prevent the browser from navigating if app.go() wants to be responsible for the event
if(app.go(link)) e.preventDefault();
});
// Replace the state for the normal page load
history.replaceState({}, document.title, window.location.href);
// Listen on the popstate event on window
window.addEventListener('popstate', function(e) {
if(e.state !== null) app.go(window.location.href, 'popstate');
});
})(window.app = window.app || {});