Initial public commit
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
//=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(target.pathname === window.location.pathname) {
|
||||
var element;
|
||||
if(target.hash) {
|
||||
if(mode === 'normal' && 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();
|
||||
});
|
||||
|
||||
// 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 || {});
|
||||
@@ -0,0 +1,11 @@
|
||||
.ajax-transition {
|
||||
cursor: progress;
|
||||
}
|
||||
|
||||
.main {
|
||||
transition: opacity 0.5s ease;
|
||||
|
||||
.ajax-transition & {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user