I'm practicing by writing utilities for myself; I prefer CSS3 transitions over jQuery animations whenever possible. This is intended to be used for transitioning multiple DOM elements who's relationship to one and other is only given by the context (ie. on clicking a menu icon, hide/show, relocate, resize, etc. multiple dynamic-content elements).
I'm seeking advice related to design, utility, proper use of JavaScript concepts, etc. i.e. does it make sense for this to exist, what needs are overlooked or problems left unresolved, etc.
Note: Ignore client-compatibility; assume well-configured modernizr is loaded.
function obj_merge(keys_obj, vals_obj) {
if (!is_object(keys_obj) || !is_object(vals_obj)) throw "keys_obj and vals_obj must both be javascript objects";
var merged_obj = {};
for (var key in keys_obj) merged_obj[key] = key in vals_obj ? vals_obj[key] : keys_obj[key];
for (var key in vals_obj) if ( !(key in merged_obj) ) merged_obj[key] = vals_obj[key];
return merged_obj;
}
function eCustom(eName, eProperties) {
var defaultProps = {"bubbles": true, "cancelable": false, "eventPhase": 0, "type": eName};
if (typeof(eProperties) == "object") {
for (var prop in eProperties) {
if (eProperties.hasOwnProperty(prop)) {
defaultProps[prop] = eProperties[prop];
}
}
}
return jQuery.Event(eName, defaultProps);
}
function is_object(obj) { return typeof obj === 'object' && obj != null;}
function is_array(obj) { return obj instanceof Array; }
function EffectChain(config) {
var effect_chain = Object();
effect_chain.defaults = {
interval:undefined,
context: undefined
};
effect_chain.step_complete = eCustom("TransitionerStepComplete");
effect_chain.steps = [];
effect_chain.step_template = {
target: false,
context: "body",
state: undefined,
interval: undefined,
next: undefined
}
effect_chain.exec_result = undefined;
effect_chain.init = function(config) {
$(this).on("TransitionerStepComplete", function(e, data) {this.exec_step(data.step_index, data.target)})
if (is_object(config) ) this.defaults = obj_merge(this.defaults, config);
for (var key in this.defaults) if (this.defaults[key]) this.step_template[key] = this.defaults[key];
return this;
}
effect_chain.add = function(step) {
if ( is_array(step) ) {
while (step.length > 0) this.add(step.shift());
return this;
}
step = obj_merge(this.step_template, step);
step.exec = function(self, target) {
if (!target) target = self.target;
var f = function() {
if ("attr" in self.state) {
for (var attr in self.state.attr) $(target, self.context).attr(attr, self.state.attr);
}
if ("add" in self.state) {
if ( !is_array(self.state.add) ) self.state.add = [self.state.add];
for (var i=0; i < self.state.add.length; i++) {
try { $(target).addClass(self.state.add[i](target)); }
catch(e) { $(target).addClass(self.state.add[i]); }
}
}
if ("remove" in self.state) {
if ( !is_array(self.state.remove) ) self.state.remove = [self.state.remove];
for (var i=0; i < self.state.remove.length; i++) {
try { $(target).removeClass(self.state.remove[i](target)); }
catch(e) { $(target).removeClass(self.state.remove[i]); }
}
}
};
$(target, self.context).each(f);
if ( self.next ) {
try { return self.next(target, self.context); } // test: next is a function
catch(e) {
try { // test: next is range or specific index of selected elements
try { var refer_el = $(self.next.selector, context); }
catch(e) { var refer_el = $(self.next.selector); }
try { return refer_el.slice(self.next.from, "to" in self.next ? self.next.to : -1) }
catch(e) { return refer_el[self.next.index]; }
} catch(e) {
return $(self.next); // test: next is a string selector
}
}
}
return;
}
this.steps.push(step);
return this;
};
effect_chain.exec_step = function(step_index, target) {
var self = this;
var step = this.steps[step_index]
var result = step.exec(step, target);
step_index++;
if (this.steps.length > step_index) {
setTimeout( function() {
$(self).trigger("TransitionerStepComplete", {step_index: step_index, target:result})
}, step.interval);
} else {
this.exec_result = result;
}
}
effect_chain.duration = function() {
var t = 0;
// sum all step intervals except last
for (var i=0; i < this.steps.length - 1; i++) t += this.steps[i].interval;
return t;
}
effect_chain.run = function(initial_target) { this.exec_step(0, initial_target); }
effect_chain.init(config);
return effect_chain;
}
EffectChain
objects would see enough reuse to warrant the amount of code that has to be written, but it at least demonstrates use: codepen.io/anon/pen/PqEBxm \$\endgroup\$