Browse Source

Merging widget version into master

pull/14/head
David DeSandro 14 years ago
parent
commit
aa36f779a7
  1. 564
      src/jquery.molequul-widget.js
  2. 617
      src/jquery.molequul.js

564
src/jquery.molequul-widget.js

@ -1,564 +0,0 @@
(function( $, undefined ) {
// our "Widget" object constructor
var Molequul = function( options, element ){
this.element = $( element );
this._create( options );
this._init();
};
Molequul.prototype = {
options : {
columnWidth : 150,
resizeable: true,
layoutMode : 'masonry',
masonrySingleMode : false,
containerClass : 'molequul',
hiddenClass : 'molequul-hidden',
hiddenStyle : Modernizr.csstransforms && !$.browser.opera ?
{ opacity : 0, scale : 0.001 } :
{ opacity : 0 },
visibleStyle : Modernizr.csstransforms && !$.browser.opera ?
{ opacity : 1, scale : 1 } :
{ opacity : 1 },
animationEngine : 'best-available',
animationOptions: {
queue: false
},
sortBy : 'original-order',
sortDir : 'asc'
},
_filterFind: function( $elems, selector ) {
return selector ? $elems.filter( selector ).add( $elems.find( selector ) ) : $elems;
},
// sets up widget
_create : function( options ) {
this.options = $.extend( true, {}, this.options, options );
this.isNew = {};
this.styleQueue = [];
this.elemCount = 0;
// need to get atoms
this.$allAtoms = this._filterFind( this.element.children(), this.options.itemSelector );
// console.log( 'all atoms', this.$allAtoms.length )
this.element.css({
overflow : 'hidden',
position : 'relative'
});
var jQueryAnimation = false;
// get applyStyleFnName
switch ( this.options.animationEngine.toLowerCase().replace( /[ _\-]/g, '') ) {
case 'none' :
this.applyStyleFnName = 'css';
break;
case 'jquery' :
this.applyStyleFnName = 'animate';
jQueryAnimation = true;
break;
case 'bestavailable' :
default :
this.applyStyleFnName = Modernizr.csstransitions ? 'css' : 'animate';
}
this.usingTransforms = Modernizr.csstransforms && Modernizr.csstransitions && !jQueryAnimation;
this.positionFn = this.usingTransforms ? this._translate : this._positionAbs;
// sorting
var originalOrderSorter = {
'original-order' : function( $elem, instance ) {
return instance.elemCount;
}
};
this.options.getSortData = $.extend( this.options.getSortData, originalOrderSorter );
this._setupAtoms( this.$allAtoms );
// get top left position of where the bricks should be
var $cursor = $( document.createElement('div') );
this.element.prepend( $cursor );
this.posTop = Math.round( $cursor.position().top );
this.posLeft = Math.round( $cursor.position().left );
$cursor.remove();
// add molequul class first time around
var instance = this;
setTimeout( function() {
instance.element.addClass( instance.options.containerClass );
}, 0 );
// do any layout-specific setup
this.width = this.element.width();
this._getMasonryColCount();
// bind resize method
if ( this.options.resizeable ) {
$(window).bind('smartresize.molequul', function() { instance.element.molequul('resize') } );
}
},
_isNewProp : function( prop ) {
return this.prevOpts ? ( this.options[ prop ] !== this.prevOpts[ prop ] ) : true;
},
// _init fires when your instance is first created
// (from the constructor above), and when you
// attempt to initialize the widget again (by the bridge)
// after it has already been initialized.
_init : function( callback ) {
// check if watched properties are new
var instance = this;
$.each( [ 'filter', 'sortBy', 'sortDir' ], function( i, propName ){
instance.isNew[ propName ] = instance._isNewProp( propName );
});
if ( this.isNew.filter ) {
this.$filteredAtoms = this._filter( this.$allAtoms )
} else {
this.$filteredAtoms = this.$allAtoms;
}
if ( this.isNew.filter || this.isNew.sortBy || this.isNew.sortDir ) {
this._sort();
}
this.reLayout( callback );
},
option: function( key, value ){
// get/change options AFTER initialization:
// you don't have to support all these cases,
// but here's how:
// signature: $('#foo').bar({ cool:false });
if ( $.isPlainObject( key ) ){
this.options = $.extend(true, this.options, key);
// signature: $('#foo').option('cool'); - getter
} else if ( key && typeof value === "undefined" ){
return this.options[ key ];
// signature: $('#foo').bar('option', 'baz', false);
} else {
this.options[ key ] = value;
}
return this; // make sure to return the instance!
},
// ====================== Adding ======================
_setupAtoms : function( $atoms ) {
// base style for atoms
var atomStyle = { position: 'absolute' };
if ( this.usingTransforms ) {
atomStyle.left = 0;
atomStyle.top = 0;
}
$atoms.css( atomStyle );
var instance = this;
$atoms.each(function(){
var $this = $(this),
sortData = {},
getSortData = instance.options.getSortData,
key;
// get value for sort data based on fn( $elem ) passed in
for ( key in getSortData ) {
sortData[ key ] = getSortData[ key ]( $this, instance );
}
// apply sort data to $element
$this.data( 'molequul-sort-data', sortData );
// increment element count
// console.log( instance.elemCount )
instance.elemCount ++;
});
},
// ====================== Filtering ======================
_filter : function( $atoms ) {
var $filteredAtoms,
filter = this.options.filter === '' ? '*' : this.options.filter;
if ( !filter ) {
$filteredAtoms = $atoms;
} else {
var hiddenClass = this.options.hiddenClass,
hiddenSelector = '.' + hiddenClass,
$visibleAtoms = $atoms.not( hiddenSelector ),
$hiddenAtoms = $atoms.filter( hiddenSelector ),
$atomsToShow = $hiddenAtoms;
$filteredAtoms = $atoms.filter( filter );
if ( filter !== '*' ) {
$atomsToShow = $hiddenAtoms.filter( filter );
var $atomsToHide = $visibleAtoms.not( filter ).toggleClass( hiddenClass );
$atomsToHide.addClass( hiddenClass );
this.styleQueue.push({ $el: $atomsToHide, style: this.options.hiddenStyle });
}
this.styleQueue.push({ $el: $atomsToShow, style: this.options.visibleStyle });
$atomsToShow.removeClass( hiddenClass );
}
return $filteredAtoms;
},
// ====================== Sorting ======================
_getSortFn : function( sortBy, sortDir ) {
switch ( sortDir.toLowerCase() ) {
case 'd' :
case 'des' :
case 'desc' :
case 'desend' :
case 'decend' :
case 'descend' :
case 'descending' :
sortDir = -1;
break;
default :
sortDir = 1;
}
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() {
var sortFn = this._getSortFn( this.options.sortBy, this.options.sortDir );
this.$filteredAtoms.sort( sortFn );
return this;
},
// ====================== Layout ======================
_translate : function( x, y ) {
return { translate : [ x, y ] };
},
_positionAbs : function( x, y ) {
return { left: x, top: y };
},
_pushPosition : function( $elem, x, y ) {
var position = this.positionFn( x, y );
this.styleQueue.push({ $el: $elem, style: position });
},
// ====================== masonry ======================
_placeBrick : function( $brick, setCount, setY ) {
// 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 + $brick.outerHeight(true),
i = setY.length,
shortCol = i,
setSpan = this.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 = this.colW * shortCol + this.posLeft;
y = minimumY;
this._pushPosition( $brick, x, y );
// apply setHeight to necessary columns
for ( i=0; i < setSpan; i++ ) {
this.colYs[ shortCol + i ] = setHeight;
}
},
_masonrySingleColumn : function( $elems ) {
var instance = this;
$elems.each(function(){
instance._placeBrick( $(this), instance.colCount, instance.colYs );
});
},
_masonryMultiColumn : function( $elems ) {
var instance = this;
$elems.each(function(){
var $this = $(this),
//how many columns does this brick span
colSpan = Math.ceil( $this.outerWidth(true) / instance.colW );
colSpan = Math.min( colSpan, instance.colCount );
if ( colSpan === 1 ) {
// if brick spans only one column, just like singleMode
instance._placeBrick( $this, instance.colCount, instance.colYs );
} else {
// brick spans more than one column
// how many different places could this brick fit horizontally
var groupCount = instance.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 = instance.colYs.slice( i, i+colSpan );
// and get the max value of the array
groupY[i] = Math.max.apply( Math, groupColY );
}
instance._placeBrick( $this, groupCount, groupY );
}
});
},
_getMasonryColCount : function( ) {
// console.log( 'getting masonry col count')
this.colW = this.options.columnWidth || this.$allAtoms.outerWidth(true);
// if colW == 0, back out before divide by zero
if ( !this.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;
}
this.width = this.element.width();
this.colCount = Math.floor( this.width / this.colW ) ;
this.colCount = Math.max( this.colCount, 1 );
return this;
},
_masonryResetLayoutProps : function() {
var i = this.colCount;
this.colYs = [];
while (i--) {
this.colYs.push( this.posTop );
}
return this;
},
_masonryResize : function() {
var prevColCount = this.colCount;
// get updated colCount
this._getMasonryColCount();
if ( this.colCount !== prevColCount ) {
// if column count has changed, do a new column cound
this.reLayout();
}
return this;
},
_masonryMeasureContainerHeight : function() {
this.containerHeight = Math.max.apply( Math, this.colYs ) - this.posTop;
},
// ====================== ClearFloat ======================
_clearFloat : function( $elems ) {
var instance = this;
return $elems.each( function() {
var $this = $(this),
atomW = $this.outerWidth(true),
atomH = $this.outerHeight(true),
x, y;
if ( instance.clearFloat.x !== 0 && atomW + instance.clearFloat.x > instance.width ) {
// if this element cannot fit in the current row
instance.clearFloat.x = 0;
instance.clearFloat.y = instance.clearFloat.height;
}
// position the atom
x = instance.clearFloat.x + instance.posLeft;
y = instance.clearFloat.y + instance.posTop;
instance._pushPosition( $this, x, y );
instance.clearFloat.height = Math.max( instance.clearFloat.y + atomH, instance.clearFloat.height );
instance.clearFloat.x += atomW;
});
},
_clearFloatResetLayoutProps : function() {
this.clearFloat = {
x : 0,
y : 0,
height : 0
};
return this;
},
_clearFloatMeasureContainerHeight : function () {
this.containerHeight = this.clearFloat.height;
},
_clearFloatResize : function() {
this.width = this.element.width();
return this.reLayout()
},
// ====================== 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 layoutMode = this.options.layoutMode,
layoutMethod = '_' + layoutMode;
// layout logic
if ( layoutMethod === '_masonry' ) {
layoutMethod += this.options.masonrySingleMode ? 'SingleColumn' : 'MultiColumn';
}
this[ layoutMethod ]( $elems );
// $elems.molequul( layoutMethod, props );
// set the height of the container to the tallest column
this[ '_' + layoutMode + 'MeasureContainerHeight' ]();
var containerStyle = { height: this.containerHeight };
this.styleQueue.push({ $el: this.element, style: containerStyle });
// are we animating the layout arrangement?
// use plugin-ish syntax for css or animate
var styleFn = ( this.applyStyleFnName === 'animate' && !$.data( this.element, 'molequul' ) ) ?
'css' : this.applyStyleFnName,
animOpts = this.options.animationOptions;
// process styleQueue
$.each( this.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
this.styleQueue = [];
// provide $elems as context for the callback
if ( callback ) {
callback.call( $elems );
}
return this;
},
resize : function() {
return this[ '_' + this.options.layoutMode + 'Resize' ]();
},
reLayout : function( callback ) {
return this
[ '_' + this.options.layoutMode + 'ResetLayoutProps' ]()
.layout( this.$filteredAtoms, callback )
},
// ====================== Convenience methods ======================
// adds a jQuery object of items to a molequul container
addAtoms : function( $content, callback ) {
var $newAtoms = this._filterFind( $content, this.options.itemSelector );
this._setupAtoms( $newAtoms );
// add new atoms to atoms pools
this.$allAtoms = this.$allAtoms.add( $newAtoms );
// this.$filteredAtoms = this.$filteredAtoms.add( $newAtoms );
// return $newAtoms;
if ( callback ) {
callback( $newAtoms );
}
},
// convienence method for adding elements properly to any layout
insert : function( $content, callback ) {
this.element.append( $content );
var instance = this;
this.addAtoms( $content, function( $newAtoms ) {
$filteredAtoms = instance._filter( $newAtoms );
instance.$filteredAtoms = instance.$filteredAtoms.add( $filteredAtoms );
});
this._sort().reLayout( callback );
},
// convienence method for working with Infinite Scroll
appended : function( $content, callback ) {
// var $newAtoms = this.addAtoms( $content );
// this.$filteredAtoms = this.$filteredAtoms.add( $newAtoms );
//
// return this.layout( $newAtoms, callback );
var instance = this;
this.addAtoms( $content, function( $newAtoms ){
instance.$filteredAtoms = instance.$filteredAtoms.add( $newAtoms );
instance.layout( $newAtoms, callback )
});
}
};
$.widget.bridge( 'molequul', Molequul );
})( jQuery );

