highway.js
Version:
Highway is an light-weight JS Page Loader
741 lines (618 loc) • 19.4 kB
JavaScript
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define("Highway", [], factory);
else if(typeof exports === 'object')
exports["Highway"] = factory();
else
root["Highway"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // identity function for calling harmony imports with the correct context
/******/ __webpack_require__.i = function(value) { return value; };
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 2);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* Highway - Loader File:
* This file contains the methods handling the routing of your pages.
*
* @author: Anthodpnt <antho.dpnt@gmail.com>
* @version: 1.0.0
*/
var Loader = function () {
/**
* Constructor:
* Construct the Loader, initialise it and extract options.
*
* @param {object} options - The Options
*/
function Loader(options) {
_classCallCheck(this, Loader);
// Options
this.views = options.views;
this.mode = options.mode || 'out-in';
// Events
this.onPopstate = this.popstate.bind(this);
this.onPathChange = this.change.bind(this);
// PopState
window.addEventListener('popstate', this.onPopstate);
// Init
this.bind();
this.init();
}
/**
* Initialisation:
* When landing on the website the first page does'nt pass through the loader
* so we need to initialise it as well as the attributes of our loader.
*/
_createClass(Loader, [{
key: 'init',
value: function init() {
// Attributes
var title = document.querySelector('title').innerHTML;
var view = document.querySelector('[loader-view]');
var name = view.getAttribute('loader-view');
this.path = window.location.pathname;
this.stack = {};
this.stack[this.path] = {
title: title,
view: view,
name: name
};
// Load Current
this.current = new this.views[name](this.stack[this.path]);
this.current.init();
// Active
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = this.links[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var link = _step.value;
if (link.pathname === this.path) {
this.active(link);
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
}
/**
* Binding:
* We create events we bind on DOM elements.
*/
}, {
key: 'bind',
value: function bind() {
// Get Links
this.links = document.querySelectorAll('a:not([loader-disabled])');
// Add Events on DOM Links
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = this.links[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var link = _step2.value;
link.addEventListener('click', this.onPathChange);
link.addEventListener('touchstart', this.onPathChange);
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
}
/**
* Unbinding:
* We remove events we bound on DOM elements.
*/
}, {
key: 'unbind',
value: function unbind() {
// Remove Events from DOM Links
var _iteratorNormalCompletion3 = true;
var _didIteratorError3 = false;
var _iteratorError3 = undefined;
try {
for (var _iterator3 = this.links[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
var link = _step3.value;
link.removeEventListener('click', this.onPathChange);
link.removeEventListener('touchstart', this.onPathChange);
}
} catch (err) {
_didIteratorError3 = true;
_iteratorError3 = err;
} finally {
try {
if (!_iteratorNormalCompletion3 && _iterator3.return) {
_iterator3.return();
}
} finally {
if (_didIteratorError3) {
throw _iteratorError3;
}
}
}
}
/**
* Popstate:
* We fetch the path from the window location on `popstate` event
*/
}, {
key: 'popstate',
value: function popstate() {
// Update Path
this.path = window.location.pathname;
// Active
var _iteratorNormalCompletion4 = true;
var _didIteratorError4 = false;
var _iteratorError4 = undefined;
try {
for (var _iterator4 = this.links[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
var link = _step4.value;
if (link.pathname === this.path) {
this.temp = link;
}
}
// Fetch Path
} catch (err) {
_didIteratorError4 = true;
_iteratorError4 = err;
} finally {
try {
if (!_iteratorNormalCompletion4 && _iterator4.return) {
_iterator4.return();
}
} finally {
if (_didIteratorError4) {
throw _iteratorError4;
}
}
}
this.fetchCall();
}
/**
* Active:
* Set active classnames on elements and remove previous active classname.
*
* @param {object} el - The Element
*/
}, {
key: 'active',
value: function active(el) {
var classname = el.getAttribute('loader-active');
if (classname) {
if (this.HTMLActive) {
var _classname = this.HTMLActive.getAttribute('loader-active');
this.HTMLActive.classList.remove(_classname);
}
this.HTMLActive = el;
this.HTMLActive.classList.add(classname);
}
}
/**
* Change:
* We check when path changes on link `click` event
*
* @param {object} event - The `click` Event
*/
}, {
key: 'change',
value: function change(event) {
// Prevent event from firing
event = event || window.event;
if (event.preventDefault) {
event.preventDefault();
}
event.returnValue = false;
// Attributes
var link = event.currentTarget;
var path = link.getAttribute('href');
var target = link.getAttribute('target');
// We check if the current link target should open in a new blank window.
// If the target is set to `_blank` the script stops otherwise it continues.
if (target === '_blank') {
// Open New Window
return window.open(path, '_blank');
}
// Save Link Temporary
this.temp = link;
// Update
this.update(path);
}
/**
* Update:
* We check when path updates.
*
* @param {string} path - The Path
*/
}, {
key: 'update',
value: function update(path) {
// We check if the current link redirects to a different page than the
// displayed one. If the two pages are different the script continues
// otherwise the script stops.
if (path !== this.path) {
// Update Path
this.path = path;
// Push State in History
window.history.pushState({ path: this.path }, '', this.path);
// Fetch Path
this.fetchCall();
}
}
/**
* Fetch:
* We fetch the path.
*/
}, {
key: 'fetchCall',
value: function fetchCall() {
var _this = this;
// We check if the path has been cached and we fetch it if not.
if (!this.stack[this.path]) {
fetch(this.path).then(function (response) {
return response.text();
}).then(function (result) {
return _this.fetchSuccess(result);
}).catch(function (error) {
return _this.fetchError(error);
});
return;
}
// Redirect
this.redirect();
}
/**
* Fetch Sucess:
* We fetch update DOM on fetch success.
*
* @param {string} result - The Page HTML
*/
}, {
key: 'fetchSuccess',
value: function fetchSuccess(result) {
// Update Attributes
var page = document.createRange().createContextualFragment(result);
var title = page.querySelector('title').innerHTML;
var view = page.querySelector('[loader-view]');
var name = view.getAttribute('loader-view');
// Update Stack
this.stack[this.path] = {
title: title,
view: view,
name: name
};
// Redirect
this.redirect();
}
/**
* Fetch Error:
* We throw an error message on fetch error.
*
* @param {string} error - The Error
*/
}, {
key: 'fetchError',
value: function fetchError(error) {
throw Error(error);
}
/**
* Redirect:
* We update DOM with the page to display.
*/
}, {
key: 'redirect',
value: function redirect() {
var _this2 = this;
var name = this.stack[this.path].name;
// Update Previous/Current
this.previous = this.current;
this.current = new this.views[name](this.stack[this.path]);
// Update Active
this.active(this.temp);
// Unbind Events
this.unbind();
// Load/Unload Views
switch (this.mode) {
// Both:
// Previous and current transitions run at the same time.
case 'both':
this.current.load();
this.previous.unload().then(function () {
return _this2.bind();
}).catch(function (e) {
throw Error(e);
});
break;
// In-Out:
// Previous transition runs after current transition is over.
case 'in-out':
this.current.load().then(function () {
return _this2.previous.unload();
}).then(function () {
return _this2.bind();
}).catch(function (e) {
throw Error(e);
});
break;
// Out-In:
// Current transition runs after previous transition is over (default).
default:
this.previous.unload().then(function () {
return _this2.current.load();
}).then(function () {
return _this2.bind();
}).catch(function (e) {
throw Error(e);
});
}
}
}]);
return Loader;
}();
// Export Class
exports.default = Loader;
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* Highway - View File:
* This file contains the methods handling the display of your pages.
*
* @author: Anthodpnt <antho.dpnt@gmail.com>
* @version: 1.0.0
*/
var View = function () {
/**
* Constructor:
* Construct the View, get the `loader-wrapper` and extract options.
*
* @param {object} options - The options
*/
function View(options) {
_classCallCheck(this, View);
// Wrapper
this.wrapper = document.querySelector('[loader-wrapper]');
// Options
this.title = options.title;
this.view = options.view;
}
/**
* Initialisation:
* We call the enter completed method. This is usefull for the landing page
* that does not pass through the loader.
*/
_createClass(View, [{
key: 'init',
value: function init() {
// Enter Completed
if (this.onEnterCompleted) {
this.onEnterCompleted();
}
}
/**
* Append:
* We append the view content to the `loader-wrapper` and we update the page
* title.
*/
}, {
key: 'append',
value: function append() {
// Update Title
document.title = this.title;
// Append
this.wrapper.appendChild(this.view);
}
/**
* Remove:
* We remove the view content from the `loader-wrapper`.
*/
}, {
key: 'remove',
value: function remove() {
// Remove
this.wrapper.removeChild(this.view);
}
/**
* Load:
* We load the view content and call the transition `in`. Analytics are
* updated as well if the GA script has been setup.
*/
}, {
key: 'load',
value: function load() {
var _this = this;
// Enter
if (this.onEnter) {
this.onEnter();
}
// Append
this.append();
// Promise for Async methods
return new Promise(function (resolve, reject) {
// Transition
_this.transition.in(_this.view, function () {
// Resolve
resolve();
// Send GA
_this.analytics();
// Enter Completed
if (_this.onEnterCompleted) {
_this.onEnterCompleted();
}
});
});
}
/**
* Unload:
* We unload the view content and call the transition `out`.
*/
}, {
key: 'unload',
value: function unload() {
var _this2 = this;
// Leave
if (this.onLeave) {
this.onLeave();
}
// Promise for Async methods
return new Promise(function (resolve, reject) {
// Transition
_this2.transition.out(_this2.view, function () {
// Resolve
resolve();
// Remove
_this2.remove();
// Leave Completed
if (_this2.onLeaveCompleted) {
_this2.onLeaveCompleted();
}
});
});
}
/**
* Analytics:
* We update GA if the script has been setup.
*/
}, {
key: 'analytics',
value: function analytics() {
// Send Page View to GA
if (typeof ga !== 'undefined') {
ga('set', { page: window.location.pathname, title: document.title });
ga('send', 'pageview');
}
}
}]);
return View;
}();
// Export Class
exports.default = View;
/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _view = __webpack_require__(1);
var _view2 = _interopRequireDefault(_view);
var _loader = __webpack_require__(0);
var _loader2 = _interopRequireDefault(_loader);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Highway - Main File:
* This file requires the requested files for Highway.
*
* @author: Anthodpnt <antho.dpnt@gmail.com>
* @version: 1.0.0
*/
var Highway = {
View: _view2.default,
Loader: _loader2.default
};
exports.default = Highway;
/***/ })
/******/ ]);
});
//# sourceMappingURL=highway.js.map