364 lines
9.8 KiB
JavaScript
364 lines
9.8 KiB
JavaScript
// Tâmia © 2013 Artem Sapegin http://sapegin.me
|
||
// https://github.com/sapegin/tamia
|
||
// JS core
|
||
// jQuery and Modernizr aren’t required but very useful
|
||
|
||
/*jshint newcap:false*/
|
||
/*global DEBUG:true, Modernizr:false, console:false*/
|
||
|
||
/**
|
||
* Debug mode.
|
||
*
|
||
* You can use DEBUG global variable in your scripts to hide some code from minified production version of JavaScript.
|
||
*
|
||
* To make it work add to your Gruntfile:
|
||
*
|
||
* uglify: {
|
||
* options: {
|
||
* compress: {
|
||
* global_defs: {
|
||
* DEBUG: !!grunt.option('debug')
|
||
* }
|
||
* }
|
||
* },
|
||
* ...
|
||
* }
|
||
*
|
||
* Then if you run `grunt --debug` DEBUG variable will be true and false if you run just `grunt`.
|
||
*/
|
||
if (typeof window.DEBUG === 'undefined') window.DEBUG = true;
|
||
|
||
;(function(window, jQuery, Modernizr, undefined) {
|
||
'use strict';
|
||
|
||
// IE8+
|
||
if (!document.querySelectorAll) return;
|
||
|
||
// Namespace
|
||
var tamia = window.tamia = {};
|
||
|
||
|
||
if (DEBUG) {
|
||
// Debug logger
|
||
var addBadge = function(args, name) {
|
||
// Color console badge
|
||
// Based on https://github.com/jbail/lumberjack
|
||
var ua = navigator.userAgent.toLowerCase();
|
||
if (ua.indexOf('chrome') !== -1 || ua.indexOf('firefox') !== -1) {
|
||
var format = '%c %s %c ' + args.shift();
|
||
args.unshift(format, 'background:#aa759f; color:#fff', name, 'background:inherit; color:inherit');
|
||
}
|
||
else {
|
||
args[0] = name + ': ' + args[0];
|
||
}
|
||
return args;
|
||
};
|
||
var logger = function() {
|
||
var args = Array.prototype.slice.call(arguments);
|
||
var func = args.shift();
|
||
console[func].apply(console, addBadge(args, 'Tâmia'));
|
||
};
|
||
var log = tamia.log = logger.bind(null, 'log');
|
||
var warn = tamia.warn = logger.bind(null, 'warn');
|
||
|
||
// Check optional dependencies
|
||
if (!jQuery) warn('jQuery not found.');
|
||
if (!jQuery.Transitions) warn('jQuery Transition Events plugin (tamia/vendor/transition-events.js) not found.');
|
||
if (!Modernizr) warn('Modernizr not found.');
|
||
|
||
// Check required Modernizr features
|
||
$.each([
|
||
'csstransitions',
|
||
'cssgradients',
|
||
'flexbox',
|
||
'touch',
|
||
], function(idx, feature) {
|
||
if (!(feature in Modernizr)) warn('Modernizr should be built with "' + feature + '" feautre.');
|
||
});
|
||
}
|
||
|
||
|
||
var _containersCache;
|
||
var _components = {};
|
||
var _initializedAttribute = '_tamia-yep';
|
||
|
||
function _getContainers(parent) {
|
||
return (parent || document).querySelectorAll('[data-component]');
|
||
}
|
||
|
||
|
||
/**
|
||
* Initialize components.
|
||
*
|
||
* @param {Object} components Initializers for each component.
|
||
*
|
||
* Examples:
|
||
*
|
||
* <div data-component="pony"></div>
|
||
*
|
||
* tamia.initComponents({
|
||
* // New style component
|
||
* pony: Pony, // class Pony extends Component {...}
|
||
* // Plain initializer
|
||
* pony: function(elem) {
|
||
* // $(elem) === <div data-component="pony">
|
||
* },
|
||
* // Initialize jQuery plugins (plain initializer)
|
||
* jquerypony: function(elem) {
|
||
* $(elem).pluginmethod({option1: 'val1', options2: 'val2'});
|
||
* $(elem).pluginmethod2();
|
||
* },
|
||
* // Initialize jQuery plugins (shortcut)
|
||
* jquerypony: {
|
||
* pluginmethod: {option1: 'val1', options2: 'val2'},
|
||
* pluginmethod2: ['attr1', 'attr2', 'attr3'],
|
||
* pluginmethod3: null
|
||
* }
|
||
* }
|
||
*
|
||
* Caveats:
|
||
*
|
||
* 1. To initialize components inside container that was hidden or inside dynamically created container use
|
||
* init.tamia event: `$('.js-container').trigger('init.tamia');`
|
||
* 2. No components will be initialized twice. It’s safe to trigger init.tamia event multiple times: only new nodes
|
||
* or nodes that was hidden before will be affected.
|
||
*/
|
||
tamia.initComponents = function(components, parent) {
|
||
var containers;
|
||
if (parent === undefined) {
|
||
containers = _containersCache || (_containersCache = _getContainers());
|
||
}
|
||
else {
|
||
// Init all components inside DOM node
|
||
containers = _getContainers(parent);
|
||
components = _components;
|
||
}
|
||
|
||
// Init components
|
||
for (var containerIdx = 0, containerCnt = containers.length; containerIdx < containerCnt; containerIdx++) {
|
||
var container = containers[containerIdx];
|
||
var componentName = container.getAttribute('data-component');
|
||
var component = components[componentName];
|
||
if (!component || container.hasAttribute(_initializedAttribute)) continue;
|
||
|
||
var initialized = true;
|
||
if ('__tamia_cmpnt__' in component) {
|
||
// New style component
|
||
initialized = (new component(container)).initializable;
|
||
}
|
||
else if (typeof component === 'function') {
|
||
// Old style component
|
||
initialized = component(container);
|
||
}
|
||
else if (jQuery) {
|
||
// jQuery plugins shortcut
|
||
for (var method in component) {
|
||
var params = component[method];
|
||
var elem = jQuery(container);
|
||
if (DEBUG && !jQuery.isFunction(elem[method])) warn('jQuery method "%s" not found (used in "%s" component).', method, componentName);
|
||
if (jQuery.isArray(params)) {
|
||
elem[method].apply(elem, params);
|
||
}
|
||
else {
|
||
elem[method](params);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (initialized !== false) {
|
||
container.setAttribute(_initializedAttribute, 'yes');
|
||
}
|
||
}
|
||
|
||
// Add new components to all components array
|
||
for (var name in components) {
|
||
_components[name] = components[name];
|
||
}
|
||
};
|
||
|
||
if (jQuery) {
|
||
|
||
var _doc = jQuery(document);
|
||
var _hiddenClass = 'is-hidden';
|
||
var _transitionClass = 'is-transit';
|
||
var _appearedEvent = 'appeared.tamia';
|
||
var _disappearedEvent = 'disappeared.tamia';
|
||
var _fallbackTimeout = 1000;
|
||
|
||
/**
|
||
* Registers Tâmia events (eventname.tamia) on document.
|
||
*
|
||
* Example:
|
||
*
|
||
* // Registers enable.tamia event.
|
||
* tamia.registerEvents({
|
||
* enable: function(elem) {
|
||
* }
|
||
* });
|
||
*
|
||
* @param {Object} handlers Handlers list.
|
||
*/
|
||
tamia.registerEvents = function(handlers) {
|
||
var events = $.map(handlers, _tamiaze).join(' ');
|
||
_doc.on(events, function(event) {
|
||
if (DEBUG) log('Event "%s":', event.type, event.target);
|
||
handlers[event.type](event.target);
|
||
});
|
||
};
|
||
|
||
var _tamiaze = function (handler, name) {
|
||
return name + '.tamia';
|
||
};
|
||
|
||
|
||
/**
|
||
* Events
|
||
*/
|
||
var _handlers = {};
|
||
|
||
/**
|
||
* Init components inside any jQuery node.
|
||
*
|
||
* Examples:
|
||
*
|
||
* $(document).trigger('init.tamia');
|
||
* $('.js-container').trigger('init.tamia');
|
||
*/
|
||
_handlers.init = function(elem) {
|
||
tamia.initComponents(undefined, elem);
|
||
};
|
||
|
||
/**
|
||
* Show element with CSS transition.
|
||
*
|
||
* appeared.tamia event will be fired the moment transition ends.
|
||
*
|
||
* Example:
|
||
*
|
||
* .dialog
|
||
* transition: opacity .5s ease-in-out
|
||
* ...
|
||
* &.is-hidden
|
||
* opacity: 0
|
||
*
|
||
* <div class="dialog is-hidden js-dialog">...</div>
|
||
*
|
||
* $('.js-dialog').trigger('appear.tamia');
|
||
*/
|
||
_handlers.appear = function(elem) {
|
||
elem = $(elem);
|
||
if (Modernizr && Modernizr.csstransitions) {
|
||
if (elem.hasClass(_transitionClass) && !elem.hasClass(_hiddenClass)) return;
|
||
elem.addClass(_transitionClass);
|
||
setTimeout(function() {
|
||
elem.removeClass(_hiddenClass);
|
||
elem.afterTransition(function() {
|
||
elem.removeClass(_transitionClass);
|
||
elem.trigger(_appearedEvent);
|
||
});
|
||
}, 0);
|
||
}
|
||
else {
|
||
elem.removeClass(_hiddenClass);
|
||
elem.trigger(_appearedEvent);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Hide element with CSS transition.
|
||
*
|
||
* disappeared.tamia event will be fired the moment transition ends.
|
||
*
|
||
* Opposite of `appear.tamia` event.
|
||
*/
|
||
_handlers.disappear = function(elem) {
|
||
elem = $(elem);
|
||
if (Modernizr && Modernizr.csstransitions) {
|
||
if (elem.hasClass(_transitionClass) && elem.hasClass(_hiddenClass)) return;
|
||
elem.addClass(_transitionClass);
|
||
elem.addClass(_hiddenClass);
|
||
elem.afterTransition(function() {
|
||
elem.removeClass(_transitionClass);
|
||
elem.trigger(_disappearedEvent);
|
||
});
|
||
}
|
||
else {
|
||
elem.addClass(_hiddenClass);
|
||
elem.trigger(_disappearedEvent);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Toggles element’s visibility with CSS transition.
|
||
*
|
||
* See `appear.tamia` event for details.
|
||
*/
|
||
_handlers.toggle = function(elem) {
|
||
elem = $(elem);
|
||
if (elem.hasClass(_hiddenClass)) {
|
||
_handlers.appear(elem);
|
||
}
|
||
else {
|
||
_handlers.disappear(elem);
|
||
}
|
||
};
|
||
|
||
tamia.registerEvents(_handlers);
|
||
|
||
|
||
/**
|
||
* Controls.
|
||
*
|
||
* Fires jQuery event to specified element on click at this element.
|
||
*
|
||
* @param data-fire Event name.
|
||
* @param [data-target] Target element selector.
|
||
* @param [data-closest] Target element selector: search only through element ancestors.
|
||
* @param [data-attrs] Comma separated attributes list.
|
||
*
|
||
* Either of data-target or data-closest is required.
|
||
*
|
||
* Example:
|
||
*
|
||
* <span data-fire="slider-next" data-target=".portfolio" data-attrs="1,2,3">Next</span>
|
||
* <!-- $('.portfolio').trigger('slider-next', [1, 2, 3]); -->
|
||
*/
|
||
_doc.on('click', '[data-fire]', function(event) {
|
||
var elem = jQuery(event.currentTarget);
|
||
|
||
var data = elem.data();
|
||
if (DEBUG) if (!data.target && !data.closest) return log('You should define either data-target or data-closest on', elem[0]);
|
||
|
||
var target = data.target && jQuery(data.target) || elem.closest(data.closest);
|
||
if (DEBUG) if (!target.length) return log('Target element %s not found for', data.target || data.closest, elem[0]);
|
||
|
||
var attrs = data.attrs;
|
||
if (DEBUG) log('Fire "%s" with attrs [%s] on', data.fire, attrs || '', target);
|
||
target.trigger(data.fire, attrs ? attrs.split(/[;, ]/) : undefined);
|
||
|
||
event.preventDefault();
|
||
});
|
||
|
||
/**
|
||
* Grid helper.
|
||
*
|
||
* Example:
|
||
*
|
||
* <div data-component="grid"></div>
|
||
*/
|
||
if (DEBUG) tamia.initComponents({
|
||
grid: function(elem) {
|
||
elem = $(elem);
|
||
elem
|
||
.addClass('g-row')
|
||
.html(
|
||
new Array((elem.data('columns') || 12) + 1).join('<b class="g-debug-col" style="height:'+document.documentElement.scrollHeight+'px"></b>')
|
||
)
|
||
;
|
||
}
|
||
});
|
||
|
||
}
|
||
|
||
}(window, window.jQuery, window.Modernizr));
|