Complete refactor of js/default.js

Signed-off-by: Collin J. Doering <collin.doering@rekahsoft.ca>
This commit is contained in:
Collin J. Doering 2016-01-10 22:19:57 -05:00
parent bb2dcbddf6
commit f026829d1c
1 changed files with 357 additions and 196 deletions

View File

@ -30,250 +30,411 @@
// Global array for processing piwik analytics commands // Global array for processing piwik analytics commands
var _paq = _paq || []; var _paq = _paq || [];
(function ($, mj, _paq) { (function ($, mj) {
"use strict"; "use strict";
var router = (function () { var router = (function () {
var routes = [ var routes = [],
{ // Post pages handler routeDefault = { acceptUrls: /.*/ },
acceptUrls: /posts\/.*\.html/, initCallbacks = [],
ajaxCallbacks: { onChangeCallbacks = [],
beforeSend: function () { beforeSends = [],
$('#nav-menu li.active').removeClass('active'); onSuccesss = [],
$('#nav-menu li a[href="/blog1.html"]').parent('li').addClass('active'); onErrors = [],
}
}
},
{ // Tag pages handler
acceptUrls: /tags\/.*(\d*)\.html/,
ajaxCallbacks: {
beforeSend: function () {
$('#nav-menu li.active').removeClass('active');
$('#nav-menu li a[href="/blog1.html"]').parent('li').addClass('active');
}
}
},
{ // Blog pages handler
acceptUrls: /blog\d*\.html/,
ajaxCallbacks: {
beforeSend: function () {
// Set the blog menuitem as active
$('a.menuitem[href="/blog1.html"]').closest('ul').find('li.active').removeClass('active');
$('a.menuitem[href="/blog1.html"]').closest('li').addClass('active');
}
}
},
{ // Default page handler
acceptUrls: /.*/,
ajaxCallbacks: {
beforeSend: function (url) {
if (url === "/") {
url = "/index.html";
}
// Initially set the active menuitem in the nav
$('a.menuitem[href="' + url + '"]').closest('ul').find('li.active').removeClass('active');
$('a.menuitem[href="' + url + '"]').closest('li').addClass('active');
}
}
}],
callback = function () { return null; },
spec = { spec = {
runRouter: function runRouter(url) { runRouter: function runRouter(url) {
var i; var i;
for (i = 0; i < routes.length; i += 1) { for (i = 0; i < routes.length; i += 1) {
if (routes[i].acceptUrls.test(url)) { if (routes[i].acceptUrls.test(url)) {
if (url === "/index.html") { load(url, routes[i]);
history.pushState(null, "Home", "/");
} else {
// TODO: strip url into title
history.pushState(null, "Title", url);
}
callback(url, routes[i].ajaxCallbacks);
break; break;
} }
} }
},
setCallback: function setCallback(cb) { // No route ran; run default route
if (typeof cb === 'function') { if (i === routes.length) {
callback = cb; load(url, routeDefault);
} }
},
onInit: function onInit(cb) {
if (typeof cb === "function") {
initCallbacks.push(cb);
}
},
onChange: function onChange(cb) {
if (typeof cb === 'function') {
onChangeCallbacks.push(cb);
}
},
beforeSend: function beforeSend(cb) {
if (typeof cb === "function") {
beforeSends.push(cb);
}
},
onSuccess: function onSuccess(cb) {
if (typeof cb === "function") {
onSuccesss.push(cb);
}
},
onError: function onError(cb) {
if (typeof cb === "function") {
onErrors.push(cb);
}
},
route: function route(r) {
if (typeof r === "object" && r.acceptUrls instanceof RegExp) {
routes.push(r);
} else {
throw "Error invalid route";
}
},
defaultRoute: function defaultRoute(r) {
if (typeof r === "object") {
r.acceptUrls = /.*/;
routeDefault = r;
}
},
jsUrls: function (sel) {
$(sel).each(function (i) {
var href = $(this).attr('href'),
external_url_regexp = /https?:\/\/.*/,
mailto_regexp = /mailto:.*/,
files_regexp = /files\/.*/,
images_regexp = /images\/.*/;
if (!(external_url_regexp.test(href) || mailto_regexp.test(href) || files_regexp.test(href) || images_regexp.test(href))) {
$(this).click(function (e) {
e.preventDefault();
spec.runRouter(href);
});
}
});
} }
}; };
function setUrl(url) {
if (url === "/index.html") {
history.pushState(null, "Home", "/");
} else {
// TODO: strip url into title
history.pushState(null, "Title", url);
}
runCallbacks(url);
}
function load(url, r) {
// Set url in navigation bar
setUrl(url);
// Load route using ajax
$.ajax({
url: url,
type: 'GET',
dataType: 'html',
beforeSend: function (xhr, settings) {
// Run router beforeSend handlers
beforeSends.forEach(function (cb) {
cb(url);
});
// Run current handlers onSuccess callback (if it exists)
if (r.hasOwnProperty('beforeSend') && typeof r.beforeSend === 'function') {
r.beforeSend(url);
}
},
success: function (dta) {
// Parse script tags out of html
dta = $($.parseHTML(dta)).filter('#page-content').html();
// Add anchor click handlers for internal links in new content
spec.jsUrls('#page-content a');
// Run router onSuccess handlers
onSuccesss.forEach(function (cb) {
cb(url, dta);
});
// Run current handles onSuccess callback (if it exists)
if (r.hasOwnProperty('onSuccess') && typeof r.onSuccess === 'function') {
r.onSuccess(url, dta);
}
},
error: function (xhr, status) {
// Run router onError handlers
onErrors.forEach(function (cb) {
cb(url, status);
});
// Run current handles onError callback (if it exists)
if (r.hasOwnProperty('onError') && typeof r.onError === 'function') {
r.onError(url, status);
}
}
});
}
function runCallbacks(url) {
// Run each of the onChangeCallbacks
onChangeCallbacks.forEach(function (cb) {
cb(url);
});
}
// Listen for load event and run initCallbacks
window.addEventListener("load", function () {
initCallbacks.forEach(function (cb) {
cb(location.pathname);
});
});
// Listen for popstate event and call router with the new url
window.addEventListener("popstate", function (e) {
router.runRouter(location.pathname);
});
return spec; return spec;
}()), }()),
page = (function () {
// var pageId = '#page-content', navId = '#nav';
analytics = (function () { analytics = (function () {
var spec = { init: init, var inited = false,
trackPageView: trackPageView spec = {
}; trackPageView: trackPageView,
debugEnable: function () {
function init () { init();
_paq.push(["setDoNotTrack", true]); }
_paq.push(["enableLinkTracking"]); };
_paq.push(["setTrackerUrl", "//analytics.rekahsoft.ca/piwik.php"]);
_paq.push(["setSiteId", 1]);
}
function trackPageView (href) { function trackPageView (href) {
_paq.push(["setDocumentTitle", document.domain + href]); if (inited) {
_paq.push(["trackPageView"]); _paq.push(["setDocumentTitle", document.domain + href]);
_paq.push(["trackPageView"]);
}
} }
function init() {
if (!inited) {
_paq.push(["setDoNotTrack", true]);
_paq.push(["enableLinkTracking"]);
_paq.push(["setTrackerUrl", "//analytics.rekahsoft.ca/piwik.php"]);
_paq.push(["setSiteId", 1]);
inited = true;
}
}
// Initialize piwik.js when site is initially loaded
router.onInit(function () {
if (document.domain != "localhost") {
init();
trackPageView('/');
}
});
// Track page views with piwik each time the url changes
router.onChange(function (url, dta) {
trackPageView(url);
});
return spec return spec
}()), }()),
page = (function () { site = (function () {
function loadPageContent(page_href, handlerCallback) { var spec = {
// Track page view with piwik init: init
analytics.trackPageView(page_href); },
$.ajax({ status = (function () {
url: page_href, var messages = [],
type: 'GET', validIndicators = ["error", "success", "info"],
dataType: 'html', spec = {
beforeSend: function (xhr, settings) { setMessage: function setMessage(indicator, msg) {
// Add .loading to #page-content and #nav to facilitate a loading animation var message = {}, hasValidIndicator = false;
$('#page-content, #nav').addClass('loading'); for (var i = 0; i < validIndicators.length; i++) {
if (indicator === validIndicators[i]) {
// Run current handlers onSuccess callback (if it exists) hasValidIndicator = true;
if (handlerCallback.hasOwnProperty('beforeSend') && typeof handlerCallback.beforeSend === 'function') { break;
handlerCallback.beforeSend(page_href); }
}
console.log('beforeSend a.menuitem');
},
success: function (dta) {
// Remove any status message errors or successes
$('#status').slideUp('normal', function () {
$('#status').removeClass('error').removeClass('success').children('p.message').remove();
});
// Stop animations in the nav and page-content and scroll to the top of the page in a set amount of time
setTimeout(function () {
// Replace old page-content with new page-content
dta = $($.parseHTML(dta)).filter('#page-content').html();
$('#page-content').html(dta);
// Stop page loading
$('#page-content, #nav').removeClass('loading');
// Reload any new maths using MathJax
$('#page-content .math').each(function (math_elem) {
mj.Hub.Queue(["Typeset", mj.Hub, math_elem[0]]);
});
// Add anchor click handlers for internal links in new content loaded into #page-content
jsUrls('#page-content a');
// Add fullscreen functionality to inline-images and figures
$('article.post p > img').click(function () {
$(this).get(0).toggleFullScreen();
});
$('figure').click(function () {
$(this).children('img').get(0).toggleFullScreen();
});
// Run current handles onSuccess callback (if it exists)
if (handlerCallback.hasOwnProperty('onSuccess') && typeof handlerCallback.onSuccess === 'function') {
handlerCallback.onSuccess();
} }
// Scroll to top of the page if (!hasValidIndicator) {
if ($('body').scrollTop() > $('#nav').offset().top - 15) { throw "The indicator given '" + indicator + "' is not know";
$('html, body').animate({ } else {
scrollTop: $('#nav').offset().top - 15 message.indicator = indicator;
}, 'fast'); message.msg = msg;
messages.push(message);
// TODO: Set the current message and appropriate class on #status given the indicator
// parameter, which can be one of "error", "success", or "info".
} }
}, 250); },
}, hasMessage: function hasMessage() {
error: function (xhr, status) { if (messages.length === 0) {
/* Remove .loading from #page-content and #nav to stop the loading return false;
* animation. Finally, display an error message in #status. } else {
*/ return true;
$('#page-content, #nav').removeClass('loading'); }
},
// TODO: instead of immediately displaying error, check if the content is stored in local storage validIndicators: validIndicators,
$('#status').prepend('<p class="message">Error retrieving page ' + page_href + '</p>'); clear: function clear() {
$('#status').addClass('error').slideDown(); // clear the status messages
messages = [];
// Run current handles onError callback (if it exists) $('#status').html('');
if (handlerCallback.hasOwnProperty('onError') && typeof handlerCallback.onError === 'function') { },
handlerCallback.onError(); hide: function hide(indication) {
$('#status').slideUp('normal', function () {
$('#status').removeClass('error').removeClass('success').children('p.message').remove();
});
} }
} };
router.onSuccess(function (url, dta) {
status.hide();
}); });
}
function jsUrls(sel) {
$(sel).each(function (i) {
var href = $(this).attr('href'),
external_url_regexp = /https?:\/\/.*/,
mailto_regexp = /mailto:.*/,
files_regexp = /files\/.*/,
images_regexp = /images\/.*/;
if (!(external_url_regexp.test(href) || mailto_regexp.test(href) || files_regexp.test(href) || images_regexp.test(href))) {
$(this).click(function (e) {
e.preventDefault();
router.runRouter(href);
});
}
});
}
function appCacheUpdateReady () {
window.applicationCache.swapCache();
// TODO: find what resource is loaded currently and reload it if it has changed
}
function init(router) {
router.setCallback(loadPageContent);
// Setup piwik analytics
analytics.init();
window.addEventListener("popstate", function (e) {
router.runRouter(location.pathname);
});
window.addEventListener("updateready", appCacheUpdateReady);
if (window.applicationCache.status === window.applicationCache.UPDATEREADY) {
appCacheUpdateReady();
}
$(document).ready(function () { $(document).ready(function () {
$('#nav-menu a.menuitem').click(function () {
$(this).closest('ul').find('li.active').removeClass('active');
$(this).closest('li').addClass('active');
//$('.navbar-collapse').collapse('hide');
});
$('#status a.close-button').click(function () { $('#status a.close-button').click(function () {
$(this).parent().slideUp(function () { $(this).parent().slideUp(function () {
$(this).removeClass('error').removeClass('success'); $(this).removeClass('error').removeClass('success');
$(this).children('p.message').remove(); $(this).children('p.message').remove();
}); });
}); });
});
// Add anchor click handlers for internal links return spec;
jsUrls('#page-content a, #nav-menu a.menuitem'); }()),
nav = (function () {
var spec = {
setActive: function setActive(url) {
if (url === "/") {
url = "/index.html";
}
// Initially set the active menuitem in the nav
$('a.menuitem[href="' + url + '"]').closest('ul').find('li.active').removeClass('active');
$('a.menuitem[href="' + url + '"]').closest('li').addClass('active');
},
toggleLoading: function toggleLoading() {
$('#nav').toggleClass('loading');
},
loadingOff: function loadingOff() {
$('#nav').removeClass('loading');
},
loadingOn: function loadingOn() {
$('#nav').addClass('loading');
}
};
router.onChange(function (url, dta) {
nav.setActive(url);
});
router.beforeSend(function (url) {
nav.toggleLoading();
});
$(document).ready(function () {
$('#nav-menu a.menuitem').click(function () {
$(this).closest('ul').find('li.active').removeClass('active');
$(this).closest('li').addClass('active');
});
});
return spec;
}());
function appCacheUpdateReady () {
window.applicationCache.swapCache();
// TODO: find what resource is loaded currently and reload it if it has changed
}
function init() {
window.addEventListener("updateready", appCacheUpdateReady);
if (window.applicationCache.status === window.applicationCache.UPDATEREADY) {
appCacheUpdateReady();
}
// TODO: deal with jsUrls function which is moved to the router
$(document).ready(function () {
// Add anchor click handlers for internal links
router.jsUrls('#page-content a, #nav-menu a.menuitem');
}); });
} }
var spec = {
init: init
};
return spec; return spec;
}()); }());
page.init(router); // Initialize routes
}(jQuery, MathJax)); router.route({ // Post pages handler
acceptUrls: /posts\/.*\.html/
});
router.route({ // Tag pages handler
acceptUrls: /tags\/.*(\d*)\.html/
});
router.route({ // Blog pages handler
acceptUrls: /blog\d*\.html/
});
router.defaultRoute({ // Default page handler
beforeSend: undefined,
onSuccess: undefined,
onError: undefined
});
router.beforeSend(function (url) {
// Add .loading to #page-content and #nav to facilitate a loading animation
$('#page-content').addClass('loading');
});
router.onSuccess(function (url, dta) {
// Stop animations in the nav and page-content and scroll to the top of the page in a set amount of time
setTimeout(function () {
// Replace old page-content with new page-content
$('#page-content').html(dta);
// Add anchor click handlers for internal links in new content loaded into #page-content
router.jsUrls('#page-content a');
// Stop page loading
$('#page-content, #nav').removeClass('loading');
// Reload any new maths using MathJax
$('#page-content .math').each(function (math_elem) {
mj.Hub.Queue(["Typeset", mj.Hub, math_elem[0]]);
});
// Add fullscreen functionality to inline-images and figures
$('article.post p > img').click(function () {
$(this).get(0).toggleFullScreen();
});
$('figure').click(function () {
$(this).children('img').get(0).toggleFullScreen();
});
// Scroll to top of the page
if ($('body').scrollTop() > $('#nav').offset().top - 15) {
$('html, body').animate({
scrollTop: $('#nav').offset().top - 15
}, 'fast');
}
}, 250);
});
router.onError(function (url, status) {
/* Remove .loading from #page-content and #nav to stop the loading
* animation. Finally, display an error message in #status.
*/
$('#page-content, #nav').removeClass('loading');
// TODO: instead of immediately displaying error, check if the content is stored in local storage
$('#status').prepend('<p class="message">Error retrieving page ' + url + '</p>');
$('#status').addClass('error').slideDown();
});
site.init();
})(jQuery, MathJax);
// Modified from: https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Using_full_screen_mode // Modified from: https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Using_full_screen_mode
function toggleFullScreen() { function toggleFullScreen() {