From 3afa2106ccb5d9fb171f400e683730d76dbe55e8 Mon Sep 17 00:00:00 2001 From: David DeSandro Date: Sat, 30 Oct 2010 18:39:41 -0400 Subject: [PATCH] Move all other methods into widget script --- src/jquery.molequul-widget.js | 521 +++++++++++++++++++++++++++++++++- 1 file changed, 520 insertions(+), 1 deletion(-) diff --git a/src/jquery.molequul-widget.js b/src/jquery.molequul-widget.js index 2100bf0..6187675 100644 --- a/src/jquery.molequul-widget.js +++ b/src/jquery.molequul-widget.js @@ -72,7 +72,7 @@ // need to get atoms this.atoms.$all = this.$elem.children().molequul('_filterFind', this.options.itemSelector ); - console.log( 'all atoms', this.atoms.$all.length ) + // console.log( 'all atoms', this.atoms.$all.length ) this.$elem.css({ overflow : 'hidden', @@ -169,6 +169,525 @@ }, + + isNewProp : function( property, props ) { + if ( !props.initialized ) { + return true; + } + var previousProp = props.prevOpts[ property ]; + return ( props.opts[ property ] !== previousProp ); + }, + + // ====================== Adding ====================== + + addSortData : function( props ) { + return this.each(function(){ + var $this = $(this), + sortData = {}, + getSortData = props.opts.getSortData, + key; + // get value for sort data based on fn( $elem ) passed in + for ( key in getSortData ) { + sortData[ key ] = getSortData[ key ]( $this ); + } + // apply sort data to $element + $this.data( 'molequul-sort-data', sortData ); + // increment element count + props.elemCount ++; + }); + }, + + setupAtoms : function( props ) { + // base style for atoms + var atomStyle = { position: 'absolute' }; + if ( props.usingTransforms ) { + atomStyle.left = 0; + atomStyle.top = 0; + } + + // add sort data to each elem + return this.molequul( 'addSortData', props ).css( atomStyle ); + }, + + // ====================== Filtering ====================== + + filter : function( $atoms ) { + var props = this.data('molequul'), + filter = props.opts.filter === '' ? '*' : props.opts.filter; + + if ( !filter ) { + props.atoms.$filtered = $atoms; + } else { + var hiddenClass = props.opts.hiddenClass, + hiddenSelector = '.' + hiddenClass, + $visibleAtoms = $atoms.not( hiddenSelector ), + $hiddenAtoms = $atoms.filter( hiddenSelector ), + $atomsToShow = $hiddenAtoms; + + props.atoms.$filtered = $atoms.filter( filter ); + + if ( filter !== '*' ) { + $atomsToShow = $hiddenAtoms.filter( filter ); + + var $atomsToHide = $visibleAtoms.not( filter ).toggleClass( hiddenClass ); + $atomsToHide.addClass( hiddenClass ); + props.styleQueue.push({ $el: $atomsToHide, style: props.opts.hiddenStyle }); + } + + props.styleQueue.push({ $el: $atomsToShow, style: props.opts.visibleStyle }); + $atomsToShow.removeClass( hiddenClass ); + } + + return this; + }, + + // ====================== Sorting ====================== + + + getSortFn : function( sortBy, sortDir ) { + var getSorter = function( elem ) { + return $(elem).data('molequul-sort-data')[ sortBy ]; + }; + return function( alpha, beta ) { + var a = getSorter( alpha ), + b = getSorter( beta ); + return ( ( a > b ) ? 1 : ( a < b ) ? -1 : 0 ) * sortDir; + }; + }, + + randomSortFn : function() { + return Math.random() > 5 ? 1 : -1; + }, + + // used on all the filtered atoms, $atoms.filtered + sort : function( props ) { + + var sortFn = props.opts.sortBy === 'random' ? $.molequul.randomSortFn : + $.molequul.getSortFn( props.opts.sortBy, props.opts.sortDir ); + + props.atoms.$filtered.sort( sortFn ); + + return this; + }, + + + // ====================== Layout ====================== + + + translate : function( x, y ) { + return { translate : [ x, y ] }; + }, + + positionAbs : function( x, y ) { + return { left: x, top: y }; + }, + + pushPosition : function( x, y, props ) { + var position = props.positionFn( x, y ); + props.styleQueue.push({ $el: this, style: position }); + return this; + }, + + // ====================== masonry ====================== + + placeBrick : function( setCount, setY, props ) { + // here, `this` refers to a child element or "brick" + // get the minimum Y value from the columns + var minimumY = Math.min.apply( Math, setY ), + setHeight = minimumY + this.outerHeight(true), + i = setY.length, + shortCol = i, + setSpan = props.colCount + 1 - i, + x, y ; + // Which column has the minY value, closest to the left + while (i--) { + if ( setY[i] === minimumY ) { + shortCol = i; + } + } + + // position the brick + x = props.colW * shortCol + props.posLeft; + y = minimumY; + this.molequul( 'pushPosition', x, y, props ); + + // apply setHeight to necessary columns + for ( i=0; i < setSpan; i++ ) { + props.colYs[ shortCol + i ] = setHeight; + } + + return this; + }, + + masonrySingleColumn : function( props ) { + return this.each(function(){ + $(this).molequul( 'placeBrick', props.colCount, props.colYs, props ); + }); + }, + + + masonryMultiColumn : function( props ) { + return this.each(function(){ + var $this = $(this), + //how many columns does this brick span + colSpan = Math.ceil( $this.outerWidth(true) / props.colW ); + colSpan = Math.min( colSpan, props.colCount ); + + if ( colSpan === 1 ) { + // if brick spans only one column, just like singleMode + $this.molequul( 'placeBrick', props.colCount, props.colYs, props ); + } else { + // brick spans more than one column + // how many different places could this brick fit horizontally + var groupCount = props.colCount + 1 - colSpan, + groupY = [], + groupColY; + + // for each group potential horizontal position + for ( var i=0; i < groupCount; i++ ) { + // make an array of colY values for that one group + groupColY = props.colYs.slice( i, i+colSpan ); + // and get the max value of the array + groupY[i] = Math.max.apply( Math, groupColY ); + } + + $this.molequul( 'placeBrick', groupCount, groupY, props ); + } + }); + }, + + getMasonryColCount : function( props ) { + props.colW = props.opts.columnWidth || props.atoms.$all.outerWidth(true); + + // if colW == 0, back out before divide by zero + if ( !props.colW ) { + window.console && console.error('Column width calculated to be zero. Stopping Molequul plugin before divide by zero. Check that the width of first child inside the molequul container is not zero.'); + return this; + } + props.width = this.width(); + props.colCount = Math.floor( props.width / props.colW ) ; + props.colCount = Math.max( props.colCount, 1 ); + return this; + }, + + masonryResetLayoutProps : function( props ) { + + var i = props.colCount; + props.colYs = []; + while (i--) { + props.colYs.push( props.posTop ); + } + return this; + }, + + + + masonryResize : function( props ) { + var prevColCount = props.colCount; + // get updated colCount + this.molequul( 'getMasonryColCount', props ); + if ( props.colCount !== prevColCount ) { + // if column count has changed, do a new column cound + this.molequul( 'reLayout', props ); + } + + return this; + }, + + masonryMeasureContainerHeight : function( props ) { + props.containerHeight = Math.max.apply( Math, props.colYs ) - props.posTop; + return this; + }, + + masonrySetup : function( props ) { + this.molequul('getMasonryColCount', props ); + return this; + }, + + + + // ====================== ClearFloat ====================== + + clearFloat : function( props ) { + return this.each( function() { + var $this = $(this), + atomW = $this.outerWidth(true), + atomH = $this.outerHeight(true), + x, y; + + if ( props.clearFloat.x !== 0 && atomW + props.clearFloat.x > props.width ) { + // if this element cannot fit in the current row + props.clearFloat.x = 0; + props.clearFloat.y = props.clearFloat.height; + } + + // position the atom + x = props.clearFloat.x + props.posLeft; + y = props.clearFloat.y + props.posTop; + $this.molequul( 'pushPosition', x, y, props ); + + props.clearFloat.height = Math.max( props.clearFloat.y + atomH, props.clearFloat.height ); + props.clearFloat.x += atomW; + + }); + }, + + clearFloatSetup : function( props ) { + props.width = this.width(); + return this; + }, + + clearFloatResetLayoutProps : function( props ) { + props.clearFloat = { + x : 0, + y : 0, + height : 0 + }; + return this; + }, + + clearFloatMeasureContainerHeight : function ( props ) { + props.containerHeight = props.clearFloat.height; + return this; + }, + + clearFloatResize : function( props ) { + props.width = this.width(); + return this.molequul( 'reLayout', props ); + }, + + + // ====================== General Layout ====================== + + + // used on collection of atoms (should be filtered, and sorted before ) + // accepts atoms-to-be-laid-out to start with + layout : function( $elems, callback ) { + + var props = this.data('molequul'), + layoutMode = props.opts.layoutMode, + layoutMethod = layoutMode; + + // layout logic + if ( layoutMethod === 'masonry' ) { + layoutMethod = props.opts.masonrySingleMode ? 'masonrySingleColumn' : 'masonryMultiColumn'; + } + + $elems.molequul( layoutMethod, props ); + + // set the height of the container to the tallest column + this.molequul( layoutMode + 'MeasureContainerHeight', props ); + var containerStyle = { height: props.containerHeight }; + + + + props.styleQueue.push({ $el: this, style: containerStyle }); + + + + // are we animating the layout arrangement? + // use plugin-ish syntax for css or animate + var styleFn = ( props.applyStyleFnName === 'animate' && !props.initialized ) ? + 'css' : props.applyStyleFnName, + animOpts = props.opts.animationOptions; + + + // process styleQueue + $.each( props.styleQueue, function( i, obj ){ + // have to extend animation to play nice with jQuery + obj.$el[ styleFn ]( obj.style, $.extend( {}, animOpts ) ); + }); + + + + // clear out queue for next time + props.styleQueue = []; + + // provide $elems as context for the callback + if ( callback ) { + callback.call( $elems ); + } + + return this; + }, + + + resize : function() { + var props = this.data('molequul'); + + return this.molequul( props.opts.layoutMode + 'Resize', props ); + }, + + + reLayout : function( props ) { + props = props || this.data('molequul'); + props.initialized = true; + return this + .molequul( props.opts.layoutMode + 'ResetLayoutProps', props ) + .molequul( 'layout', props.atoms.$filtered ); + }, + + // ====================== Setup and Init ====================== + + // only run though on initial init + setup : function( props ) { + + props.atoms = {}; + props.isNew = {}; + props.styleQueue = []; + props.elemCount = 0; + // need to get atoms + props.atoms.$all = props.opts.selector ? + this.find( props.opts.selector ) : + this.children(); + + this.css({ + overflow : 'hidden', + position : 'relative' + }); + + var jQueryAnimation = false; + + // get applyStyleFnName + switch ( props.opts.animationEngine.toLowerCase().replace( /[ _\-]/g, '') ) { + case 'none' : + props.applyStyleFnName = 'css'; + break; + case 'jquery' : + props.applyStyleFnName = 'animate'; + jQueryAnimation = true; + break; + case 'bestavailable' : + default : + props.applyStyleFnName = Modernizr.csstransitions ? 'css' : 'animate'; + } + + props.usingTransforms = Modernizr.csstransforms && Modernizr.csstransitions && !jQueryAnimation; + + props.positionFn = props.usingTransforms ? $.molequul.translate : $.molequul.positionAbs; + + // sorting + var originalOrderSorter = { + 'original-order' : function( $elem ) { + return props.elemCount; + } + }; + props.opts.getSortData = $.extend( originalOrderSorter, props.opts.getSortData ); + + props.atoms.$all.molequul( 'setupAtoms', props ); + + + // get top left position of where the bricks should be + var $cursor = $( document.createElement('div') ); + this.prepend( $cursor ); + props.posTop = Math.round( $cursor.position().top ); + props.posLeft = Math.round( $cursor.position().left ); + $cursor.remove(); + + // add molequul class first time around + var $container = this; + setTimeout(function(){ + $container.addClass( props.opts.containerClass ); + }, 1 ); + + // do any layout-specific setup + this.molequul( props.opts.layoutMode + 'Setup', props ); + + // save data + this.data( 'molequul', props ); + + return this; + }, + + watchedProps : [ 'filter', 'sortBy', 'sortDir', 'layoutMode' ], + + init : function( options, callback ) { + + return this.each(function() { + + var $this = $(this), + data = $this.data('molequul'), + props = data || {}; + + // checks if molquul has been called before on this object + props.initialized = !!data; + + props.prevOpts = props.initialized ? data.opts : {}; + + props.opts = $.extend( + {}, + $.molequul.defaults, + props.prevOpts, + options + ); + + if ( !props.initialized ) { + $this.molequul( 'setup', props ); + } + + // check if watched properties are new + $.each( $.molequul.watchedProps, function( i, propName ){ + props.isNew[ propName ] = $.molequul.isNewProp( propName, props ); + }); + + if ( props.isNew.layoutMode ) { + $this.molequul( props.opts.layoutMode + 'Setup', props ); + } + + if ( props.isNew.filter || props.appending ) { + $this.molequul( 'filter', props.atoms.$all ); + } + + if ( props.isNew.filter || props.isNew.sortBy || props.isNew.sortDir || props.appending ) { + $this.molequul( 'sort', props ); + } + + $this.molequul( props.opts.layoutMode + 'ResetLayoutProps', props ) + .molequul( 'layout', props.atoms.$filtered, callback ); + + + // binding window resizing + if ( props.opts.resizeable ) { + $(window).bind('smartresize.molequul', function() { $this.molequul( 'resize' ); } ); + } else if ( !props.opts.resizeable && !!props.prevOpts.resizeable ) { + $(window).unbind('smartresize.molequul'); + } + + // reset this prop for next time + props.appending = false; + + // set all data so we can retrieve it for appended appendedContent + // or anyone else's crazy jquery fun + $this.data( 'molequul', props ); + + }); + + }, + + // ====================== Convenience methods ====================== + + // adds a jQuery object of items to a molequul container + add : function( $content ) { + var props = this.data('molequul'), + $newAtoms = props.opts.selector ? $content.filter( props.opts.selector ) : $content; + $newAtoms.molequul( 'setupAtoms', props ) + // add new atoms to atoms pools + props.atoms.$all = props.atoms.$all.add( $newAtoms ); + props.atoms.$filtered = props.atoms.$filtered.add( $newAtoms ); + props.appending = true; + return this; + }, + + // convienence method for adding elements properly to any layout + insert : function( $content ) { + return this.append( $content ).molequul( 'add', $content ).molequul('init'); + }, + + // convienence method for working with Infinite Scroll + appended : function( $content ) { + return this.molequul( 'add', $content ).molequul( 'layout', $content ); + } + // publicFn: function(){ // notice no underscore // return "public method";