617
src/jquery.molequul.js

@ -2,107 +2,255 @@
** jQuery Molequul version 0.1 ** jQuery Molequul version 0.1
** Copyright David DeSandro ** Copyright David DeSandro
**************************************************/ **************************************************/
(function($){ (function( $, undefined ) {
$.molequul = { // our "Widget" object constructor
var Molequul = function( options, element ){
this.element = $( element );
// Default plugin options this._create( options );
defaults : { this._init();
};
Molequul.prototype = {
options : {
columnWidth : 150, columnWidth : 150,
resizeable: true, resizeable: true,
layoutMode : 'masonry', layoutMode : 'masonry',
masonrySingleMode : false, masonrySingleMode : false,
containerClass : 'molequul', containerClass : 'molequul',
hiddenClass : 'molequul-hidden', hiddenClass : 'molequul-hidden',
hiddenStyle : { hiddenStyle : Modernizr.csstransforms && !$.browser.opera ?
opacity : 0 { opacity : 0, scale : 0.001 } :
}, { opacity : 0 },
visibleStyle : { visibleStyle : Modernizr.csstransforms && !$.browser.opera ?
opacity : 1 { opacity : 1, scale : 1 } :
}, { opacity : 1 },
animationEngine : 'best-available', animationEngine : 'best-available',
animationOptions: { animationOptions: {
queue: false queue: false
}, },
sortBy : 'original-order', sortBy : 'original-order',
sortDir : 1 sortDir : 'asc'
},
_filterFind: function( $elems, selector ) {
return selector ? $elems.filter( selector ).add( $elems.find( selector ) ) : $elems;
},
// sets up widget
_create : function( options ) {
this.options = $.extend( true, {}, this.options, options );
this.isNew = {};
this.styleQueue = [];
this.elemCount = 0;
// need to get atoms
this.$allAtoms = this._filterFind( this.element.children(), this.options.itemSelector );
// console.log( 'all atoms', this.$allAtoms.length )
this.element.css({
overflow : 'hidden',
position : 'relative'
});
var jQueryAnimation = false;
// get applyStyleFnName
switch ( this.options.animationEngine.toLowerCase().replace( /[ _\-]/g, '') ) {
case 'none' :
this.applyStyleFnName = 'css';
break;
case 'jquery' :
this.applyStyleFnName = 'animate';
jQueryAnimation = true;
break;
case 'bestavailable' :
default :
this.applyStyleFnName = Modernizr.csstransitions ? 'css' : 'animate';
}
this.usingTransforms = Modernizr.csstransforms && Modernizr.csstransitions && !jQueryAnimation;
this.positionFn = this.usingTransforms ? this._translate : this._positionAbs;
// sorting
var originalOrderSorter = {
'original-order' : function( $elem, instance ) {
return instance.elemCount;
}
};
this.options.getSortData = $.extend( this.options.getSortData, originalOrderSorter );
this._setupAtoms( this.$allAtoms );
// get top left position of where the bricks should be
var $cursor = $( document.createElement('div') );
this.element.prepend( $cursor );
this.posTop = Math.round( $cursor.position().top );
this.posLeft = Math.round( $cursor.position().left );
$cursor.remove();
// add molequul class first time around
var instance = this;
setTimeout( function() {
instance.element.addClass( instance.options.containerClass );
}, 0 );
// do any layout-specific setup
this.width = this.element.width();
this._getMasonryColCount();
// bind resize method
if ( this.options.resizeable ) {
$(window).bind('smartresize.molequul', function() { instance.element.molequul('resize') } );
}
}, },
isNewProp : function( property, props ) {
if ( !props.initialized ) { _isNewProp : function( prop ) {
return true; return this.prevOpts ? ( this.options[ prop ] !== this.prevOpts[ prop ] ) : true;
},
// _init fires when your instance is first created
// (from the constructor above), and when you
// attempt to initialize the widget again (by the bridge)
// after it has already been initialized.
_init : function( callback ) {
// check if watched properties are new
var instance = this;
$.each( [ 'filter', 'sortBy', 'sortDir' ], function( i, propName ){
instance.isNew[ propName ] = instance._isNewProp( propName );
});
if ( this.isNew.filter ) {
this.$filteredAtoms = this._filter( this.$allAtoms )
} else {
this.$filteredAtoms = this.$allAtoms;
}
if ( this.isNew.filter || this.isNew.sortBy || this.isNew.sortDir ) {
this._sort();
} }
var previousProp = props.prevOpts[ property ];
return ( props.opts[ property ] !== previousProp ); this.reLayout( callback );
},
option: function( key, value ){
// get/change options AFTER initialization:
// you don't have to support all these cases,
// but here's how:
// signature: $('#foo').bar({ cool:false });
if ( $.isPlainObject( key ) ){
this.options = $.extend(true, this.options, key);
// signature: $('#foo').option('cool'); - getter
} else if ( key && typeof value === "undefined" ){
return this.options[ key ];
// signature: $('#foo').bar('option', 'baz', false);
} else {
this.options[ key ] = value;
}
return this; // make sure to return the instance!
}, },
// ====================== Adding ====================== // ====================== Adding ======================
addSortData : function( props ) { _setupAtoms : function( $atoms ) {
return this.each(function(){
// base style for atoms
var atomStyle = { position: 'absolute' };
if ( this.usingTransforms ) {
atomStyle.left = 0;
atomStyle.top = 0;
}
$atoms.css( atomStyle );
var instance = this;
$atoms.each(function(){
var $this = $(this), var $this = $(this),
sortData = {}, sortData = {},
getSortData = props.opts.getSortData, getSortData = instance.options.getSortData,
key; key;
// get value for sort data based on fn( $elem ) passed in // get value for sort data based on fn( $elem ) passed in
for ( key in getSortData ) { for ( key in getSortData ) {
sortData[ key ] = getSortData[ key ]( $this ); sortData[ key ] = getSortData[ key ]( $this, instance );
} }
// apply sort data to $element // apply sort data to $element
$this.data( 'molequul-sort-data', sortData ); $this.data( 'molequul-sort-data', sortData );
// increment element count // increment element count
props.elemCount ++; // console.log( instance.elemCount )
instance.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 ====================== // ====================== Filtering ======================
filter : function( $atoms ) { _filter : function( $atoms ) {
var props = this.data('molequul'), var $filteredAtoms,
filter = props.opts.filter === '' ? '*' : props.opts.filter; filter = this.options.filter === '' ? '*' : this.options.filter;
if ( !filter ) { if ( !filter ) {
props.atoms.$filtered = $atoms; $filteredAtoms = $atoms;
} else { } else {
var hiddenClass = props.opts.hiddenClass, var hiddenClass = this.options.hiddenClass,
hiddenSelector = '.' + hiddenClass, hiddenSelector = '.' + hiddenClass,
$visibleAtoms = $atoms.not( hiddenSelector ), $visibleAtoms = $atoms.not( hiddenSelector ),
$hiddenAtoms = $atoms.filter( hiddenSelector ), $hiddenAtoms = $atoms.filter( hiddenSelector ),
$atomsToShow = $hiddenAtoms; $atomsToShow = $hiddenAtoms;
props.atoms.$filtered = $atoms.filter( filter ); $filteredAtoms = $atoms.filter( filter );
if ( filter !== '*' ) { if ( filter !== '*' ) {
$atomsToShow = $hiddenAtoms.filter( filter ); $atomsToShow = $hiddenAtoms.filter( filter );
var $atomsToHide = $visibleAtoms.not( filter ).toggleClass( hiddenClass ); var $atomsToHide = $visibleAtoms.not( filter ).toggleClass( hiddenClass );
$atomsToHide.addClass( hiddenClass ); $atomsToHide.addClass( hiddenClass );
props.styleQueue.push({ $el: $atomsToHide, style: props.opts.hiddenStyle }); this.styleQueue.push({ $el: $atomsToHide, style: this.options.hiddenStyle });
} }
props.styleQueue.push({ $el: $atomsToShow, style: props.opts.visibleStyle }); this.styleQueue.push({ $el: $atomsToShow, style: this.options.visibleStyle });
$atomsToShow.removeClass( hiddenClass ); $atomsToShow.removeClass( hiddenClass );
} }
return this; return $filteredAtoms;
}, },
// ====================== Sorting ====================== // ====================== Sorting ======================
getSortFn : function( sortBy, sortDir ) { _getSortFn : function( sortBy, sortDir ) {
switch ( sortDir.toLowerCase() ) {
case 'd' :
case 'des' :
case 'desc' :
case 'desend' :
case 'decend' :
case 'descend' :
case 'descending' :
sortDir = -1;
break;
default :
sortDir = 1;
}
var getSorter = function( elem ) { var getSorter = function( elem ) {
return $(elem).data('molequul-sort-data')[ sortBy ]; return $(elem).data('molequul-sort-data')[ sortBy ];
}; };
@ -118,12 +266,11 @@
}, },
// used on all the filtered atoms, $atoms.filtered // used on all the filtered atoms, $atoms.filtered
sort : function( props ) { _sort : function() {
var sortFn = props.opts.sortBy === 'random' ? $.molequul.randomSortFn : var sortFn = this._getSortFn( this.options.sortBy, this.options.sortDir );
$.molequul.getSortFn( props.opts.sortBy, props.opts.sortDir );
props.atoms.$filtered.sort( sortFn ); this.$filteredAtoms.sort( sortFn );
return this; return this;
}, },
@ -132,30 +279,29 @@
// ====================== Layout ====================== // ====================== Layout ======================
translate : function( x, y ) { _translate : function( x, y ) {
return { translate : [ x, y ] }; return { translate : [ x, y ] };
}, },
positionAbs : function( x, y ) { _positionAbs : function( x, y ) {
return { left: x, top: y }; return { left: x, top: y };
}, },
pushPosition : function( x, y, props ) { _pushPosition : function( $elem, x, y ) {
var position = props.positionFn( x, y ); var position = this.positionFn( x, y );
props.styleQueue.push({ $el: this, style: position }); this.styleQueue.push({ $el: $elem, style: position });
return this;
}, },
// ====================== masonry ====================== // ====================== masonry ======================
placeBrick : function( setCount, setY, props ) { _placeBrick : function( $brick, setCount, setY ) {
// here, `this` refers to a child element or "brick" // here, `this` refers to a child element or "brick"
// get the minimum Y value from the columns // get the minimum Y value from the columns
var minimumY = Math.min.apply( Math, setY ), var minimumY = Math.min.apply( Math, setY ),
setHeight = minimumY + this.outerHeight(true), setHeight = minimumY + $brick.outerHeight(true),
i = setY.length, i = setY.length,
shortCol = i, shortCol = i,
setSpan = props.colCount + 1 - i, setSpan = this.colCount + 1 - i,
x, y ; x, y ;
// Which column has the minY value, closest to the left // Which column has the minY value, closest to the left
while (i--) { while (i--) {
@ -165,138 +311,129 @@
} }
// position the brick // position the brick
x = props.colW * shortCol + props.posLeft; x = this.colW * shortCol + this.posLeft;
y = minimumY; y = minimumY;
this.molequul( 'pushPosition', x, y, props ); this._pushPosition( $brick, x, y );
// apply setHeight to necessary columns // apply setHeight to necessary columns
for ( i=0; i < setSpan; i++ ) { for ( i=0; i < setSpan; i++ ) {
props.colYs[ shortCol + i ] = setHeight; this.colYs[ shortCol + i ] = setHeight;
} }
return this;
}, },
masonrySingleColumn : function( props ) { _masonrySingleColumn : function( $elems ) {
return this.each(function(){ var instance = this;
$(this).molequul( 'placeBrick', props.colCount, props.colYs, props ); $elems.each(function(){
instance._placeBrick( $(this), instance.colCount, instance.colYs );
}); });
}, },
masonryMultiColumn : function( props ) { _masonryMultiColumn : function( $elems ) {
return this.each(function(){ var instance = this;
$elems.each(function(){
var $this = $(this), var $this = $(this),
//how many columns does this brick span //how many columns does this brick span
colSpan = Math.ceil( $this.outerWidth(true) / props.colW ); colSpan = Math.ceil( $this.outerWidth(true) / instance.colW );
colSpan = Math.min( colSpan, props.colCount ); colSpan = Math.min( colSpan, instance.colCount );
if ( colSpan === 1 ) { if ( colSpan === 1 ) {
// if brick spans only one column, just like singleMode // if brick spans only one column, just like singleMode
$this.molequul( 'placeBrick', props.colCount, props.colYs, props ); instance._placeBrick( $this, instance.colCount, instance.colYs );
} else { } else {
// brick spans more than one column // brick spans more than one column
// how many different places could this brick fit horizontally // how many different places could this brick fit horizontally
var groupCount = props.colCount + 1 - colSpan, var groupCount = instance.colCount + 1 - colSpan,
groupY = [], groupY = [],
groupColY; groupColY;
// for each group potential horizontal position // for each group potential horizontal position
for ( var i=0; i < groupCount; i++ ) { for ( var i=0; i < groupCount; i++ ) {
// make an array of colY values for that one group // make an array of colY values for that one group
groupColY = props.colYs.slice( i, i+colSpan ); groupColY = instance.colYs.slice( i, i+colSpan );
// and get the max value of the array // and get the max value of the array
groupY[i] = Math.max.apply( Math, groupColY ); groupY[i] = Math.max.apply( Math, groupColY );
} }
$this.molequul( 'placeBrick', groupCount, groupY, props ); instance._placeBrick( $this, groupCount, groupY );
} }
}); });
}, },
getMasonryColCount : function( props ) { _getMasonryColCount : function( ) {
props.colW = props.opts.columnWidth || props.atoms.$all.outerWidth(true); // console.log( 'getting masonry col count')
this.colW = this.options.columnWidth || this.$allAtoms.outerWidth(true);
// if colW == 0, back out before divide by zero // if colW == 0, back out before divide by zero
if ( !props.colW ) { if ( !this.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.'); 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; return this;
} }
props.width = this.width(); this.width = this.element.width();
props.colCount = Math.floor( props.width / props.colW ) ; this.colCount = Math.floor( this.width / this.colW ) ;
props.colCount = Math.max( props.colCount, 1 ); this.colCount = Math.max( this.colCount, 1 );
return this; return this;
}, },
masonryResetLayoutProps : function( props ) { _masonryResetLayoutProps : function() {
var i = this.colCount;
var i = props.colCount; this.colYs = [];
props.colYs = [];
while (i--) { while (i--) {
props.colYs.push( props.posTop ); this.colYs.push( this.posTop );
} }
return this; return this;
}, },
masonryResize : function( props ) { _masonryResize : function() {
var prevColCount = props.colCount; var prevColCount = this.colCount;
// get updated colCount // get updated colCount
this.molequul( 'getMasonryColCount', props ); this._getMasonryColCount();
if ( props.colCount !== prevColCount ) { if ( this.colCount !== prevColCount ) {
// if column count has changed, do a new column cound // if column count has changed, do a new column cound
this.molequul( 'reLayout', props ); this.reLayout();
} }
return this; return this;
}, },
masonryMeasureContainerHeight : function( props ) { _masonryMeasureContainerHeight : function() {
props.containerHeight = Math.max.apply( Math, props.colYs ) - props.posTop; this.containerHeight = Math.max.apply( Math, this.colYs ) - this.posTop;
return this;
},
masonrySetup : function( props ) {
this.molequul('getMasonryColCount', props );
return this;
}, },
// ====================== ClearFloat ====================== // ====================== ClearFloat ======================
clearFloat : function( props ) { _clearFloat : function( $elems ) {
return this.each( function() { var instance = this;
return $elems.each( function() {
var $this = $(this), var $this = $(this),
atomW = $this.outerWidth(true), atomW = $this.outerWidth(true),
atomH = $this.outerHeight(true), atomH = $this.outerHeight(true),
x, y; x, y;
if ( props.clearFloat.x !== 0 && atomW + props.clearFloat.x > props.width ) { if ( instance.clearFloat.x !== 0 && atomW + instance.clearFloat.x > instance.width ) {
// if this element cannot fit in the current row // if this element cannot fit in the current row
props.clearFloat.x = 0; instance.clearFloat.x = 0;
props.clearFloat.y = props.clearFloat.height; instance.clearFloat.y = instance.clearFloat.height;
} }
// position the atom // position the atom
x = props.clearFloat.x + props.posLeft; x = instance.clearFloat.x + instance.posLeft;
y = props.clearFloat.y + props.posTop; y = instance.clearFloat.y + instance.posTop;
$this.molequul( 'pushPosition', x, y, props ); instance._pushPosition( $this, x, y );
props.clearFloat.height = Math.max( props.clearFloat.y + atomH, props.clearFloat.height ); instance.clearFloat.height = Math.max( instance.clearFloat.y + atomH, instance.clearFloat.height );
props.clearFloat.x += atomW; instance.clearFloat.x += atomW;
}); });
}, },
clearFloatSetup : function( props ) { _clearFloatResetLayoutProps : function() {
props.width = this.width(); this.clearFloat = {
return this;
},
clearFloatResetLayoutProps : function( props ) {
props.clearFloat = {
x : 0, x : 0,
y : 0, y : 0,
height : 0 height : 0
@ -304,14 +441,13 @@
return this; return this;
}, },
clearFloatMeasureContainerHeight : function ( props ) { _clearFloatMeasureContainerHeight : function () {
props.containerHeight = props.clearFloat.height; this.containerHeight = this.clearFloat.height;
return this;
}, },
clearFloatResize : function( props ) { _clearFloatResize : function() {
props.width = this.width(); this.width = this.element.width();
return this.molequul( 'reLayout', props ); return this.reLayout()
}, },
@ -322,36 +458,37 @@
// accepts atoms-to-be-laid-out to start with // accepts atoms-to-be-laid-out to start with
layout : function( $elems, callback ) { layout : function( $elems, callback ) {
var props = this.data('molequul'), var layoutMode = this.options.layoutMode,
layoutMode = props.opts.layoutMode, layoutMethod = '_' + layoutMode;
layoutMethod = layoutMode;
// layout logic // layout logic
if ( layoutMethod === 'masonry' ) { if ( layoutMethod === '_masonry' ) {
layoutMethod = props.opts.masonrySingleMode ? 'masonrySingleColumn' : 'masonryMultiColumn'; layoutMethod += this.options.masonrySingleMode ? 'SingleColumn' : 'MultiColumn';
} }
$elems.molequul( layoutMethod, props ); this[ layoutMethod ]( $elems );
// $elems.molequul( layoutMethod, props );
// set the height of the container to the tallest column // set the height of the container to the tallest column
this.molequul( layoutMode + 'MeasureContainerHeight', props ); this[ '_' + layoutMode + 'MeasureContainerHeight' ]();
var containerStyle = { height: props.containerHeight }; var containerStyle = { height: this.containerHeight };
props.styleQueue.push({ $el: this, style: containerStyle }); this.styleQueue.push({ $el: this.element, style: containerStyle });
// are we animating the layout arrangement? // are we animating the layout arrangement?
// use plugin-ish syntax for css or animate // use plugin-ish syntax for css or animate
var styleFn = ( props.applyStyleFnName === 'animate' && !props.initialized ) ? var styleFn = ( this.applyStyleFnName === 'animate' && !$.data( this.element, 'molequul' ) ) ?
'css' : props.applyStyleFnName, 'css' : this.applyStyleFnName,
animOpts = props.opts.animationOptions; animOpts = this.options.animationOptions;
// process styleQueue // process styleQueue
$.each( props.styleQueue, function( i, obj ){ $.each( this.styleQueue, function( i, obj ){
// have to extend animation to play nice with jQuery // have to extend animation to play nice with jQuery
obj.$el[ styleFn ]( obj.style, $.extend( {}, animOpts ) ); obj.$el[ styleFn ]( obj.style, $.extend( {}, animOpts ) );
}); });
@ -359,7 +496,7 @@
// clear out queue for next time // clear out queue for next time
props.styleQueue = []; this.styleQueue = [];
// provide $elems as context for the callback // provide $elems as context for the callback
if ( callback ) { if ( callback ) {
@ -371,211 +508,61 @@
resize : function() { resize : function() {
var props = this.data('molequul'); return this[ '_' + this.options.layoutMode + 'Resize' ]();
return this.molequul( props.opts.layoutMode + 'Resize', props );
}, },
reLayout : function( props ) { reLayout : function( callback ) {
props = props || this.data('molequul');
props.initialized = true;
return this return this
.molequul( props.opts.layoutMode + 'ResetLayoutProps', props ) [ '_' + this.options.layoutMode + 'ResetLayoutProps' ]()
.molequul( 'layout', props.atoms.$filtered ); .layout( this.$filteredAtoms, callback )
}, },
// ====================== Setup and Init ====================== // ====================== Convenience methods ======================
// 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 // adds a jQuery object of items to a molequul container
var originalOrderSorter = { addAtoms : function( $content, callback ) {
'original-order' : function( $elem ) { var $newAtoms = this._filterFind( $content, this.options.itemSelector );
return props.elemCount; this._setupAtoms( $newAtoms );
// add new atoms to atoms pools
this.$allAtoms = this.$allAtoms.add( $newAtoms );
// this.$filteredAtoms = this.$filteredAtoms.add( $newAtoms );
// return $newAtoms;
if ( callback ) {
callback( $newAtoms );
} }
};
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' ], // convienence method for adding elements properly to any layout
insert : function( $content, callback ) {
init : function( options, callback ) { this.element.append( $content );
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 );
var instance = this;
this.addAtoms( $content, function( $newAtoms ) {
$filteredAtoms = instance._filter( $newAtoms );
instance.$filteredAtoms = instance.$filteredAtoms.add( $filteredAtoms );
}); });
}, this._sort().reLayout( callback );
// ====================== 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 // convienence method for working with Infinite Scroll
appended : function( $content ) { appended : function( $content, callback ) {
return this.molequul( 'add', $content ).molequul( 'layout', $content ); // var $newAtoms = this.addAtoms( $content );
} // this.$filteredAtoms = this.$filteredAtoms.add( $newAtoms );
//
}; // return this.layout( $newAtoms, callback );
var instance = this;
// add transform default styles for filtered classes this.addAtoms( $content, function( $newAtoms ){
if ( Modernizr.csstransforms && !$.browser.opera ) { instance.$filteredAtoms = instance.$filteredAtoms.add( $newAtoms );
$.molequul.defaults.hiddenStyle = { instance.layout( $newAtoms, callback )
opacity : 0, });
scale : 0.001
};
$.molequul.defaults.visibleStyle = {
opacity : 1,
scale : 1
};
} }
// molequul code begin
$.fn.molequul = function( firstArg ) {
// Method calling logic
var method = $.molequul[ firstArg ];
if ( method ) {
// remove firstArg, which is a string of the function name, from arguments
var args = Array.prototype.slice.call( arguments, 1 );
return method.apply( this, args );
} else if ( !firstArg || typeof firstArg === 'object' ) {
return $.molequul.init.apply( this, arguments );
}
}; };
$.widget.bridge( 'molequul', Molequul );
})( jQuery ); })( jQuery );
Loading…
Cancel
Save