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));
|