mirror of
https://github.com/locomotivemtl/locomotive-boilerplate.git
synced 2026-01-15 00:55:08 +08:00
Merge pull request #47 from locomotivemtl/event-dispatch
Restructuring of scripts for better use of ES6 concepts
This commit is contained in:
@@ -1,36 +1,34 @@
|
||||
/* jshint esnext: true */
|
||||
// ==========================================================================
|
||||
import globals from './utils/globals';
|
||||
import * as modules from './modules';
|
||||
|
||||
class App {
|
||||
constructor(options) {
|
||||
constructor() {
|
||||
this.modules = modules;
|
||||
this.globals;
|
||||
this.currentModules = [];
|
||||
}
|
||||
|
||||
// Init globals
|
||||
// ==========================================================================
|
||||
/**
|
||||
* Execute global functions and settings
|
||||
* @return {Object}
|
||||
*/
|
||||
initGlobals() {
|
||||
this.globals = new this.modules['Globals'];
|
||||
globals();
|
||||
return this;
|
||||
}
|
||||
|
||||
// Init modules
|
||||
// ==========================================================================
|
||||
/**
|
||||
* Module init
|
||||
*
|
||||
* @todo [1] Discuss storing instanciated objects
|
||||
* @todo [2] Discuss singleton concept (one off functions/declarations)
|
||||
* @return {thigArg}
|
||||
* Find modules and initialize them
|
||||
* @return {Object} this Allows chaining
|
||||
*/
|
||||
initModules() {
|
||||
// Elements with module
|
||||
const moduleEls = document.querySelectorAll('[data-module]');
|
||||
var moduleEls = document.querySelectorAll('[data-module]');
|
||||
|
||||
// Loop through elements
|
||||
let i = 0,
|
||||
elsLen = moduleEls.length;
|
||||
var i = 0;
|
||||
var elsLen = moduleEls.length;
|
||||
|
||||
for (; i < elsLen; i++) {
|
||||
|
||||
// Current element
|
||||
@@ -39,7 +37,7 @@ class App {
|
||||
// All data- attributes considered as options
|
||||
let options = this.getElemData(el);
|
||||
|
||||
// Add current element AND jQuery element
|
||||
// Add current DOM element and jQuery element
|
||||
options.el = el;
|
||||
options.$el = $(el);
|
||||
|
||||
@@ -47,18 +45,17 @@ class App {
|
||||
let attr = options.module;
|
||||
|
||||
// Splitting modules found in the data-attribute
|
||||
let moduleAttrs = attr.replace(/\s/g, '').split(',');
|
||||
let moduleIdents = attr.replace(/\s/g, '').split(',');
|
||||
|
||||
// Loop modules
|
||||
let j = 0,
|
||||
modLen = moduleAttrs.length
|
||||
for (; j < modLen; j++) {
|
||||
let moduleAttr = moduleAttrs[j];
|
||||
let j = 0;
|
||||
let modulesLen = moduleIdents.length;
|
||||
|
||||
if (typeof this.modules[moduleAttr] === 'function' && this.currentModules.indexOf(moduleAttr) === -1) {
|
||||
// [1,2]
|
||||
for (; j < modulesLen; j++) {
|
||||
let moduleAttr = moduleIdents[j];
|
||||
|
||||
if (typeof this.modules[moduleAttr] === 'function') {
|
||||
let module = new this.modules[moduleAttr](options);
|
||||
// [2]
|
||||
this.currentModules.push(module);
|
||||
}
|
||||
}
|
||||
@@ -67,32 +64,20 @@ class App {
|
||||
return this;
|
||||
}
|
||||
|
||||
// Init
|
||||
// ==========================================================================
|
||||
init() {
|
||||
this.initGlobals();
|
||||
this.initModules();
|
||||
}
|
||||
|
||||
|
||||
// Utils
|
||||
// ==========================================================================
|
||||
//
|
||||
/**
|
||||
* Get element datas
|
||||
*
|
||||
* @param {DOMElement} el
|
||||
* @return {Array} data
|
||||
* Get element data attributes
|
||||
* @param {DOMElement} el
|
||||
* @return {Array} data
|
||||
*/
|
||||
getElemData(el) {
|
||||
// All attributes
|
||||
let attributes = el.attributes;
|
||||
var attributes = el.attributes;
|
||||
|
||||
// Regex Pattern
|
||||
let pattern = /^data\-(.+)$/;
|
||||
var pattern = /^data\-(.+)$/;
|
||||
|
||||
// Output
|
||||
let data = {};
|
||||
var data = {};
|
||||
|
||||
for (let i in attributes) {
|
||||
// Attributes name (ex: data-module)
|
||||
@@ -109,16 +94,21 @@ class App {
|
||||
}
|
||||
|
||||
// If this throws an error, you have some
|
||||
// serious problem in your HTML.
|
||||
data[ match[1] ] = el.getAttribute(name);
|
||||
// serious problems in your HTML.
|
||||
data[match[1]] = el.getAttribute(name);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
// Document ready
|
||||
// ==========================================================================
|
||||
/**
|
||||
* Initialize app after document ready
|
||||
*/
|
||||
init() {
|
||||
this.initGlobals().initModules();
|
||||
}
|
||||
}
|
||||
|
||||
$(function() {
|
||||
window.app = new App();
|
||||
window.app.init();
|
||||
|
||||
4
assets/scripts/global/svg.js
Normal file
4
assets/scripts/global/svg.js
Normal file
@@ -0,0 +1,4 @@
|
||||
/* jshint esnext: true */
|
||||
export default function() {
|
||||
svg4everybody();
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
export {default as Globals} from './modules/Globals';
|
||||
export {default as Generic} from './modules/Generic';
|
||||
/* jshint esnext: true */
|
||||
export {default as Button} from './modules/Button';
|
||||
export {default as Title} from './modules/Title';
|
||||
|
||||
16
assets/scripts/modules/AbstractModule.js
Normal file
16
assets/scripts/modules/AbstractModule.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/* jshint esnext: true */
|
||||
import { $document, $window, $html, $body } from '../utils/environment';
|
||||
|
||||
/**
|
||||
* Abstract module
|
||||
* Gives access to generic jQuery nodes
|
||||
*/
|
||||
export default class {
|
||||
constructor(options) {
|
||||
this.$document = $document;
|
||||
this.$window = $window;
|
||||
this.$html = $html;
|
||||
this.$body = $body;
|
||||
this.$el = options.$el;
|
||||
}
|
||||
}
|
||||
16
assets/scripts/modules/Button.js
Normal file
16
assets/scripts/modules/Button.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/* jshint esnext: true */
|
||||
import AbstractModule from './AbstractModule';
|
||||
|
||||
export default class extends AbstractModule {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.$el.on('click.Button', (event) => {
|
||||
this.$document.trigger('Title.changeLabel', [$(event.currentTarget).val()]);
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.$el.off('.Button');
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// ==========================================================================
|
||||
// Generic module
|
||||
// ==========================================================================
|
||||
import Module from './Module';
|
||||
|
||||
class Generic extends Module {
|
||||
constructor(options) {
|
||||
super();
|
||||
this.$el = options.$el;
|
||||
|
||||
console.log('Generic module');
|
||||
console.log(this.$el);
|
||||
}
|
||||
|
||||
// Destroy
|
||||
// ==========================================================================
|
||||
destroy() {
|
||||
this.$el.off();
|
||||
}
|
||||
}
|
||||
|
||||
export default Generic;
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// ==========================================================================
|
||||
// Globals module
|
||||
// ==========================================================================
|
||||
import Svg from './Svg';
|
||||
|
||||
class Globals {
|
||||
constructor() {
|
||||
new Svg();
|
||||
}
|
||||
}
|
||||
|
||||
export default Globals;
|
||||
@@ -1,13 +0,0 @@
|
||||
// ==========================================================================
|
||||
// Module
|
||||
// ==========================================================================
|
||||
|
||||
class Module {
|
||||
constructor() {
|
||||
this.$window = $(window);
|
||||
this.$html = $(document.documentElement);
|
||||
this.$body = $(document.body);
|
||||
}
|
||||
}
|
||||
|
||||
export default Module;
|
||||
@@ -1,21 +0,0 @@
|
||||
// ==========================================================================
|
||||
// Svg module
|
||||
// ==========================================================================
|
||||
import Module from './Module';
|
||||
|
||||
class Svg extends Module {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
svg4everybody();
|
||||
}
|
||||
|
||||
// Destroy
|
||||
// ==========================================================================
|
||||
destroy() {
|
||||
this.$el.off();
|
||||
}
|
||||
}
|
||||
|
||||
export default Svg;
|
||||
|
||||
@@ -1,23 +1,58 @@
|
||||
// ==========================================================================
|
||||
// Title module
|
||||
// ==========================================================================
|
||||
import Module from './Module';
|
||||
/* jshint esnext: true */
|
||||
import { visibilityApi } from '../utils/visibility';
|
||||
import AbstractModule from './AbstractModule';
|
||||
|
||||
class Title extends Module {
|
||||
export default class extends AbstractModule {
|
||||
constructor(options) {
|
||||
super();
|
||||
this.$el = options.$el;
|
||||
super(options);
|
||||
|
||||
console.log('Title module');
|
||||
console.log(this.$el);
|
||||
this.$label = this.$el.find('.js-label');
|
||||
|
||||
this.$document.on('Title.changeLabel', (event, value) => {
|
||||
this.changeLabel(value);
|
||||
this.destroy();
|
||||
});
|
||||
|
||||
this.hiddenCallbackIdent = visibilityApi({
|
||||
action: 'addCallback',
|
||||
state: 'hidden',
|
||||
callback: this.logHidden
|
||||
});
|
||||
|
||||
this.visibleCallbackIdent = visibilityApi({
|
||||
action: 'addCallback',
|
||||
state: 'visible',
|
||||
callback: this.logVisible
|
||||
});
|
||||
}
|
||||
|
||||
logHidden() {
|
||||
console.log('Title is hidden');
|
||||
}
|
||||
|
||||
logVisible() {
|
||||
console.log('Title is visible');
|
||||
}
|
||||
|
||||
changeLabel(value) {
|
||||
this.$label.text(value);
|
||||
}
|
||||
|
||||
// Destroy
|
||||
// ==========================================================================
|
||||
destroy() {
|
||||
this.$el.off();
|
||||
this.$document.off('Title.changeLabel');
|
||||
|
||||
visibilityApi({
|
||||
action: 'removeCallback',
|
||||
state: 'hidden',
|
||||
ident: this.hiddenCallbackIdent
|
||||
});
|
||||
|
||||
visibilityApi({
|
||||
action: 'removeCallback',
|
||||
state: 'visible',
|
||||
ident: this.visibleCallbackIdent
|
||||
});
|
||||
|
||||
this.$el.off('.Title');
|
||||
}
|
||||
}
|
||||
|
||||
export default Title;
|
||||
|
||||
|
||||
83
assets/scripts/utils/array.js
Normal file
83
assets/scripts/utils/array.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import { isArray } from './is';
|
||||
|
||||
export function addToArray ( array, value ) {
|
||||
var index = array.indexOf( value );
|
||||
|
||||
if ( index === -1 ) {
|
||||
array.push( value );
|
||||
}
|
||||
}
|
||||
|
||||
export function arrayContains ( array, value ) {
|
||||
for ( let i = 0, c = array.length; i < c; i++ ) {
|
||||
if ( array[i] == value ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function arrayContentsMatch ( a, b ) {
|
||||
var i;
|
||||
|
||||
if ( !isArray( a ) || !isArray( b ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( a.length !== b.length ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
i = a.length;
|
||||
while ( i-- ) {
|
||||
if ( a[i] !== b[i] ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function ensureArray ( x ) {
|
||||
if ( typeof x === 'string' ) {
|
||||
return [ x ];
|
||||
}
|
||||
|
||||
if ( x === undefined ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
export function lastItem ( array ) {
|
||||
return array[ array.length - 1 ];
|
||||
}
|
||||
|
||||
export function removeFromArray ( array, member ) {
|
||||
if ( !array ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = array.indexOf( member );
|
||||
|
||||
if ( index !== -1 ) {
|
||||
array.splice( index, 1 );
|
||||
}
|
||||
}
|
||||
|
||||
export function toArray ( arrayLike ) {
|
||||
var array = [], i = arrayLike.length;
|
||||
while ( i-- ) {
|
||||
array[i] = arrayLike[i];
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
export function findByKeyValue( array, key, value ) {
|
||||
return array.filter(function( obj ) {
|
||||
return obj[key] === value;
|
||||
});
|
||||
}
|
||||
6
assets/scripts/utils/environment.js
Normal file
6
assets/scripts/utils/environment.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const $document = $(document);
|
||||
const $window = $(window);
|
||||
const $html = $(document.documentElement);
|
||||
const $body = $(document.body);
|
||||
|
||||
export { $document, $window, $html, $body };
|
||||
6
assets/scripts/utils/globals.js
Normal file
6
assets/scripts/utils/globals.js
Normal file
@@ -0,0 +1,6 @@
|
||||
/* jshint esnext: true */
|
||||
import svg from '../global/svg';
|
||||
|
||||
export default function() {
|
||||
svg();
|
||||
}
|
||||
37
assets/scripts/utils/is.js
Normal file
37
assets/scripts/utils/is.js
Normal file
@@ -0,0 +1,37 @@
|
||||
var toString = Object.prototype.toString,
|
||||
arrayLikePattern = /^\[object (?:Array|FileList)\]$/;
|
||||
|
||||
// thanks, http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/
|
||||
export function isArray ( thing ) {
|
||||
return toString.call( thing ) === '[object Array]';
|
||||
}
|
||||
|
||||
export function isArrayLike ( obj ) {
|
||||
return arrayLikePattern.test( toString.call( obj ) );
|
||||
}
|
||||
|
||||
export function isEqual ( a, b ) {
|
||||
if ( a === null && b === null ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( typeof a === 'object' || typeof b === 'object' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return a === b;
|
||||
}
|
||||
|
||||
// http://stackoverflow.com/questions/18082/validate-numbers-in-javascript-isnumeric
|
||||
export function isNumeric ( thing ) {
|
||||
return !isNaN( parseFloat( thing ) ) && isFinite( thing );
|
||||
}
|
||||
|
||||
export function isObject ( thing ) {
|
||||
return ( thing && toString.call( thing ) === '[object Object]' );
|
||||
}
|
||||
|
||||
export function isFunction( thing ) {
|
||||
var getType = {};
|
||||
return thing && getType.toString.call(thing) === '[object Function]';
|
||||
}
|
||||
130
assets/scripts/utils/visibility.js
Normal file
130
assets/scripts/utils/visibility.js
Normal file
@@ -0,0 +1,130 @@
|
||||
/* jshint esnext: true */
|
||||
import { isFunction } from './is';
|
||||
import { arrayContains, findByKeyValue, removeFromArray } from './array';
|
||||
import { $document, $window, $html, $body } from './environment';
|
||||
|
||||
const CALLBACKS = {
|
||||
hidden: [],
|
||||
visible: []
|
||||
};
|
||||
|
||||
const ACTIONS = [
|
||||
'addCallback',
|
||||
'removeCallback'
|
||||
];
|
||||
|
||||
const STATES = [
|
||||
'visible',
|
||||
'hidden'
|
||||
];
|
||||
|
||||
const PREFIX = 'v-';
|
||||
|
||||
let UUID = 0;
|
||||
|
||||
// Main event
|
||||
$document.on('visibilitychange', function(event) {
|
||||
if (document.hidden) {
|
||||
onDocumentChange('hidden');
|
||||
} else {
|
||||
onDocumentChange('visible');
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Add a callback
|
||||
* @param {string} state
|
||||
* @param {function} callback
|
||||
* @return {string} ident
|
||||
*/
|
||||
function addCallback (state, options) {
|
||||
let callback = options.callback || '';
|
||||
|
||||
if (!isFunction(callback)) {
|
||||
console.warn('Callback is not a function');
|
||||
return false;
|
||||
}
|
||||
|
||||
let ident = PREFIX + UUID++;
|
||||
|
||||
CALLBACKS[state].push({
|
||||
ident: ident,
|
||||
callback: callback
|
||||
});
|
||||
|
||||
return ident;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a callback
|
||||
* @param {string} state Visible or hidden
|
||||
* @param {string} ident Unique identifier
|
||||
* @return {boolean} If operation was a success
|
||||
*/
|
||||
function removeCallback (state, options) {
|
||||
let ident = options.ident || '';
|
||||
|
||||
if (typeof(ident) === 'undefined' || ident === '') {
|
||||
console.warn('Need ident to remove callback');
|
||||
return false;
|
||||
}
|
||||
|
||||
let index = findByKeyValue(CALLBACKS[state], 'ident', ident)[0];
|
||||
|
||||
// console.log(ident)
|
||||
// console.log(CALLBACKS[state])
|
||||
|
||||
if (typeof(index) !== 'undefined') {
|
||||
removeFromArray(CALLBACKS[state], index);
|
||||
return true;
|
||||
} else {
|
||||
console.warn('Callback could not be found');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When document state changes, trigger callbacks
|
||||
* @param {string} state Visible or hidden
|
||||
*/
|
||||
function onDocumentChange (state) {
|
||||
let callbackArray = CALLBACKS[state];
|
||||
let i = 0;
|
||||
let len = callbackArray.length;
|
||||
|
||||
for (; i < len; i++) {
|
||||
callbackArray[i].callback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Public facing API for adding and removing callbacks
|
||||
* @param {object} options Options
|
||||
* @return {boolean|integer} Unique identifier for the callback or boolean indicating success or failure
|
||||
*/
|
||||
function visibilityApi (options) {
|
||||
let action = options.action || '';
|
||||
let state = options.state || '';
|
||||
let ret;
|
||||
|
||||
// Type and value checking
|
||||
if (!arrayContains(ACTIONS, action)) {
|
||||
console.warn('Action does not exist');
|
||||
return false;
|
||||
}
|
||||
if (!arrayContains(STATES, state)) {
|
||||
console.warn('State does not exist');
|
||||
return false;
|
||||
}
|
||||
|
||||
// @todo Magic call function pls
|
||||
if (action === 'addCallback') {
|
||||
ret = addCallback(state, options);
|
||||
} else if (action === 'removeCallback') {
|
||||
ret = removeCallback(state, options);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
export { visibilityApi };
|
||||
File diff suppressed because one or more lines are too long
@@ -16,7 +16,11 @@
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1 data-module="Generic, Title">Locomotive boilerplate</h1>
|
||||
<div data-module="Title">
|
||||
<h1 class="js-label">Locomotive boilerplate</h1>
|
||||
</div>
|
||||
|
||||
<button data-module="Button" type="button" value="Clicked! Title destroyed">Change value and destroy Title()</button>
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>
|
||||
<script>window.jQuery || document.write('<script src="assets/scripts/jquery-2.2.2.min.js"><\/script>')</script>
|
||||
|
||||
Reference in New Issue
Block a user