279 lines
6.4 KiB
JavaScript
279 lines
6.4 KiB
JavaScript
(function() {
|
|
'use strict';
|
|
var $, Component,
|
|
__slice = [].slice;
|
|
|
|
$ = jQuery;
|
|
|
|
/*
|
|
JS component base class.
|
|
|
|
Elements: any HTML element with class name that follow a pattern `.js-name` where `name` is an element name.
|
|
|
|
States: any class on component root HTML node that follow a pattern `.is-state` where `state` is a state name.
|
|
After initialization all components will have `ok` state.
|
|
|
|
Example:
|
|
|
|
class Pony extends Component
|
|
init: ->
|
|
@on('click', 'toggle', @toggle)
|
|
toggle: ->
|
|
@toggleState('pink')
|
|
|
|
tamia.initComponents(pony: Pony)
|
|
|
|
<div class="pink-pony is-pink" data-component="pony">
|
|
<button class="pink-pony__button js-toggle">To pink or not to pink?</div>
|
|
</div>
|
|
*/
|
|
|
|
|
|
Component = (function() {
|
|
function Component(elem) {
|
|
if (!elem || elem.nodeType !== 1) {
|
|
throw new ReferenceError('No DOM node passed to Component constructor.');
|
|
}
|
|
this.elemNode = elem;
|
|
this.elem = $(elem);
|
|
this.initializable = this.isInitializable();
|
|
if (!this.initializable) {
|
|
return;
|
|
}
|
|
this._fillStates();
|
|
if (this.isSupported()) {
|
|
this.handlers = {};
|
|
this.init();
|
|
this.addState('ok');
|
|
} else {
|
|
this.fallback();
|
|
this.addState('unsupported');
|
|
}
|
|
}
|
|
|
|
/*
|
|
Put all your initialization code in this method.
|
|
*/
|
|
|
|
|
|
Component.prototype.init = function() {};
|
|
|
|
/*
|
|
You can implement this method to do destroy component.
|
|
*/
|
|
|
|
|
|
Component.prototype.destroy = function() {};
|
|
|
|
/*
|
|
Implement this method if you want to check whether browser is good for your component or not.
|
|
|
|
@returns {Boolean}
|
|
*/
|
|
|
|
|
|
Component.prototype.isSupported = function() {
|
|
return true;
|
|
};
|
|
|
|
/*
|
|
Implement this method if you want to check whether component could be initialized.
|
|
|
|
Example:
|
|
|
|
isInitializable: ->
|
|
# Do not initialize component if it's not visible
|
|
@isVisible()
|
|
|
|
@return {Boolean}
|
|
*/
|
|
|
|
|
|
Component.prototype.isInitializable = function() {
|
|
return true;
|
|
};
|
|
|
|
/*
|
|
You can implement this method to do some fallbacks. It will be called if isSupported() returns false.
|
|
*/
|
|
|
|
|
|
Component.prototype.fallback = function() {};
|
|
|
|
/*
|
|
Finds element.
|
|
|
|
@param {String} name Element ID.
|
|
|
|
@return {jQuery} Element with .js-name class.
|
|
*/
|
|
|
|
|
|
Component.prototype.find = function(name) {
|
|
return this.elem.find(".js-" + name).first();
|
|
};
|
|
|
|
/*
|
|
Attaches event handler.
|
|
|
|
@param {String} events Event names (space separated).
|
|
@param {String} [element] Element id.
|
|
@param {Function} handler Handler function (scope will automatically sets to this).
|
|
*/
|
|
|
|
|
|
Component.prototype.on = function() {
|
|
var args;
|
|
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
|
return this._toggleEvent.apply(this, ['on'].concat(__slice.call(args)));
|
|
};
|
|
|
|
/*
|
|
Detaches event handler.
|
|
|
|
@param {String} events Event names (space separated).
|
|
@param {String} [element] Element id.
|
|
@param {Function} handler Handler function (scope will automatically sets to this).
|
|
*/
|
|
|
|
|
|
Component.prototype.off = function() {
|
|
var args;
|
|
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
|
return this._toggleEvent.apply(this, ['off'].concat(__slice.call(args)));
|
|
};
|
|
|
|
/*
|
|
Returns component state.
|
|
|
|
@param {String} [name] State name.
|
|
|
|
@return {Boolean} Sate value.
|
|
*/
|
|
|
|
|
|
Component.prototype.hasState = function(name) {
|
|
return !!this.states[name];
|
|
};
|
|
|
|
/*
|
|
Sets state to true.
|
|
|
|
@param {String} [name] State name.
|
|
*/
|
|
|
|
|
|
Component.prototype.addState = function(name) {
|
|
return this.toggleState(name, true);
|
|
};
|
|
|
|
/*
|
|
Sets state to false.
|
|
|
|
@param {String} [name] State name.
|
|
*/
|
|
|
|
|
|
Component.prototype.removeState = function(name) {
|
|
return this.toggleState(name, false);
|
|
};
|
|
|
|
/*
|
|
Toggles state value.
|
|
|
|
@param {String} [name] State name.
|
|
@param {Boolean} [value] State value.
|
|
*/
|
|
|
|
|
|
Component.prototype.toggleState = function(name, value) {
|
|
if (value == null) {
|
|
value = !this.states[name];
|
|
}
|
|
this.states[name] = value;
|
|
return this._updateStates();
|
|
};
|
|
|
|
/*
|
|
Returns component visibility.
|
|
|
|
@return {Boolean}
|
|
*/
|
|
|
|
|
|
Component.prototype.isVisible = function() {
|
|
return !!(this.elemNode.offsetWidth || this.elemNode.offsetHeight);
|
|
};
|
|
|
|
Component.prototype._toggleEvent = function() {
|
|
var action, args, func, funcArg, handler, _ref;
|
|
action = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
|
|
if (typeof args[1] === 'string') {
|
|
args[1] = ".js-" + args[1];
|
|
}
|
|
funcArg = args.length - 1;
|
|
func = args[funcArg];
|
|
handler;
|
|
if (this.handlers[func]) {
|
|
handler = this.handlers[func];
|
|
}
|
|
if (action === 'on') {
|
|
if (handler) {
|
|
handler.counter++;
|
|
} else {
|
|
this.handlers[func] = handler = {
|
|
counter: 1,
|
|
func: func.bind(this)
|
|
};
|
|
}
|
|
}
|
|
if (!handler) {
|
|
return;
|
|
}
|
|
args[funcArg] = handler.func;
|
|
(_ref = this.elem)[action].apply(_ref, args);
|
|
if (action === 'off') {
|
|
handler.counter--;
|
|
if (handler.counter <= 0) {
|
|
return this.handlers[func] = null;
|
|
}
|
|
}
|
|
};
|
|
|
|
Component.prototype._fillStates = function() {
|
|
var classes, cls, clsName, re, states;
|
|
states = {};
|
|
classes = this.elemNode.className.split(' ');
|
|
for (clsName in classes) {
|
|
cls = classes[clsName];
|
|
re = /^is-/;
|
|
if (re.test(cls)) {
|
|
states[cls.replace(re, '')] = true;
|
|
}
|
|
}
|
|
return this.states = states;
|
|
};
|
|
|
|
Component.prototype._updateStates = function() {
|
|
var classes, name;
|
|
classes = this.elemNode.className;
|
|
classes = $.trim(classes.replace(/\bis-[-\w]+/g, ''));
|
|
classes = classes.split(/\s+/);
|
|
for (name in this.states) {
|
|
if (this.states[name]) {
|
|
classes.push("is-" + name);
|
|
}
|
|
}
|
|
return this.elemNode.className = classes.join(' ');
|
|
};
|
|
|
|
return Component;
|
|
|
|
})();
|
|
|
|
Component.__tamia_cmpnt__ = true;
|
|
|
|
window.Component = Component;
|
|
|
|
}).call(this);
|