/*!
* Vocabulary 0.9.1
* https://github.com/visiongeist/vocabulary
*
*
* Copyright 2014 Damien Antipa
* Released under the MIT license
*
* Date: 2014-06-22T10:38:51.324Z
*/
(function (win, undefined) {
// const
var VERSION = '0.9.1',
DEBUG = false,
GLOBAL_NAME = 'vocabulary',
PREFIX = 'voc',
ACTION_IDENTIFIER = 'action',
DEFAULT_EVENT = 'click',
EVENTS = ["blur","change","click","dblclick","focus","focusin","focusout","keydown","keypress","keyup","mousedown","mouseenter","mouseleave","mousemove","mouseout","mouseover","mouseup","submit"];
// private
var store = {};
// dictionary (is also a chapter)
/**
* A dictionary is a collection of chapters
* @constructor Dictionary
* @augments Chapter
*
* @param name
*/
function Dictionary(name) {
this._chapters = {};
this.constructor.prototype.constructor.call(this, name);
}
/**
* Returns a chapter or a list of available chapters
* @memberof Dictionary
* @param {String} [name] name of a chapter or empty to list all
* @return {Chapter|Chapter[]}
*/
Dictionary.prototype.chap = function (name) {
return name ? this._chapters[name] = this._chapters[name] || (new Chapter(name))
: this._chapters;
};
/**
* A chapter is a collection of words which are associated to actions
* @constructor Chapter
* @param {String} name name of a chapter or empty to list all
*/
function Chapter(name) {
this.name = name;
this._actions = {};
}
// a dictionary may be directly used as a chapter
Dictionary.prototype = new Chapter();
/**
* Add new word to a chapter
* @memberof Chapter
* @param {String} word
* @param {Function} fn
* @param {Mixed} [options]
*/
Chapter.prototype.add = function (word, fn, options) {
// TODO add plugin hook
// plugins should be to react on options
this._actions[word] = fn;
return this;
};
/**
* Directly execute a word
* @memberof Chapter
* @param {String} word
* @param {Object} options
* @return {Dictionary} the current dictionary
*/
Chapter.prototype.execute = function (word, options) {
// TODO add plugin hook
// plugins should be able to modify the options before they get passed to the handler
if (this._actions[word]) {
this._actions[word].call(this, options);
} else if (DEBUG) {
console.warn('No action defined for ' + word, this, options);
}
return this;
};
/**
* @memberof Chapter
* @return {Object} [description]
*/
Chapter.prototype.list = function () {
return this._actions;
};
/**
* @namespace vocabulary
*/
var vocabulary = {
/**
* version of the running vocabulary
* @memberof vocabulary
* @type {String}
*/
version: VERSION,
/**
* List of bound event types
* @memberof vocabulary
* @type {String[]}
*/
events: [],
/**
* adds additional events for observation
* @memberof vocabulary
* @param {String[]|String} evt Array of event names or space separated list of events
*/
addEvent: function (evt) {
var events = evt instanceof Array ? evt : evt.split(' ');
for (var i=0; i < evt.length; i++) {
// add event for information purpose
this.events.push(evt[i]);
//register delegation
document.addEventListener(evt[i], eventHandler);
}
},
/**
* access a dictionary
* @memberof vocabulary
* @param {String} [name]
* @return {Dictionary|Object} the dictionary to lookup or a key-value map of all dictionaries if name is null
*/
dict: function (name) {
return name ? store[name] = store[name] || (new Dictionary(name))
: store;
}
};
/**
* Find actions in the chain
* @private
* @param {HTMLElement} target
* @return {HTMLElement[]}
*/
function matchIdentifier(target) {
// TODO add plugin hook
// plugins should be able to filter additionally
var result = [],
node = target,
search = PREFIX + '-' + ACTION_IDENTIFIER;
while (node && node !== document) {
if (node.classList.contains(search)) {
result.push(node);
}
node = node.parentNode;
}
return result;
}
/**
* Handling the events
* @param {Event} ev
*/
function eventHandler(ev) {
var hit = matchIdentifier(ev.target),
actionId = PREFIX + '-' + ACTION_IDENTIFIER;
if (hit.length) {
for (var i=0; i < hit.length; i++) {
// find commands
for (var j=0; j < hit[i].classList.length; j++) {
if (hit[i].classList[j].indexOf(PREFIX + '-') === 0 && hit[i].classList[j] !== actionId) {
// PREFIX-DICT(-CHAP)-CMD(--EVENT)
var cmd = hit[i].classList[j].match(/([^-]+)-([^-]+)-?([^-]*)-([^-]+)[-]{0,2}(.*)/),
options = {},
def = {
prefix: PREFIX,
dict: null,
chap: null,
word: null,
event: null
},
eventIsDef, selectedDict, dataAttr;
// either specific or default command
def.event = cmd[5] || DEFAULT_EVENT;
// event specific command
if (ev.type !== def.event) {
// abort if command does not match event
continue;
}
def.dict = cmd[2];
def.chap = cmd[3];
def.word = cmd[4];
//read data attributes
for (dataAttr in hit[i].dataset) {
if (hit[i].dataset.hasOwnProperty(dataAttr)) {
options[dataAttr] = hit[i].dataset[dataAttr];
}
}
// add additional data
options.definition = def;
options.target = hit[i];
options.originalEvent = ev;
//execute command
selectedDict = vocabulary.dict(def.dict);
(selectedDict || selectedDict.chap(def.chap)).execute(def.word, options);
}
}
}
}
}
// add pre-configured events
vocabulary.addEvent(EVENTS);
// reveal
win[GLOBAL_NAME] = vocabulary;
}(this));