578 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			578 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/**
 | 
						|
 * jQuery Nested v1.03
 | 
						|
 *
 | 
						|
 * For a (total) gap free, multi column, grid layout experience.
 | 
						|
 * http://suprb.com/apps/nested/
 | 
						|
 * By Andreas Pihlström and additional brain activity by Jonas Blomdin
 | 
						|
 *
 | 
						|
 * Licensed under the MIT license.
 | 
						|
 */
 | 
						|
 | 
						|
// Debouncing function from John Hann
 | 
						|
// http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
 | 
						|
// Copy pasted from http://paulirish.com/2009/throttled-smartresize-jquery-event-handler/
 | 
						|
 | 
						|
(function ($, sr) {
 | 
						|
    var debounce = function (func, threshold, execAsap) {
 | 
						|
        var timeout;
 | 
						|
        return function debounced() {
 | 
						|
            var obj = this,
 | 
						|
                args = arguments;
 | 
						|
 | 
						|
            function delayed() {
 | 
						|
                if (!execAsap) func.apply(obj, args);
 | 
						|
                timeout = null;
 | 
						|
            };
 | 
						|
            if (timeout) clearTimeout(timeout);
 | 
						|
            else if (execAsap) func.apply(obj, args);
 | 
						|
 | 
						|
            timeout = setTimeout(delayed, threshold || 150);
 | 
						|
        };
 | 
						|
    };
 | 
						|
    jQuery.fn[sr] = function (fn) {
 | 
						|
        return fn ? this.bind('resize', debounce(fn)) : this.trigger(sr);
 | 
						|
    };
 | 
						|
 | 
						|
})(jQuery, 'smartresize');
 | 
						|
 | 
						|
// Simple count object properties
 | 
						|
 | 
						|
