diff --git a/examples/example.css b/examples/example.css index a48d50394..fdcf09096 100755 --- a/examples/example.css +++ b/examples/example.css @@ -62,6 +62,14 @@ a:hover { } #example pre { background-color: #F0F0F0; +} +#example code { + background-color: #F0F0F0; + border: 1px solid #DDDDDD; + padding: 3px 5px; +} +#example pre code { + } #example p { margin: 10px 0px; diff --git a/examples/index.html b/examples/index.html index cc4010f35..a77b941a7 100755 --- a/examples/index.html +++ b/examples/index.html @@ -41,13 +41,13 @@

Regular Shape

-
+
-
+
-
+
@@ -120,7 +120,7 @@ Setting Name Default Value - Usage + Usage moduleName diff --git a/examples/ui/table.css b/examples/ui/table.css index b19bb6ba0..6853f7fa2 100755 --- a/examples/ui/table.css +++ b/examples/ui/table.css @@ -2,6 +2,7 @@ width: 100%; border: 1px solid #F0F0F0; border: 1px solid rgba(0, 0, 0, 0.1); + border-collapse: collapse; font-size: 12px; color: #555555; diff --git a/src/multiple.js b/src/multiple.js new file mode 100755 index 000000000..4f855385d --- /dev/null +++ b/src/multiple.js @@ -0,0 +1,270 @@ +/* ******************************************************************************************* + + Example Boilerplate - Multiple Instances + + Version 0.1 + + Author : Jack Lukic + Last revision : April 2013 + +********************************************************************************************* */ + +;(function ( $, window, document, undefined ) { + +$.fn.example = function(parameters) { + var + // store a reference to the module group, this can be useful to refer to other modules inside each module + $allModules = $(this), + + // extend settings to merge run time settings with defaults + settings = $.extend(true, {}, $.fn.example.settings, parameters), + + // define namespaces for modules + eventNamespace = '.' + settings.namespace, + moduleNamespace = 'module-' + settings.namespace, + + // preserve original arguments to determine if a method is being invoked + query = arguments[0], + methodInvoked = (typeof query == 'string'), + queryArguments = [].slice.call(arguments, 1), + invokedResponse + ; + + // iterate over all elements to initialize module + $allModules + .each(function() { + var + // cache selectors using selector definitions for access inside instance of module + $module = $(this), + $text = $module.find(settings.selector.text), + + // define private variables which can be used to maintain internal state + foo = false, + + // define variables used to track module state. In semantic modules default values are set using 'a || b' syntax + selector = $module.selector || '', + instance = $module.data(moduleNamespace), + element = this, + + // alias settings object for convenience and performance + namespace = settings.namespace, + error = settings.error, + className = settings.className, + + // you may also find it useful to alias your own settings + text = settings.text, + + module + ; + + // define the entire module + module = { + + // initialize attaches events and preserves each instance in html metadata + initialize: function() { + module.verbose('Initializing module for', element); + $module + .on('click' + eventNamespace, module.exampleBehavior) + ; + // the instance is just a copy of the module definition, we store it in metadata so we can use it outside of scope, but also define it for immediate use + instance = module; + $module + .data(moduleNamespace, instance) + ; + }, + + // destroy removes all events and the instance from metadata + destroy: function() { + module.verbose('Destroying previous module for', element); + $module + .removeData(moduleNamespace) + .off(eventNamespace) + ; + }, + + // selectors are cached so we need a method to manually refresh the cache + refresh: function() { + module.verbose('Refreshing selector cache for', element); + $module = $(element); + $text = $(this).find(settings.selector.text); + }, + + exampleBehavior: function(event) { + + }, + + // all of your module's specific behavior goes here. In this example our module can only get and set text of a child node + has: { + text: function(state) { + return (text[state] !== undefined); + } + }, + + set: { + text: function(state) { + if( module.has.text(state) ) { + $text + .text( text[state] ) + ; + } + } + }, + + // module settings can be read or set using this method + setting: function(name, value) { + if( $.isPlainObject(name) ) { + $.extend(true, settings, name); + } + else if(value === undefined) { + return settings[name]; + } + else { + settings[name] = value; + } + }, + + // verbose allows for additional data to be logged by the module which can assist in debugging + verbose: function() { + if(settings.verbose) { + module.debug.apply(this, arguments); + } + }, + + // debug pushes arguments to the console formatted as a debug statement + debug: function() { + var + output = [], + message = settings.moduleName + ': ' + arguments[0], + variables = [].slice.call( arguments, 1 ), + log = console.info || console.log || function(){} + ; + log = Function.prototype.bind.call(log, console); + if(settings.debug) { + output.push(message); + log.apply(console, output.concat(variables) ); + } + }, + + // error allows for the module to report named error messages + error: function() { + var + output = [], + errorMessage = settings.moduleName + ': ' + arguments[0], + variables = [].slice.call( arguments, 1 ), + log = console.warn || console.log || function(){} + ; + log = Function.prototype.bind.call(log, console); + if(settings.debug) { + output.push(errorMessage); + output.concat(variables); + log.apply(console, output.concat(variables) ); + } + }, + + // invoke is used to lookup and invoke a method or property by its dot notation string definition + invoke: function(query, passedArguments, context) { + var + maxDepth, + found + ; + passedArguments = passedArguments || queryArguments; + context = element || context; + // invoke iterates through the module instance looking for methods or properties that match the requested query + if(typeof query == 'string' && instance !== undefined) { + query = query.split('.'); + maxDepth = query.length - 1; + $.each(query, function(depth, value) { + if( $.isPlainObject( instance[value] ) && (depth != maxDepth) ) { + instance = instance[value]; + return true; + } + else if( instance[value] !== undefined ) { + found = instance[value]; + return true; + } + module.error(error.method); + return false; + }); + } + if ( $.isFunction( found ) ) { + module.verbose('Executing invoked function', found); + return found.apply(context, passedArguments); + } + // return retrieved variable or chain + return found || false; + } + }; + + // the module checks to see if you passed in a method name to call + if(methodInvoked) { + if(instance === undefined) { + module.initialize(); + } + invokedResponse = module.invoke(query); + } + // if you didn't pass in anything it can assume you are initializing the module + else { + if(instance !== undefined) { + module.destroy(); + } + module.initialize(); + } + }) + ; + + // if you called invoke, you may have a returned value which shoudl be returned, otherwise allow the call to chain + return (invokedResponse) + ? invokedResponse + : this + ; +}; + +// After you define your plugin, its necessary to include a settings object which specifies the defaults for your module +$.fn.example.settings = { + + // used in debug statements to refer to the module itself + moduleName : 'Todo Module', + + // whether debug content should be outputted to console + debug : true, + + // whether extra debug content should be outputted + verbose : true, + + // a unique identifier used to namespace events, and preserve the module instance + namespace : 'example', + + // callbacks are often useful to include in your settings object + onChange : function() {}, + + // you may also want to include settings specific to your module's function + text: { + hover: 'You are hovering me now', + click: 'You clicked on me' + }, + + // error messages returned by the module + error: { + side : 'You tried to switch to a side that does not exist.', + method : 'The method you called is not defined' + }, + + // class names which your module refers to + className : { + active : 'active' + }, + + // metadata stored by your module + metadata: { + + }, + + // selectors used by your module + selector : { + example : '.example' + } + +}; + + +})( jQuery, window , document ); diff --git a/src/shape.css b/src/shape.css index 5210a9d67..dc9058d61 100755 --- a/src/shape.css +++ b/src/shape.css @@ -54,34 +54,34 @@ /* css animation */ .shape.css.module { -webkit-transition: - all 0.5s ease-in-out; + all 0.6s ease-in-out; ; -moz-transition: - all 0.5s ease-in-out; + all 0.6s ease-in-out; ; -o-transition: - all 0.5s ease-in-out; + all 0.6s ease-in-out; ; -ms-transition: - all 0.5s ease-in-out; + all 0.6s ease-in-out; ; transition: - all 0.5s ease-in-out; + all 0.6s ease-in-out; ; } .shape.css.module .shape { - -webkit-transition: all 0.5s ease-in-out; - -moz-transition: all 0.5s ease-in-out; - -o-transition: all 0.5s ease-in-out; - -ms-transition: all 0.5s ease-in-out; - transition: all 0.5s ease-in-out; + -webkit-transition: all 0.6s ease-in-out; + -moz-transition: all 0.6s ease-in-out; + -o-transition: all 0.6s ease-in-out; + -ms-transition: all 0.6s ease-in-out; + transition: all 0.6s ease-in-out; } .shape.css.module .side { - -webkit-transition: opacity 0.5s ease-out; - -moz-transition: opacity 0.5s ease-out; - -o-transition: opacity 0.5s ease-out; - -ms-transition: opacity 0.5s ease-out; - transition: opacity 0.5s ease-out; + -webkit-transition: opacity 0.6s ease-out; + -moz-transition: opacity 0.6s ease-out; + -o-transition: opacity 0.6s ease-out; + -ms-transition: opacity 0.6s ease-out; + transition: opacity 0.6s ease-out; } /* Active */ diff --git a/src/shape.js b/src/shape.js index a6980c13e..7b959924c 100755 --- a/src/shape.js +++ b/src/shape.js @@ -14,12 +14,17 @@ $.fn.shape = function(parameters) { var $allModules = $(this), - + settings = $.extend(true, {}, $.fn.shape.settings, parameters), + // define namespaces for modules + eventNamespace = '.' + settings.namespace, + moduleNamespace = 'module-' + settings.namespace, + // allow methods to be queried directly query = arguments[0], queryArguments = [].slice.call(arguments, 1), + methodInvoked = (typeof query == 'string'), invokedResponse ; @@ -34,15 +39,12 @@ $.fn.shape = function(parameters) { // private variables $activeSide, $nextSide, - endTransition = 'transitionend msTransitionEnd oTransitionEnd', // standard module selector = $module.selector || '', element = this, - moduleName = 'module-' + settings.namespace, - instance = $module.data(moduleName), - methodInvoked = (typeof query == 'string'), + instance = $module.data(moduleNamespace), // internal aliases namespace = settings.namespace, @@ -59,15 +61,15 @@ $.fn.shape = function(parameters) { module.set.defaultSide(); instance = module; $module - .data(moduleName, instance) + .data(moduleNamespace, instance) ; }, destroy: function() { module.verbose('Destroying previous module for', element); $module - .removeData(moduleName) - .off('.' + namespace) + .removeData(moduleNamespace) + .off(eventNamespace) ; }, @@ -540,7 +542,6 @@ $.fn.shape = function(parameters) { if(typeof query == 'string' && instance !== undefined) { query = query.split('.'); maxDepth = query.length - 1; - console.log('found is ', query, instance, context, passedArguments, found); $.each(query, function(depth, value) { if( $.isPlainObject( instance[value] ) && (depth != maxDepth) ) { instance = instance[value]; @@ -559,12 +560,15 @@ $.fn.shape = function(parameters) { return found.apply(context, passedArguments); } // return retrieved variable or chain - return found; + return found || false; } }; // check for invoking internal method if(methodInvoked) { + if(instance === undefined) { + module.initialize(); + } invokedResponse = module.invoke(query); } // otherwise initialize @@ -577,7 +581,7 @@ $.fn.shape = function(parameters) { }) ; // chain or return queried method - return (invokedResponse !== undefined) + return (invokedResponse) ? invokedResponse : this ; diff --git a/src/single.js b/src/single.js new file mode 100755 index 000000000..a6980c13e --- /dev/null +++ b/src/single.js @@ -0,0 +1,634 @@ +/* ******************************************************************************************* + + Shape - A 3D Animation Plugin + Version 0.1 + (built using Semantic module spec) + + Author : Jack Lukic + Last revision : April 2013 + +********************************************************************************************* */ + +;(function ( $, window, document, undefined ) { + +$.fn.shape = function(parameters) { + var + $allModules = $(this), + + settings = $.extend(true, {}, $.fn.shape.settings, parameters), + + // allow methods to be queried directly + query = arguments[0], + queryArguments = [].slice.call(arguments, 1), + invokedResponse + ; + + $allModules + .each(function() { + var + // selector cache + $module = $(this), + $shape = $module.find(settings.selector.shape), + $side = $module.find(settings.selector.side), + + // private variables + $activeSide, + $nextSide, + + endTransition = 'transitionend msTransitionEnd oTransitionEnd', + + // standard module + selector = $module.selector || '', + element = this, + moduleName = 'module-' + settings.namespace, + instance = $module.data(moduleName), + methodInvoked = (typeof query == 'string'), + + // internal aliases + namespace = settings.namespace, + error = settings.error, + className = settings.className, + + module + ; + + module = { + + initialize: function() { + module.verbose('Initializing module for', element); + module.set.defaultSide(); + instance = module; + $module + .data(moduleName, instance) + ; + }, + + destroy: function() { + module.verbose('Destroying previous module for', element); + $module + .removeData(moduleName) + .off('.' + namespace) + ; + }, + + refresh: function() { + module.verbose('Refreshing selector cache for', element); + $module = $(element); + $shape = $(this).find(settings.selector.shape); + $side = $(this).find(settings.selector.side); + }, + + repaint: function() { + module.verbose('Forcing repaint event'); + var + fakeAssignment = $shape.get(0).offsetWidth + ; + }, + + animate: function(propertyObject, callback) { + module.verbose('Animating box with properties', propertyObject); + callback = callback || function() { + module.reset(); + module.set.active(); + module.queue.perform(); + $.proxy(settings.onChange, $nextSide)(); + }; + if(settings.useCSS) { + module.verbose('Starting CSS animation'); + $module + .addClass(className.animating) + ; + module.set.stageSize(); + module.repaint(); + $module + .addClass(className.css) + ; + $activeSide + .addClass(className.hidden) + ; + $shape + .css(propertyObject) + .one(endTransition, callback) + ; + } + else { + // not yet supported until .animate() is extended to allow RotateX/Y + module.verbose('Starting javascript animation'); + $module + .addClass(className.animating) + .removeClass(className.css) + ; + module.set.stageSize(); + module.repaint(); + $activeSide + .animate({ + opacity: 0 + }, settings.duration, settings.easing) + ; + $shape + .animate(propertyObject, settings.duration, settings.easing, callback) + ; + } + }, + + queue: function(method) { + module.debug('Queueing animation of', method); + $shape + .one(endTransition, function() { + $module.shape(method); + }) + ; + }, + + reset: function() { + module.verbose('Animating states reset'); + $module + .removeClass(className.css) + .removeClass(className.animating) + .removeAttr('style') + ; + $shape + .removeAttr('style') + ; + $side + .removeAttr('style') + .removeClass(className.hidden) + ; + $nextSide + .removeClass(className.animating) + .removeAttr('style') + ; + }, + + is: { + + animating: function() { + return $module.hasClass(className.animating); + } + + }, + + get: { + + nextSide: function() { + return ( $activeSide.next(settings.selector.side).size() > 0 ) + ? $activeSide.next(settings.selector.side) + : $module.find(settings.selector.side).first() + ; + } + + }, + + set: { + + defaultSide: function() { + $activeSide = $module.find('.' + settings.className.active); + $nextSide = ( $activeSide.next(settings.selector.side).size() > 0 ) + ? $activeSide.next(settings.selector.side) + : $module.find(settings.selector.side).first() + ; + module.verbose('Active side set to', $activeSide); + module.verbose('Next side set to', $nextSide); + }, + + stageSize: function() { + var + stage = { + width : $nextSide.outerWidth(), + height : $nextSide.outerHeight() + } + ; + module.verbose('Resizing stage to fit new content', stage); + $module + .css({ + width : stage.width, + height : stage.height + }) + ; + }, + + nextSide: function(selector) { + $nextSide = $module.find(selector); + if($nextSide.size() === 0) { + module.error(error.side); + } + module.verbose('Next side manually set to', $nextSide); + }, + + active: function() { + module.verbose('Setting new side to active', $nextSide); + $side + .removeClass(className.active) + ; + $nextSide + .addClass(className.active) + ; + module.set.defaultSide(); + } + }, + + flip: { + + up: function() { + module.debug('Flipping up', $nextSide); + if( !module.is.animating() ) { + module.stage.above(); + module.animate( module.getTransform.up() ); + } + else { + module.queue('flip.up'); + } + }, + + down: function() { + module.debug('Flipping down', $nextSide); + if( !module.is.animating() ) { + module.stage.below(); + module.animate( module.getTransform.down() ); + } + else { + module.queue('flip.down'); + } + }, + + left: function() { + module.debug('Flipping left', $nextSide); + if( !module.is.animating() ) { + module.stage.left(); + module.animate(module.getTransform.left() ); + } + else { + module.queue('flip.left'); + } + }, + + right: function() { + module.debug('Flipping right', $nextSide); + if( !module.is.animating() ) { + module.stage.right(); + module.animate(module.getTransform.right() ); + } + else { + module.queue('flip.right'); + } + }, + + over: function() { + module.debug('Flipping over', $nextSide); + if( !module.is.animating() ) { + module.stage.behind(); + module.animate(module.getTransform.behind() ); + } + else { + module.queue('flip.over'); + } + } + + }, + + getTransform: { + + up: function() { + var + translate = { + y: -(($activeSide.outerHeight() - $nextSide.outerHeight()) / 2), + z: -($activeSide.outerHeight() / 2) + } + ; + return { + transform: 'translateY(' + translate.y + 'px) translateZ('+ translate.z + 'px) rotateX(-90deg)' + }; + }, + + down: function() { + var + translate = { + y: -(($activeSide.outerHeight() - $nextSide.outerHeight()) / 2), + z: -($activeSide.outerHeight() / 2) + } + ; + return { + transform: 'translateY(' + translate.y + 'px) translateZ('+ translate.z + 'px) rotateX(90deg)' + }; + }, + + left: function() { + var + translate = { + x : -(($activeSide.outerWidth() - $nextSide.outerWidth()) / 2), + z : -($activeSide.outerWidth() / 2) + } + ; + return { + transform: 'translateX(' + translate.x + 'px) translateZ(' + translate.z + 'px) rotateY(90deg)' + }; + }, + + right: function() { + var + translate = { + x : -(($activeSide.outerWidth() - $nextSide.outerWidth()) / 2), + z : -($activeSide.outerWidth() / 2) + } + ; + return { + transform: 'translateX(' + translate.x + 'px) translateZ(' + translate.z + 'px) rotateY(-90deg)' + }; + }, + + behind: function() { + var + translate = { + x : -(($activeSide.outerWidth() - $nextSide.outerWidth()) / 2) + } + ; + return { + transform: 'translateX(' + translate.x + 'px) rotateY(180deg)' + }; + } + + }, + + stage: { + + above: function() { + var + box = { + origin : (($activeSide.outerHeight() - $nextSide.outerHeight()) / 2), + depth : { + active : ($nextSide.outerHeight() / 2), + next : ($activeSide.outerHeight() / 2) + } + } + ; + module.verbose('Setting the initial animation position as above', $nextSide, box); + $activeSide + .css({ + 'transform' : 'rotateY(0deg) translateZ(' + box.depth.active + 'px)' + }) + ; + $nextSide + .addClass(className.animating) + .css({ + 'display' : 'block', + 'top' : box.origin + 'px', + 'transform' : 'rotateX(90deg) translateZ(' + box.depth.next + 'px)' + }) + ; + }, + + below: function() { + var + box = { + origin : (($activeSide.outerHeight() - $nextSide.outerHeight()) / 2), + depth : { + active : ($nextSide.outerHeight() / 2), + next : ($activeSide.outerHeight() / 2) + } + } + ; + module.verbose('Setting the initial animation position as below', $nextSide, box); + $activeSide + .css({ + 'transform' : 'rotateY(0deg) translateZ(' + box.depth.active + 'px)' + }) + ; + $nextSide + .addClass(className.animating) + .css({ + 'display' : 'block', + 'top' : box.origin + 'px', + 'transform' : 'rotateX(-90deg) translateZ(' + box.depth.next + 'px)' + }) + ; + }, + + left: function() { + var + box = { + origin : ( ( $activeSide.outerWidth() - $nextSide.outerWidth() ) / 2), + depth : { + active : ($nextSide.outerWidth() / 2), + next : ($activeSide.outerWidth() / 2) + } + } + ; + module.verbose('Setting the initial animation position as left', $nextSide, box); + $activeSide + .css({ + 'transform' : 'rotateY(0deg) translateZ(' + box.depth.active + 'px)' + }) + ; + $nextSide + .addClass(className.animating) + .css({ + 'display' : 'block', + 'left' : box.origin + 'px', + 'transform' : 'rotateY(-90deg) translateZ(' + box.depth.next + 'px)' + }) + ; + }, + + right: function() { + var + box = { + origin : ( ( $activeSide.outerWidth() - $nextSide.outerWidth() ) / 2), + depth : { + active : ($nextSide.outerWidth() / 2), + next : ($activeSide.outerWidth() / 2) + } + } + ; + module.verbose('Setting the initial animation position as left', $nextSide, box); + $activeSide + .css({ + 'transform' : 'rotateY(0deg) translateZ(' + box.depth.active + 'px)' + }) + ; + $nextSide + .addClass(className.animating) + .css({ + 'display' : 'block', + 'left' : box.origin + 'px', + 'transform' : 'rotateY(90deg) translateZ(' + box.depth.next + 'px)' + }) + ; + }, + + behind: function() { + var + box = { + origin : ( ( $activeSide.outerWidth() - $nextSide.outerWidth() ) / 2), + depth : { + active : ($nextSide.outerWidth() / 2), + next : ($activeSide.outerWidth() / 2) + } + } + ; + module.verbose('Setting the initial animation position as behind', $nextSide, box); + $activeSide + .css({ + 'transform' : 'rotateY(0deg)' + }) + ; + $nextSide + .addClass(className.animating) + .css({ + 'display' : 'block', + 'left' : box.origin + 'px', + 'transform' : 'rotateY(-180deg)' + }) + ; + } + }, + + /* standard module */ + setting: function(name, value) { + if( $.isPlainObject(name) ) { + $.extend(true, settings, name); + } + else if(value === undefined) { + return settings[name]; + } + else { + settings[name] = value; + } + }, + + verbose: function() { + if(settings.verbose) { + module.debug.apply(this, arguments); + } + }, + + debug: function() { + var + output = [], + message = settings.moduleName + ': ' + arguments[0], + variables = [].slice.call( arguments, 1 ), + log = console.info || console.log || function(){} + ; + log = Function.prototype.bind.call(log, console); + if(settings.debug) { + output.push(message); + log.apply(console, output.concat(variables) ); + } + }, + + error: function() { + var + output = [], + errorMessage = settings.moduleName + ': ' + arguments[0], + variables = [].slice.call( arguments, 1 ), + log = console.warn || console.log || function(){} + ; + log = Function.prototype.bind.call(log, console); + if(settings.debug) { + output.push(errorMessage); + output.concat(variables); + log.apply(console, output.concat(variables) ); + } + }, + + invoke: function(query, passedArguments, context) { + var + maxDepth, + found + ; + passedArguments = passedArguments || queryArguments || [].slice.call( arguments, 2 ); + context = element || context; + if(typeof query == 'string' && instance !== undefined) { + query = query.split('.'); + maxDepth = query.length - 1; + console.log('found is ', query, instance, context, passedArguments, found); + $.each(query, function(depth, value) { + if( $.isPlainObject( instance[value] ) && (depth != maxDepth) ) { + instance = instance[value]; + return true; + } + else if( instance[value] !== undefined ) { + found = instance[value]; + return true; + } + module.error(error.method); + return false; + }); + } + if ( $.isFunction( found ) ) { + module.verbose('Executing invoked function', found); + return found.apply(context, passedArguments); + } + // return retrieved variable or chain + return found; + } + }; + + // check for invoking internal method + if(methodInvoked) { + invokedResponse = module.invoke(query); + } + // otherwise initialize + else { + if(instance !== undefined) { + module.destroy(); + } + module.initialize(); + } + }) + ; + // chain or return queried method + return (invokedResponse !== undefined) + ? invokedResponse + : this + ; +}; + +$.fn.shape.settings = { + + // module info + moduleName : 'Shape Module', + + // debug content outputted to console + debug : true, + + // verbose debug output + verbose : true, + + // event namespace + namespace : 'shape', + + // callback occurs on side change + beforeChange : function() {}, + onChange : function() {}, + + // use css animation (currently only true is supported) + useCSS : true, + + // animation duration (useful only with future js animations) + duration : 1000, + easing : 'easeInOutQuad', + + // possible errors + error: { + side : 'You tried to switch to a side that does not exist.', + method : 'The method you called is not defined' + }, + + // classnames used + className : { + css : 'css', + animating : 'animating', + hidden : 'hidden', + active : 'active' + }, + + // selectors used + selector : { + shape : '.shape', + side : '.side' + } + +}; + + +})( jQuery, window , document );