if (!Object.keys) {
 | 
						|
    Object.keys = function (obj) {
 | 
						|
        var keys = [],
 | 
						|
            k;
 | 
						|
        for (k in obj) {
 | 
						|
            if (Object.prototype.hasOwnProperty.call(obj, k)) {
 | 
						|
                keys.push(k);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return keys;
 | 
						|
    };
 | 
						|
}
 | 
						|
 | 
						|
// The Nested magic
 | 
						|
 | 
						|
(function ($) {
 | 
						|
 | 
						|
    $.Nested = function (options, element) {
 | 
						|
        this.element = $(element);
 | 
						|
        this._init(options);
 | 
						|
    };
 | 
						|
 | 
						|
    $.Nested.settings = {
 | 
						|
        selector: '.box',
 | 
						|
        minWidth: 50,
 | 
						|
        minColumns: 1,
 | 
						|
        gutter: 1,
 | 
						|
        centered: false,
 | 
						|
        resizeToFit: true, // will resize block bigger than the gap
 | 
						|
        resizeToFitOptions: {
 | 
						|
            resizeAny: true // will resize any block to fit the gap         
 | 
						|
        },
 | 
						|
        animate: true,
 | 
						|
        animationOptions: {
 | 
						|
            speed: 20,
 | 
						|
            duration: 100,
 | 
						|
            queue: true,
 | 
						|
            complete: function () {}
 | 
						|
        }
 | 
						|
    };
 | 
						|
 | 
						|
    $.Nested.prototype = {
 | 
						|
 | 
						|
        _init: function (options) {
 | 
						|
            var container = this;
 | 
						|
            this.box = this.element;
 | 
						|
            $(this.box).css('position', 'relative');
 | 
						|
            this.options = $.extend(true, {}, $.Nested.settings, options);
 | 
						|
            this.elements = [];
 | 
						|
            this._isResizing = false;
 | 
						|
            this._update = true;
 | 
						|
            this.maxy = new Array();
 | 
						|
 | 
						|
            // add smartresize
 | 
						|
            $(window).smartresize(function () {
 | 
						|
                container.resize();
 | 
						|
            });
 | 
						|
 | 
						|
            // build box dimensions
 | 
						|
            this._setBoxes();
 | 
						|
        },
 | 
						|
 | 
						|
        _setBoxes: function ($els, method) {
 | 
						|
            var self = this;
 | 
						|
            this.idCounter = 0;
 | 
						|
            this.counter = 0;
 | 
						|
            this.t = 0;
 | 
						|
            this.maxHeight = 0;
 | 
						|
            this.currWidth = 0;
 | 
						|
            this.total = this.box.find(this.options.selector);
 | 
						|
            this.matrix = {};
 | 
						|
            this.gridrow = new Object;
 | 
						|
 | 
						|
            var calcWidth = !this.options.centered ? this.box.innerWidth() : $(window).width();
 | 
						|
 | 
						|
            this.columns = Math.max(this.options.minColumns, parseInt(calcWidth / (this.options.minWidth + this.options.gutter)) + 1);
 | 
						|
 | 
						|
            // build columns
 | 
						|
            var minWidth = this.options.minWidth;
 | 
						|
            var gutter = this.options.gutter;
 | 
						|
            var display = "block";
 | 
						|
 | 
						|
            $els = this.box.find(this.options.selector);
 | 
						|
 | 
						|
            $.each($els, function () {
 | 
						|
 | 
						|
                var dim = parseInt($(this).attr('class').replace(/^.*size([0-9]+).*$/, '$1')).toString().split('');
 | 
						|
                var x = (dim[0] == "N") ? 1 : parseFloat(dim[0]);
 | 
						|
                var y = (dim[1] == "a") ? 1 : parseFloat(dim[1]);
 | 
						|
 | 
						|
                var currWidth = minWidth * x + gutter * (x - 1);
 | 
						|
                var currHeight = minWidth * y + gutter * (y - 1);
 | 
						|
 | 
						|
                $(this).css({
 | 
						|
                    'display': display,
 | 
						|
                    'position': 'absolute',
 | 
						|
                    'width': currWidth,
 | 
						|
                    'height': currHeight,
 | 
						|
                    'top': $(this).position().top,
 | 
						|
                    'left': $(this).position().left
 | 
						|
                }).removeClass('nested-moved').attr('data-box', self.idCounter).attr('data-width', currWidth);
 | 
						|
 | 
						|
                self.idCounter++;
 | 
						|
 | 
						|
                // render grid
 | 
						|
                self._renderGrid($(this), method);
 | 
						|
 | 
						|
            });
 | 
						|
 | 
						|
            // position grid
 | 
						|
            if (self.counter == self.total.length) {
 | 
						|
 | 
						|
                // if option resizeToFit is true
 | 
						|
                if (self.options.resizeToFit) {
 | 
						|
                    self.elements = self._fillGaps();
 | 
						|
                }
 | 
						|
                self._renderItems(self.elements);
 | 
						|
                // reset elements
 | 
						|
                self.elements = [];
 | 
						|
            }
 | 
						|
        },
 | 
						|
 | 
						|
        _addMatrixRow: function (y) {
 | 
						|
            if (this.matrix[y]) {
 | 
						|
                return false;
 | 
						|
            } else this.matrix[y] = {};
 | 
						|
 | 
						|
            for (var c = 0; c < (this.columns - 1); c++) {
 | 
						|
                var x = c * (this.options.minWidth + this.options.gutter);
 | 
						|
                this.matrix[y][x] = 'false';
 | 
						|
            }
 | 
						|
        },
 | 
						|
 | 
						|
        _updateMatrix: function (el) {
 | 
						|
            var height = 0;
 | 
						|
            var t = parseInt(el['y']);
 | 
						|
            var l = parseInt(el['x']);
 | 
						|
            for (var h = 0; h < el['height']; h += (this.options.minWidth + this.options.gutter)) {
 | 
						|
                for (var w = 0; w < el['width']; w += (this.options.minWidth + this.options.gutter)) {
 | 
						|
                    var x = l + w;
 | 
						|
                    var y = t + h;
 | 
						|
                    if (!this.matrix[y]) {
 | 
						|
                        this._addMatrixRow(y);
 | 
						|
                    }
 | 
						|
                    this.matrix[y][x] = 'true';
 | 
						|
                }
 | 
						|
            }
 | 
						|
        },
 | 
						|
 | 
						|
        _getObjectSize: function (obj) { // Helper to get size of object, should probably be moved
 | 
						|
            var size = 0;
 | 
						|
            $.each(obj, function (p, v) {
 | 
						|
                size++;
 | 
						|
            });
 | 
						|
            return size;
 | 
						|
        },
 | 
						|
 | 
						|
 | 
						|
        _fillGaps: function () {
 | 
						|
            var self = this;
 | 
						|
            var box = {};
 | 
						|
 | 
						|
            $.each(this.elements, function (index, el) {
 | 
						|
                self._updateMatrix(el);
 | 
						|
            });
 | 
						|
 | 
						|
            var arr = this.elements;
 | 
						|
            arr.sort(function (a, b) {
 | 
						|
                return a.y - b.y;
 | 
						|
            });
 | 
						|
            arr.reverse();
 | 
						|
 | 
						|
            // Used to keep the highest y value for a box in memory
 | 
						|
            var topY = arr[0]['y'];
 | 
						|
 | 
						|
            // Used for current y with added offset
 | 
						|
            var actualY = 0;
 | 
						|
 | 
						|
            // Current number of rows in matrix
 | 
						|
            var rowsLeft = this._getObjectSize(this.matrix);
 | 
						|
 | 
						|
            $.each(this.matrix, function (y, row) {
 | 
						|
                rowsLeft--;
 | 
						|
                actualY = parseInt(y); // + parseInt(self.box.offset().top);
 | 
						|
                $.each(row, function (x, col) {
 | 
						|
 | 
						|
                    if (col === 'false' && actualY < topY) {
 | 
						|
                        if (!box.y) box.y = y;
 | 
						|
                        if (!box.x) box.x = x;
 | 
						|
                        if (!box.w) box.w = 0;
 | 
						|
                        if (!box.h) box.h = self.options.minWidth;
 | 
						|
                        box.w += (box.w) ? (self.options.minWidth + self.options.gutter) : self.options.minWidth;
 | 
						|
 | 
						|
                        var addonHeight = 0;
 | 
						|
                        for (var row = 1; row < rowsLeft; row++) {
 | 
						|
                            var z = parseInt(y) + parseInt(row * (self.options.minWidth + self.options.gutter));
 | 
						|
                            if (self.matrix[z] && self.matrix[z][x] == 'false') {
 | 
						|
                                addonHeight += (self.options.minWidth + self.options.gutter);
 | 
						|
                                self.matrix[z][x] = 'true';
 | 
						|
                            } else break;
 | 
						|
                        }
 | 
						|
 | 
						|
                        box.h + (parseInt(addonHeight) / (self.options.minWidth + self.options.gutter) == rowsLeft) ? 0 : parseInt(addonHeight);
 | 
						|
                        box.ready = true;
 | 
						|
 | 
						|
                    } else if (box.ready) {
 | 
						|
 | 
						|
                        $.each(arr, function (i, el) {
 | 
						|
                            if (box.y <= arr[i]['y'] && (self.options.resizeToFitOptions.resizeAny || box.w <= arr[i]['width'] && box.h <= arr[i]['height'])) {
 | 
						|
                                arr.splice(i, 1);
 | 
						|
                                $(el['$el']).addClass('nested-moved');
 | 
						|
                                self.elements.push({
 | 
						|
                                    $el: $(el['$el']),
 | 
						|
                                    x: parseInt(box.x),
 | 
						|
                                    y: parseInt(box.y),
 | 
						|
                                    col: i,
 | 
						|
                                    width: parseInt(box.w),
 | 
						|
                                    height: parseInt(box.h)
 | 
						|
                                });
 | 
						|
 | 
						|
                                return false;
 | 
						|
                            }
 | 
						|
                        });
 | 
						|
                        box = {};
 | 
						|
                    }
 | 
						|
                });
 | 
						|
 | 
						|
            });
 | 
						|
 | 
						|
            self.elements = arr;
 | 
						|
            return self.elements;
 | 
						|
 | 
						|
        },
 | 
						|
 | 
						|
        _renderGrid: function ($box, method) {
 | 
						|
 | 
						|
            this.counter++;
 | 
						|
            var ypos, gridy = ypos = 0;
 | 
						|
            var tot = 0;
 | 
						|
            var direction = !method ? "append" : "prepend";
 | 
						|
 | 
						|
            // Width & height
 | 
						|
            var width = $box.width();
 | 
						|
            var height = $box.height();
 | 
						|
 | 
						|
            // Calculate row and col
 | 
						|
            var col = Math.ceil(width / (this.options.minWidth + this.options.gutter));
 | 
						|
            var row = Math.ceil(height / (this.options.minWidth + this.options.gutter));
 | 
						|
 | 
						|
            // lock widest box to match minColumns
 | 
						|
            if (col > this.options.minColumns) {
 | 
						|
                this.options.minColumns = col;
 | 
						|
            }
 | 
						|
 | 
						|
            while (true) {
 | 
						|
 | 
						|
                for (var y = col; y >= 0; y--) {
 | 
						|
                    if (this.gridrow[gridy + y]) break;
 | 
						|
                    this.gridrow[gridy + y] = new Object;
 | 
						|
                    for (var x = 0; x < this.columns; x++) {
 | 
						|
                        this.gridrow[gridy + y][x] = false;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                for (var column = 0; column < (this.columns - col); column++) {
 | 
						|
 | 
						|
                    // Add default empty matrix, used to calculate and update matrix for each box
 | 
						|
                    matrixY = gridy * (this.options.minWidth + this.options.gutter);
 | 
						|
                    this._addMatrixRow(matrixY);
 | 
						|
 | 
						|
                    var fits = true;
 | 
						|
 | 
						|
                    for (var y = 0; y < row; y++) {
 | 
						|
                        for (var x = 0; x < col; x++) {
 | 
						|
 | 
						|
                            if (!this.gridrow[gridy + y]) {
 | 
						|
                                break;
 | 
						|
                            }
 | 
						|
 | 
						|
                            if (this.gridrow[gridy + y][column + x]) {
 | 
						|
                                fits = false;
 | 
						|
                                break;
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                        if (!fits) {
 | 
						|
                            break;
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                    if (fits) {
 | 
						|
                        // Set as taken
 | 
						|
                        for (var y = 0; y < row; y++) {
 | 
						|
                            for (var x = 0; x < col; x++) {
 | 
						|
 | 
						|
                                if (!this.gridrow[gridy + y]) {
 | 
						|
                                    break;
 | 
						|
                                }
 | 
						|
                                this.gridrow[gridy + y][column + x] = true;
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
 | 
						|
                        // Push to elements array
 | 
						|
                        this._pushItem($box, column * (this.options.minWidth + this.options.gutter), gridy * (this.options.minWidth + this.options.gutter), width, height, col, row, direction);
 | 
						|
                        return;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                gridy++;
 | 
						|
            }
 | 
						|
        },
 | 
						|
 | 
						|
        _pushItem: function ($el, x, y, w, h, cols, rows, method) {
 | 
						|
 | 
						|
            if (method == "prepend") {
 | 
						|
                this.elements.unshift({
 | 
						|
                    $el: $el,
 | 
						|
                    x: x,
 | 
						|
                    y: y,
 | 
						|
                    width: w,
 | 
						|
                    height: h,
 | 
						|
                    cols: cols,
 | 
						|
                    rows: rows
 | 
						|
                });
 | 
						|
            } else {
 | 
						|
                this.elements.push({
 | 
						|
                    $el: $el,
 | 
						|
                    x: x,
 | 
						|
                    y: y,
 | 
						|
                    width: w,
 | 
						|
                    height: h,
 | 
						|
                    cols: cols,
 | 
						|
                    rows: rows
 | 
						|
                });
 | 
						|
            }
 | 
						|
        },
 | 
						|
 | 
						|
        _setHeight: function ($els) {
 | 
						|
            var self = this;
 | 
						|
            $.each($els, function (index, value) {
 | 
						|
                // set maxHeight
 | 
						|
                var colY = (value['y'] + value['height']);
 | 
						|
                if (colY > self.maxHeight) {
 | 
						|
                    self.maxHeight = colY;
 | 
						|
                }
 | 
						|
            });
 | 
						|
            return self.maxHeight;
 | 
						|
        },
 | 
						|
 | 
						|
        _setWidth: function ($els) {
 | 
						|
            var self = this;
 | 
						|
            $.each($els, function (index, value) {
 | 
						|
                // set maxWidth
 | 
						|
                var colX = (value['x'] + value['width']);
 | 
						|
                if (colX > self.currWidth) {
 | 
						|
                    self.currWidth = colX;
 | 
						|
                }
 | 
						|
            });
 | 
						|
            return self.currWidth;
 | 
						|
        },
 | 
						|
 | 
						|
        _renderItems: function ($els) {
 | 
						|
            var self = this;
 | 
						|
 | 
						|
            // set container height and width
 | 
						|
            this.box.css('height', this._setHeight($els));
 | 
						|
            if (this.options.centered) {
 | 
						|
                this.box.css({'width' : this._setWidth($els), 'margin-left' : 'auto', 'margin-right' : 'auto'});
 | 
						|
            }
 | 
						|
 | 
						|
            $els.reverse();
 | 
						|
            var speed = this.options.animationOptions.speed;
 | 
						|
            var effect = this.options.animationOptions.effect;
 | 
						|
            var duration = this.options.animationOptions.duration;
 | 
						|
            var queue = this.options.animationOptions.queue;
 | 
						|
            var animate = this.options.animate;
 | 
						|
            var complete = this.options.animationOptions.complete;
 | 
						|
            var item = this;
 | 
						|
            var i = 0;
 | 
						|
            var t = 0;
 | 
						|
 | 
						|
            $.each($els, function (index, value) {
 | 
						|
 | 
						|
                $currLeft = $(value['$el']).position().left;
 | 
						|
                $currTop = $(value['$el']).position().top;
 | 
						|
                $currWidth = $(value['$el']).width();
 | 
						|
                $currHeight = $(value['$el']).width();
 | 
						|
 | 
						|
                value['$el'].attr('data-y', $currTop).attr('data-x', $currLeft);
 | 
						|
                
 | 
						|
                //if animate and queue
 | 
						|
                if (animate && queue && ($currLeft != value['x'] || $currTop != value['y'])) {
 | 
						|
                    setTimeout(function () {
 | 
						|
                        value['$el'].css({
 | 
						|
                            'display': 'block',
 | 
						|
                            'width': value['width'],
 | 
						|
                            'height': value['height']
 | 
						|
                        }).animate({
 | 
						|
                            'left': value['x'],
 | 
						|
                            'top': value['y']
 | 
						|
                        }, duration);
 | 
						|
                        t++;
 | 
						|
                        if (t == i) {
 | 
						|
                            complete.call(undefined, $els)
 | 
						|
                        }
 | 
						|
                    }, i * speed);
 | 
						|
                    i++;
 | 
						|
                }
 | 
						|
 | 
						|
                //if animate and no queue
 | 
						|
                if (animate && !queue && ($currLeft != value['x'] || $currTop != value['y'])) {
 | 
						|
                    setTimeout(function () {
 | 
						|
                        value['$el'].css({
 | 
						|
                            'display': 'block',
 | 
						|
                            'width': value['width'],
 | 
						|
                            'height': value['height']
 | 
						|
                        }).animate({
 | 
						|
                            'left': value['x'],
 | 
						|
                            'top': value['y']
 | 
						|
                        }, duration);
 | 
						|
                        t++;
 | 
						|
                        if (t == i) {
 | 
						|
                            complete.call(undefined, $els)
 | 
						|
                        }
 | 
						|
                    }, i);
 | 
						|
                    i++;
 | 
						|
                }
 | 
						|
 | 
						|
                //if no animation and no queue
 | 
						|
                if (!animate && ($currLeft != value['x'] || $currTop != value['y'])) {
 | 
						|
                    value['$el'].css({
 | 
						|
                        'display': 'block',
 | 
						|
                        'width': value['width'],
 | 
						|
                        'height': value['height'],
 | 
						|
                        'left': value['x'],
 | 
						|
                        'top': value['y']
 | 
						|
                    });
 | 
						|
                    t++;
 | 
						|
                    if (t == i) {
 | 
						|
                        complete.call(undefined, $els)
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            });
 | 
						|
            if (i == 0) {
 | 
						|
                complete.call(undefined, $els)
 | 
						|
            }
 | 
						|
        },
 | 
						|
 | 
						|
        append: function ($els) {
 | 
						|
            this._isResizing = true;
 | 
						|
            this._setBoxes($els, 'append');
 | 
						|
            this._isResizing = false;
 | 
						|
        },
 | 
						|
 | 
						|
        prepend: function ($els) {
 | 
						|
            this._isResizing = true;
 | 
						|
            this._setBoxes($els, 'prepend');
 | 
						|
            this._isResizing = false;
 | 
						|
        },
 | 
						|
 | 
						|
        resize: function ($els) {
 | 
						|
            if (Object.keys(this.matrix[0]).length % Math.floor(this.element.width() / (this.options.minWidth + this.options.gutter)) > 0) {
 | 
						|
                this._isResizing = true;
 | 
						|
                this._setBoxes(this.box.find(this.options.selector));
 | 
						|
                this._isResizing = false;
 | 
						|
            }
 | 
						|
        },
 | 
						|
      
 | 
						|
        refresh: function(options) {
 | 
						|
        	
 | 
						|
        	options = options || this.options;
 | 
						|
        
 | 
						|
            this.options = $.extend(true, {}, $.Nested.settings, options);
 | 
						|
            this.elements = [];
 | 
						|
            this._isResizing = false;
 | 
						|
 | 
						|
            // build box dimensions
 | 
						|
            this._setBoxes();
 | 
						|
        },
 | 
						|
        
 | 
						|
        destroy: function() {
 | 
						|
			
 | 
						|
			var container = this;
 | 
						|
 | 
						|
            $(window).unbind("resize", function () {
 | 
						|
                container.resize();
 | 
						|
            });
 | 
						|
	        
 | 
						|
	        // unbind the resize event
 | 
						|
            $els = this.box.find(this.options.selector);
 | 
						|
            $($els).removeClass('nested-moved').removeAttr('style data-box data-width data-x data-y').removeData();
 | 
						|
            
 | 
						|
            this.box.removeAttr("style").removeData();
 | 
						|
        }
 | 
						|
        
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
	var methods =
 | 
						|
	{
 | 
						|
		refresh: function(options) {
 | 
						|
			return this.each(function(){
 | 
						|
				var $this=$(this);
 | 
						|
				var nested = $this.data('nested');
 | 
						|
				
 | 
						|
				nested.refresh(options);
 | 
						|
			});
 | 
						|
		},
 | 
						|
		
 | 
						|
		destroy: function() {
 | 
						|
			return this.each(function(){
 | 
						|
				var $this=$(this);
 | 
						|
				var nested = $this.data('nested');
 | 
						|
				
 | 
						|
				nested.destroy();
 | 
						|
			});
 | 
						|
		}
 | 
						|
	};
 | 
						|
	
 | 
						|
	
 | 
						|
 | 
						|
    $.fn.nested = function (options, e) {
 | 
						|
 | 
						|
		if(methods[options]) {
 | 
						|
			return methods[options].apply(this, Array.prototype.slice.call(arguments, 1));
 | 
						|
		}        
 | 
						|
		
 | 
						|
		if (typeof options === 'string') {
 | 
						|
            this.each(function () {
 | 
						|
                var container = $.data(this, 'nested');
 | 
						|
                container[options].apply(container, [e]);
 | 
						|
            });
 | 
						|
        } else {
 | 
						|
            this.each(function () {
 | 
						|
                $.data(this, 'nested', new $.Nested(options, this));
 | 
						|
            });
 | 
						|
        }
 | 
						|
        return this;
 | 
						|
    }
 | 
						|
 | 
						|
})(jQuery);
 |