diff --git a/app/assets/Archive/javascripts/jquery.gridster.js b/app/assets/Archive/javascripts/jquery.gridster.js index e8998514..367c3b0e 100755 --- a/app/assets/Archive/javascripts/jquery.gridster.js +++ b/app/assets/Archive/javascripts/jquery.gridster.js @@ -1,6 +1,11 @@ -/*! gridster.js - v0.1.0 - 2012-10-20 -* http://gridster.net/ -* Copyright (c) 2012 ducksboard; Licensed MIT */ +/* + * jquery.coords + * https://github.com/ducksboard/gridster.js + * https://github.com/evaldsurtans/gridster.js/ + * + * Copyright (c) 2012 ducksboard + * Licensed under the MIT licenses. + */ ;(function($, window, document, undefined){ /** @@ -102,6 +107,13 @@ }; }(jQuery, window, document)); +;/* + * jquery.collision + * https://github.com/ducksboard/gridster.js + * + * Copyright (c) 2012 ducksboard + * Licensed under the MIT licenses. + */ ;(function($, window, document, undefined){ @@ -315,8 +327,7 @@ }(jQuery, window, document)); - -;(function(window, undefined) { +;;(function(window, undefined) { /* Debounce and throttle functions taken from underscore.js */ window.debounce = function(func, wait, immediate) { var timeout; @@ -357,6 +368,13 @@ }; })(window); +;/* + * jquery.draggable + * https://github.com/ducksboard/gridster.js + * + * Copyright (c) 2012 ducksboard + * Licensed under the MIT licenses. + */ ;(function($, window, document, undefined){ @@ -690,7 +708,7 @@ }; //jQuery adapter - $.fn.drag = function ( options ) { + $.fn.dragg = function ( options ) { return this.each(function () { if (!$.data(this, 'drag')) { $.data(this, 'drag', new Draggable( this, options )); @@ -700,2535 +718,2971 @@ }(jQuery, window, document)); +;/* + * jquery.gridster + * https://github.com/ducksboard/gridster.js + * + * Copyright (c) 2012 ducksboard + * Licensed under the MIT licenses. + */ +;( function($, window, document, undefined) { -;(function($, window, document, undefined) { - - var defaults = { - namespace: '', - widget_selector: 'li', - widget_margins: [10, 10], - widget_base_dimensions: [400, 225], - extra_rows: 0, - extra_cols: 0, - min_cols: 1, - min_rows: 15, - max_size_x: 6, - autogenerate_stylesheet: true, - avoid_overlapped_widgets: true, - serialize_params: function($w, wgd) { - return { - col: wgd.col, - row: wgd.row, - size_x: wgd.size_x, - size_y: wgd.size_y - }; - }, - collision: {}, - draggable: { - distance: 4 - } - }; - - - /** - * @class Gridster - * @uses Draggable - * @uses Collision - * @param {HTMLElement} el The HTMLelement that contains all the widgets. - * @param {Object} [options] An Object with all options you want to - * overwrite: - * @param {HTMLElement|String} [options.widget_selector] Define who will - * be the draggable widgets. Can be a CSS Selector String or a - * collection of HTMLElements - * @param {Array} [options.widget_margins] Margin between widgets. - * The first index for the horizontal margin (left, right) and - * the second for the vertical margin (top, bottom). - * @param {Array} [options.widget_base_dimensions] Base widget dimensions - * in pixels. The first index for the width and the second for the - * height. - * @param {Number} [options.extra_cols] Add more columns in addition to - * those that have been calculated. - * @param {Number} [options.extra_rows] Add more rows in addition to - * those that have been calculated. - * @param {Number} [options.min_cols] The minimum required columns. - * @param {Number} [options.min_rows] The minimum required rows. - * @param {Number} [options.max_size_x] The maximum number of columns - * that a widget can span. - * @param {Boolean} [options.autogenerate_stylesheet] If true, all the - * CSS required to position all widgets in their respective columns - * and rows will be generated automatically and injected to the - * `` of the document. You can set this to false, and write - * your own CSS targeting rows and cols via data-attributes like so: - * `[data-col="1"] { left: 10px; }` - * @param {Boolean} [options.avoid_overlapped_widgets] Avoid that widgets loaded - * from the DOM can be overlapped. It is helpful if the positions were - * bad stored in the database or if there was any conflict. - * @param {Function} [options.serialize_params] Return the data you want - * for each widget in the serialization. Two arguments are passed: - * `$w`: the jQuery wrapped HTMLElement, and `wgd`: the grid - * coords object (`col`, `row`, `size_x`, `size_y`). - * @param {Object} [options.collision] An Object with all options for - * Collision class you want to overwrite. See Collision docs for - * more info. - * @param {Object} [options.draggable] An Object with all options for - * Draggable class you want to overwrite. See Draggable docs for more - * info. - * - * @constructor - */ - function Gridster(el, options) { - this.options = $.extend(true, defaults, options); - this.$el = $(el); - this.$wrapper = this.$el.parent(); - this.$widgets = this.$el.children(this.options.widget_selector).addClass('gs_w'); - this.widgets = []; - this.$changed = $([]); - this.wrapper_width = this.$wrapper.width(); - this.min_widget_width = (this.options.widget_margins[0] * 2) + - this.options.widget_base_dimensions[0]; - this.min_widget_height = (this.options.widget_margins[1] * 2) + - this.options.widget_base_dimensions[1]; - this.init(); - } - - Gridster.generated_stylesheets = []; - - var fn = Gridster.prototype; - - fn.init = function() { - this.generate_grid_and_stylesheet(); - this.get_widgets_from_DOM(); - this.set_dom_grid_height(); - this.$wrapper.addClass('ready'); - this.draggable(); - - $(window).bind( - 'resize', throttle($.proxy(this.recalculate_faux_grid, this), 200)); - }; - - - /** - * Disables dragging. - * - * @method disable - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.disable = function() { - this.$wrapper.find('.player-revert').removeClass('player-revert'); - this.drag_api.disable(); - return this; - }; - - - /** - * Enables dragging. - * - * @method enable - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.enable = function() { - this.drag_api.enable(); - return this; - }; - - - /** - * Add a new widget to the grid. - * - * @method add_widget - * @param {String|HTMLElement} html The string representing the HTML of the widget - * or the HTMLElement. - * @param {Number} [size_x] The nº of rows the widget occupies horizontally. - * @param {Number} [size_y] The nº of columns the widget occupies vertically. - * @param {Number} [col] The column the widget should start in. - * @param {Number} [row] The row the widget should start in. - * @return {HTMLElement} Returns the jQuery wrapped HTMLElement representing. - * the widget that was just created. - */ - fn.add_widget = function(html, size_x, size_y, col, row) { - var pos; - size_x || (size_x = 1); - size_y || (size_y = 1); - - if (!col & !row) { - pos = this.next_position(size_x, size_y); - }else{ - pos = { - col: col, - row: row - }; - - this.empty_cells(col, row, size_x, size_y); - } - - var $w = $(html).attr({ - 'data-col': pos.col, - 'data-row': pos.row, - 'data-sizex' : size_x, - 'data-sizey' : size_y - }).addClass('gs_w').appendTo(this.$el).hide(); - - this.$widgets = this.$widgets.add($w); - - this.register_widget($w); - - this.add_faux_rows(pos.size_y); - //this.add_faux_cols(pos.size_x); - - this.set_dom_grid_height(); - - return $w.fadeIn(); - }; - - - - /** - * Change the size of a widget. - * - * @method resize_widget - * @param {HTMLElement} $widget The jQuery wrapped HTMLElement - * representing the widget. - * @param {Number} size_x The number of columns that will occupy the widget. - * @param {Number} size_y The number of rows that will occupy the widget. - * @return {HTMLElement} Returns $widget. - */ - fn.resize_widget = function($widget, size_x, size_y) { - var wgd = $widget.coords().grid; - size_x || (size_x = wgd.size_x); - size_y || (size_y = wgd.size_y); - - if (size_x > this.cols) { - size_x = this.cols; - } - - var old_cells_occupied = this.get_cells_occupied(wgd); - var old_size_x = wgd.size_x; - var old_size_y = wgd.size_y; - var old_col = wgd.col; - var new_col = old_col; - var wider = size_x > old_size_x; - var taller = size_y > old_size_y; - - if (old_col + size_x - 1 > this.cols) { - var diff = old_col + (size_x - 1) - this.cols; - var c = old_col - diff; - new_col = Math.max(1, c); - } - - var new_grid_data = { - col: new_col, - row: wgd.row, - size_x: size_x, - size_y: size_y + //ToDo Max_cols and Max_size_x conflict.. need to unify + var defaults = { + namespace : '', + widget_selector : 'li', + static_class : 'static', + widget_margins : [10, 10], + widget_base_dimensions : [400, 225], + extra_rows : 1, + extra_cols : 10, + min_cols : 1, + max_cols : 60, + min_rows : 15, + max_rows : 15, + max_size_x : 6, + enabled : true, + resize_handle : null, + resize_variations : [], + autogenerate_stylesheet : true, + avoid_overlapped_widgets : true, + shift_larger_widgets_down : true, + serialize_params : function($w, wgd) { + return { + col : wgd.col, + row : wgd.row, + size_x : wgd.size_x, + size_y : wgd.size_y + }; + }, + collision : {}, + draggable : { + distance : 4, + items : ".gs_w:not(.static)" + } }; - var new_cells_occupied = this.get_cells_occupied(new_grid_data); + /** + * @class Gridster + * @uses Draggable + * @uses Collision + * @param {HTMLElement} el The HTMLelement that contains all the widgets. + * @param {Object} [options] An Object with all options you want to + * overwrite: + * @param {HTMLElement|String} [options.widget_selector] Define who will + * be the draggable widgets. Can be a CSS Selector String or a + * collection of HTMLElements + * @param {Array} [options.widget_margins] Margin between widgets. + * The first index for the horizontal margin (left, right) and + * the second for the vertical margin (top, bottom). + * @param {Array} [options.widget_base_dimensions] Base widget dimensions + * in pixels. The first index for the width and the second for the + * height. + * @param {Number} [options.extra_cols] Add more columns in addition to + * those that have been calculated. + * @param {Number} [options.extra_rows] Add more rows in addition to + * those that have been calculated. + * @param {Number} [options.min_cols] The minimum required columns. + * @param {Number} [options.min_rows] The minimum required rows. + * @param {Number} [options.max_size_x] The maximum number of columns + * that a widget can span. + * @param {Boolean} [options.autogenerate_stylesheet] If true, all the + * CSS required to position all widgets in their respective columns + * and rows will be generated automatically and injected to the + * `` of the document. You can set this to false, and write + * your own CSS targeting rows and cols via data-attributes like so: + * `[data-col="1"] { left: 10px; }` + * @param {Boolean} [options.avoid_overlapped_widgets] Avoid that widgets loaded + * from the DOM can be overlapped. It is helpful if the positions were + * bad stored in the database or if there was any conflict. + * @param {Function} [options.serialize_params] Return the data you want + * for each widget in the serialization. Two arguments are passed: + * `$w`: the jQuery wrapped HTMLElement, and `wgd`: the grid + * coords object (`col`, `row`, `size_x`, `size_y`). + * @param {Object} [options.collision] An Object with all options for + * Collision class you want to overwrite. See Collision docs for + * more info. + * @param {Object} [options.draggable] An Object with all options for + * Draggable class you want to overwrite. See Draggable docs for more + * info. + * + * @constructor + */ + function Gridster(el, options) { + this.options = $.extend(true, defaults, options); + this.$el = $(el); + this.$wrapper = this.$el.parent(); + this.$widgets = this.$el.children(this.options.widget_selector).addClass('gs_w'); + this.widgets = []; + this.$changed = $([]); + this.w_queue = {}; + this.wrapper_width = this.$wrapper.width(); + this.min_widget_width = (this.options.widget_margins[0] * 2) + this.options.widget_base_dimensions[0]; + this.min_widget_height = (this.options.widget_margins[1] * 2) + this.options.widget_base_dimensions[1]; + this.init(); + } - var empty_cols = []; - $.each(old_cells_occupied.cols, function(i, col) { - if ($.inArray(col, new_cells_occupied.cols) === -1) { - empty_cols.push(col); + + Gridster.generated_stylesheets = []; + + var fn = Gridster.prototype; + + fn.init = function() { + this.generate_grid_and_stylesheet(); + this.get_widgets_from_DOM(); + this.set_dom_grid_height(); + this.$wrapper.addClass('ready'); + this.draggable(); + + if (!this.options.enabled) { + this.disable(); } - }); - var occupied_cols = []; - $.each(new_cells_occupied.cols, function(i, col) { - if ($.inArray(col, old_cells_occupied.cols) === -1) { - occupied_cols.push(col); + this.refresh_resize_handlers(); + + $(window).bind('resize', throttle($.proxy(this.recalculate_faux_grid, this), 200)); + }; + + fn.update_sizes = function(width, height, marginX, marginY) { + this.options.widget_base_dimensions = [width, height]; + this.options.widget_margins = [marginX, marginY]; + this.wrapper_width = this.$wrapper.width(); + this.min_widget_width = (this.options.widget_margins[0] * 2) + this.options.widget_base_dimensions[0]; + this.min_widget_height = (this.options.widget_margins[1] * 2) + this.options.widget_base_dimensions[1]; + this.generate_stylesheet(); + var self = this; + + self.set_dom_grid_height(); + setTimeout(function() { + self.set_dom_grid_height(); + }, 650); + }; + + /** + * Bind resize handlers + * + * @method refresh_resize_handlers + */ + fn.refresh_resize_handlers = function() { + if (this.options.resize_handle) { + this.$resizeHandle = $(this.options.resize_handle).filter(':not(.gridster-applied)').addClass('gridster-applied'); + this.$resizeHandle.bind('click', $.proxy(this.click_resize_handle, this)); } - }); + }; - var empty_rows = []; - $.each(old_cells_occupied.rows, function(i, row) { - if ($.inArray(row, new_cells_occupied.rows) === -1) { - empty_rows.push(row); + /** + * Release bindings + * + * @method destroy + */ + fn.destroy = function() { + $(window).unbind('resize'); + + if (this.$resizeHandle) { + this.$resizeHandle.unbind('click'); } - }); + }; - var occupied_rows = []; - $.each(new_cells_occupied.rows, function(i, row) { - if ($.inArray(row, old_cells_occupied.rows) === -1) { - occupied_rows.push(row); + /** + * Resize box + * + * @method click_resize_handle + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.click_resize_handle = function(event) { + + event.preventDefault(); + + if (!this.isEnabled()) { + return this; } - }); - this.remove_from_gridmap(wgd); + var $li = $(event.target).parents('li:first'); - if (occupied_cols.length) { - var cols_to_empty = [ - new_col, wgd.row, size_x, Math.min(old_size_y, size_y), $widget - ]; - this.empty_cells.apply(this, cols_to_empty); - } + for (var i = 0; i < this.options.resize_variations.length; i++) { + var variation = this.options.resize_variations[i]; - if (occupied_rows.length) { - var rows_to_empty = [new_col, wgd.row, size_x, size_y, $widget]; - this.empty_cells.apply(this, rows_to_empty); - } + if (parseInt($li.attr('data-sizex')) === variation.sizex && parseInt($li.attr('data-sizey')) === variation.sizey) { + i++; - wgd.col = new_col; - wgd.size_x = size_x; - wgd.size_y = size_y; - this.add_to_gridmap(new_grid_data, $widget); + if (i >= this.options.resize_variations.length) { + i = 0; + } - //update coords instance attributes - $widget.data('coords').update({ - width: (size_x * this.options.widget_base_dimensions[0] + - ((size_x - 1) * this.options.widget_margins[0]) * 2), - height: (size_y * this.options.widget_base_dimensions[1] + - ((size_y - 1) * this.options.widget_margins[1]) * 2) - }); + variation = this.options.resize_variations[i]; - if (size_y > old_size_y) { - this.add_faux_rows(size_y - old_size_y); - } + this.resize_widget($li, variation.sizex, variation.sizey); + this.$el.trigger('gridster:resize_handle'); - if (size_x > old_size_x) { - this.add_faux_cols(size_x - old_size_x); - } - - $widget.attr({ - 'data-col': new_col, - 'data-sizex': size_x, - 'data-sizey': size_y - }); - - if (empty_cols.length) { - var cols_to_remove_holes = [ - empty_cols[0], wgd.row, - empty_cols.length, - Math.min(old_size_y, size_y), - $widget - ]; - - this.remove_empty_cells.apply(this, cols_to_remove_holes); - } - - if (empty_rows.length) { - var rows_to_remove_holes = [ - new_col, wgd.row, size_x, size_y, $widget - ]; - this.remove_empty_cells.apply(this, rows_to_remove_holes); - } - - return $widget; - }; - - /** - * Move down widgets in cells represented by the arguments col, row, size_x, - * size_y - * - * @method empty_cells - * @param {Number} col The column where the group of cells begin. - * @param {Number} row The row where the group of cells begin. - * @param {Number} size_x The number of columns that the group of cells - * occupy. - * @param {Number} size_y The number of rows that the group of cells - * occupy. - * @param {HTMLElement} $exclude Exclude widgets from being moved. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.empty_cells = function(col, row, size_x, size_y, $exclude) { - var $nexts = this.widgets_below({ - col: col, - row: row - size_y, - size_x: size_x, - size_y: size_y - }); - - $nexts.not($exclude).each($.proxy(function(i, w) { - var wgd = $(w).coords().grid; - if (!(wgd.row <= (row + size_y - 1))) { return; } - var diff = (row + size_y) - wgd.row; - this.move_widget_down($(w), diff); - }, this)); - - this.set_dom_grid_height(); - - return this; - }; - - - /** - * Move up widgets below cells represented by the arguments col, row, size_x, - * size_y. - * - * @method remove_empty_cells - * @param {Number} col The column where the group of cells begin. - * @param {Number} row The row where the group of cells begin. - * @param {Number} size_x The number of columns that the group of cells - * occupy. - * @param {Number} size_y The number of rows that the group of cells - * occupy. - * @param {HTMLElement} $exclude Exclude widgets from being moved. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.remove_empty_cells = function(col, row, size_x, size_y, exclude) { - var $nexts = this.widgets_below({ - col: col, - row: row, - size_x: size_x, - size_y: size_y - }); - - $nexts.not(exclude).each($.proxy(function(i, widget) { - this.move_widget_up( $(widget), size_y ); - }, this)); - - this.set_dom_grid_height(); - - return this; - }; - - - /** - * Get the most left column below to add a new widget. - * - * @method next_position - * @param {Number} size_x The nº of rows the widget occupies horizontally. - * @param {Number} size_y The nº of columns the widget occupies vertically. - * @return {Object} Returns a grid coords object representing the future - * widget coords. - */ - fn.next_position = function(size_x, size_y) { - size_x || (size_x = 1); - size_y || (size_y = 1); - var ga = this.gridmap; - var cols_l = ga.length; - var valid_pos = []; - var rows_l; - - for (var c = 1; c < cols_l; c++) { - rows_l = ga[c].length; - for (var r = 1; r <= rows_l; r++) { - var can_move_to = this.can_move_to({ - size_x: size_x, - size_y: size_y - }, c, r); - - if (can_move_to) { - valid_pos.push({ - col: c, - row: r, - size_y: size_y, - size_x: size_x - }); + break; } } + + this.set_dom_grid_height(); + + return this; + }; + + /** + * Disables dragging. + * + * @method disable + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.disable = function() { + this.$wrapper.find('.player-revert').removeClass('player-revert'); + this.drag_api.disable(); + + this.$wrapper.removeClass('enabled'); + this.$wrapper.addClass('disabled'); + + return this; + }; + + /** + * Enables dragging. + * + * @method enable + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.enable = function() { + this.drag_api.enable(); + + this.$wrapper.removeClass('disabled'); + this.$wrapper.addClass('enabled'); + + return this; + }; + + /** + * Is in enabled-mode + * + * @method isEnabled + * @return {bool} + */ + fn.isEnabled = function() { + return !this.$wrapper.hasClass('disabled'); + }; + + /** + * Add a new widget to the grid. + * + * @method add_widget + * @param {String|HTMLElement} html The string representing the HTML of the widget + * or the HTMLElement. + * @param {Number} [size_x] The nº of rows the widget occupies horizontally. + * @param {Number} [size_y] The nº of columns the widget occupies vertically. + * @param {Number} [col] The column the widget should start in. + * @param {Number} [row] The row the widget should start in. + * @return {HTMLElement} Returns the jQuery wrapped HTMLElement representing. + * the widget that was just created. + */ + fn.add_widget = function(html, size_x, size_y, col, row) { + + var pos; + size_x || ( size_x = 1); + size_y || ( size_y = 1); + + if (!col & !row) { + pos = this.next_position(size_x, size_y); + } else { + pos = { + col : col, + row : row + }; + + this.empty_cells(col, row, size_x, size_y); + } + + var $w = $(html).attr({ + 'data-col' : pos.col, + 'data-row' : pos.row, + 'data-sizex' : size_x, + 'data-sizey' : size_y + }).addClass('gs_w').css('opacity', 0).show().appendTo(this.$el); + + this.$widgets = this.$widgets.add($w); + this.$changed = this.$changed.add($w); + + this.register_widget($w); + + this.add_faux_rows(pos.size_y); + //this.add_faux_cols(pos.size_x); + + this.set_dom_grid_height(); + + this.refresh_resize_handlers(); + + var self = this; + //for Each + setTimeout(function() { + $w.css('opacity', 1); + }, 200); + + clearTimeout(self.timeotThrottleAddWidget2); + self.timeotThrottleAddWidget2 = setTimeout(function() { + self.timeotThrottleAddWidget2 = 0; + self.set_dom_grid_height(); + }, 600); + + return $w; + }; + + /** + * Change the size of a widget. + * + * @method resize_widget + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement + * representing the widget. + * @param {Number} size_x The number of columns that will occupy the widget. + * @param {Number} size_y The number of rows that will occupy the widget. + * @return {HTMLElement} Returns $widget. + */ + fn.resize_widget = function($widget, size_x, size_y) { + + var wgd = $widget.coords().grid; + size_x || ( size_x = wgd.size_x); + size_y || ( size_y = wgd.size_y); + + if (size_x > this.cols) { + size_x = this.cols; + } + + var old_cells_occupied = this.get_cells_occupied(wgd); + var old_size_x = wgd.size_x; + var old_size_y = wgd.size_y; + var old_col = wgd.col; + var new_col = old_col; + var wider = size_x > old_size_x; + var taller = size_y > old_size_y; + + if (old_col + size_x - 1 > this.cols) { + var diff = old_col + (size_x - 1) - this.cols; + var c = old_col - diff; + new_col = Math.max(1, c); + } + + var new_grid_data = { + col : new_col, + row : wgd.row, + size_x : size_x, + size_y : size_y + }; + + var new_cells_occupied = this.get_cells_occupied(new_grid_data); + + var empty_cols = []; + $.each(old_cells_occupied.cols, function(i, col) { + if ($.inArray(col, new_cells_occupied.cols) === -1) { + empty_cols.push(col); + } + }); + + var occupied_cols = []; + $.each(new_cells_occupied.cols, function(i, col) { + if ($.inArray(col, old_cells_occupied.cols) === -1) { + occupied_cols.push(col); + } + }); + + var empty_rows = []; + $.each(old_cells_occupied.rows, function(i, row) { + if ($.inArray(row, new_cells_occupied.rows) === -1) { + empty_rows.push(row); + } + }); + + var occupied_rows = []; + $.each(new_cells_occupied.rows, function(i, row) { + if ($.inArray(row, old_cells_occupied.rows) === -1) { + occupied_rows.push(row); + } + }); + + this.remove_from_gridmap(wgd); + + if (occupied_cols.length) { + var cols_to_empty = [new_col, wgd.row, size_x, Math.min(old_size_y, size_y), $widget]; + this.empty_cells.apply(this, cols_to_empty); + } + + if (occupied_rows.length) { + var rows_to_empty = [new_col, wgd.row, size_x, size_y, $widget]; + this.empty_cells.apply(this, rows_to_empty); + } + + wgd.col = new_col; + wgd.size_x = size_x; + wgd.size_y = size_y; + this.add_to_gridmap(new_grid_data, $widget); + + //update coords instance attributes + $widget.data('coords').update({ + width : (size_x * this.options.widget_base_dimensions[0] + ((size_x - 1) * this.options.widget_margins[0]) * 2), + height : (size_y * this.options.widget_base_dimensions[1] + ((size_y - 1) * this.options.widget_margins[1]) * 2) + }); + + if (size_y > old_size_y) { + this.add_faux_rows(size_y - old_size_y); + } + + if (size_x > old_size_x) { + this.add_faux_cols(size_x - old_size_x); + } + + $widget.attr({ + 'data-col' : new_col, + 'data-sizex' : size_x, + 'data-sizey' : size_y + }); + + if (empty_cols.length) { + var cols_to_remove_holes = [empty_cols[0], wgd.row, empty_cols.length, Math.min(old_size_y, size_y), $widget]; + + this.remove_empty_cells.apply(this, cols_to_remove_holes); + } + + if (empty_rows.length) { + var rows_to_remove_holes = [new_col, wgd.row, size_x, size_y, $widget]; + this.remove_empty_cells.apply(this, rows_to_remove_holes); + } + + return $widget; + }; + + /** + * Move down widgets in cells represented by the arguments col, row, size_x, + * size_y + * + * @method empty_cells + * @param {Number} col The column where the group of cells begin. + * @param {Number} row The row where the group of cells begin. + * @param {Number} size_x The number of columns that the group of cells + * occupy. + * @param {Number} size_y The number of rows that the group of cells + * occupy. + * @param {HTMLElement} $exclude Exclude widgets from being moved. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.empty_cells = function(col, row, size_x, size_y, $exclude) { + var $nexts = this.widgets_below({ + col : col, + row : row - size_y, + size_x : size_x, + size_y : size_y + }); + + $nexts.not($exclude).each($.proxy(function(i, w) { + var wgd = $(w).coords().grid; + if (!wgd || !(wgd.row <= (row + size_y - 1))) { + return; + } + var diff = (row + size_y) - wgd.row; + this.move_widget_down($(w), diff); + }, this)); + + this.set_dom_grid_height(); + + return this; + }; + + /** + * Move up widgets below cells represented by the arguments col, row, size_x, + * size_y. + * + * @method remove_empty_cells + * @param {Number} col The column where the group of cells begin. + * @param {Number} row The row where the group of cells begin. + * @param {Number} size_x The number of columns that the group of cells + * occupy. + * @param {Number} size_y The number of rows that the group of cells + * occupy. + * @param {HTMLElement} $exclude Exclude widgets from being moved. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.remove_empty_cells = function(col, row, size_x, size_y, exclude) { + var $nexts = this.widgets_below({ + col : col, + row : row, + size_x : size_x, + size_y : size_y + }); + + $nexts.not(exclude).each($.proxy(function(i, widget) { + this.move_widget_up($(widget), size_y); + }, this)); + + this.set_dom_grid_height(); + + return this; + }; + + /** + * Get the most left column below to add a new widget. + * + * @method next_position + * @param {Number} size_x The nº of rows the widget occupies horizontally. + * @param {Number} size_y The nº of columns the widget occupies vertically. + * @return {Object} Returns a grid coords object representing the future + * widget coords. + */ + fn.next_position = function(size_x, size_y) { + size_x || ( size_x = 1); + size_y || ( size_y = 1); + var ga = this.gridmap; + var cols_l = ga.length; + var valid_pos = []; + var rows_l; + + for (var c = 1; c < cols_l; c++) { + rows_l = ga[c].length; + for (var r = 1; r <= rows_l; r++) { + var can_move_to = this.can_move_to({ + size_x : size_x, + size_y : size_y + }, c, r); + + if (can_move_to) { + valid_pos.push({ + col : c, + row : r, + size_y : size_y, + size_x : size_x + }); + } + } + } + + if (valid_pos.length) { + return this.sort_by_row_and_col_asc(valid_pos)[0]; + } + return false; + }; + + fn.remove_by_grid = function(col, row) { + var $w = this.is_widget(col, row); + if ($w) { + this.remove_widget($w); + } } + /** + * Remove a widget from the grid. + * + * @method remove_widget + * @param {HTMLElement} el The jQuery wrapped HTMLElement you want to remove. + * @param {Boolean|Function} silent If true, widgets below the removed one + * will not move up. If a Function is passed it will be used as callback. + * @param {Function} callback Function executed when the widget is removed. + * @param {Boolean} execute at once + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.remove_widget = function(el, silent, callback, noTimeout) { + var $el = el instanceof jQuery ? el : $(el); + var wgd = $el.coords().grid; - if (valid_pos.length) { - return this.sort_by_row_and_col_asc(valid_pos)[0]; - } - return false; - }; + // if silent is a function assume it's a callback + if ($.isFunction(silent)) { + callback = silent; + silent = false; + } + this.cells_occupied_by_placeholder = {}; + this.$widgets = this.$widgets.not($el); - /** - * Remove a widget from the grid. - * - * @method remove_widget - * @param {HTMLElement} el The jQuery wrapped HTMLElement you want to remove. - * @param {Boolean|Function} silent If true, widgets below the removed one - * will not move up. If a Function is passed it will be used as callback. - * @param {Function} callback Function executed when the widget is removed. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.remove_widget = function(el, silent, callback) { - var $el = el instanceof jQuery ? el : $(el); - var wgd = $el.coords().grid; + var $nexts = this.widgets_below($el); - // if silent is a function assume it's a callback - if ($.isFunction(silent)) { - callback = silent; - silent = false; - } + this.remove_from_gridmap(wgd); - this.cells_occupied_by_placeholder = {}; - this.$widgets = this.$widgets.not($el); + $el.removeClass('portfolio-top-box').css('opacity', 0); - var $nexts = this.widgets_below($el); + if (noTimeout) { + this.remove_widget_callback($nexts, $el, silent, wgd, callback); + } else { + setTimeout($.proxy(this.remove_widget_callback, this), 100, $nexts, $el, silent, wgd, callback); + } + }; - this.remove_from_gridmap(wgd); - - $el.fadeOut($.proxy(function() { + /* + * Callback to remove_widget + */ + fn.remove_widget_callback = function($nexts, $el, silent, wgd, callback) { $el.remove(); if (!silent) { $nexts.each($.proxy(function(i, widget) { - this.move_widget_up( $(widget), wgd.size_y ); + this.move_widget_up($(widget), wgd.size_y); }, this)); + + this.set_dom_grid_height(); } - this.set_dom_grid_height(); + var self = this; + clearTimeout(this.timeoutThrottleRemoveCallback); + this.timeoutThrottleRemoveCallback = setTimeout(function() { + self.timeoutThrottleRemoveCallback = 0; + self.set_dom_grid_height(); + }, 600); if (callback) { callback.call(this, el); } - }, this)); - }; - - - /** - * Remove all widgets from the grid. - * - * @method remove_all_widgets - * @param {Function} callback Function executed for each widget removed. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.remove_all_widgets = function(callback) { - this.$widgets.each($.proxy(function(i, el){ - this.remove_widget(el, true, callback); - }, this)); - - return this; - }; - - - /** - * Returns a serialized array of the widgets in the grid. - * - * @method serialize - * @param {HTMLElement} [$widgets] The collection of jQuery wrapped - * HTMLElements you want to serialize. If no argument is passed all widgets - * will be serialized. - * @return {Array} Returns an Array of Objects with the data specified in - * the serialize_params option. - */ - fn.serialize = function($widgets) { - $widgets || ($widgets = this.$widgets); - var result = []; - $widgets.each($.proxy(function(i, widget) { - result.push(this.options.serialize_params( - $(widget), $(widget).coords().grid ) ); - }, this)); - - return result; - }; - - - /** - * Returns a serialized array of the widgets that have changed their - * position. - * - * @method serialize_changed - * @return {Array} Returns an Array of Objects with the data specified in - * the serialize_params option. - */ - fn.serialize_changed = function() { - return this.serialize(this.$changed); - }; - - - /** - * Creates the grid coords object representing the widget a add it to the - * mapped array of positions. - * - * @method register_widget - * @return {Array} Returns the instance of the Gridster class. - */ - fn.register_widget = function($el) { - - var wgd = { - 'col': parseInt($el.attr('data-col'), 10), - 'row': parseInt($el.attr('data-row'), 10), - 'size_x': parseInt($el.attr('data-sizex'), 10), - 'size_y': parseInt($el.attr('data-sizey'), 10), - 'el': $el }; - if (this.options.avoid_overlapped_widgets && - !this.can_move_to( - {size_x: wgd.size_x, size_y: wgd.size_y}, wgd.col, wgd.row) - ) { - wgd = this.next_position(wgd.size_x, wgd.size_y); - wgd.el = $el; - $el.attr({ - 'data-col': wgd.col, - 'data-row': wgd.row, - 'data-sizex': wgd.size_x, - 'data-sizey': wgd.size_y - }); - } - - // attach Coord object to player data-coord attribute - $el.data('coords', $el.coords()); - - // Extend Coord object with grid position info - $el.data('coords').grid = wgd; - - this.add_to_gridmap(wgd, $el); - - return this; - }; - - - /** - * Update in the mapped array of positions the value of cells represented by - * the grid coords object passed in the `grid_data` param. - * - * @param {Object} grid_data The grid coords object representing the cells - * to update in the mapped array. - * @param {HTMLElement|Boolean} value Pass `false` or the jQuery wrapped - * HTMLElement, depends if you want to delete an existing position or add - * a new one. - * @method update_widget_position - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.update_widget_position = function(grid_data, value) { - this.for_each_cell_occupied(grid_data, function(col, row) { - if (!this.gridmap[col]) { return this; } - this.gridmap[col][row] = value; - }); - return this; - }; - - - /** - * Remove a widget from the mapped array of positions. - * - * @method remove_from_gridmap - * @param {Object} grid_data The grid coords object representing the cells - * to update in the mapped array. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.remove_from_gridmap = function(grid_data) { - return this.update_widget_position(grid_data, false); - }; - - - /** - * Add a widget to the mapped array of positions. - * - * @method add_to_gridmap - * @param {Object} grid_data The grid coords object representing the cells - * to update in the mapped array. - * @param {HTMLElement|Boolean} value The value to set in the specified - * position . - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.add_to_gridmap = function(grid_data, value) { - this.update_widget_position(grid_data, value || grid_data.el); - - if (grid_data.el) { - var $widgets = this.widgets_below(grid_data.el); - $widgets.each($.proxy(function(i, widget) { - this.move_widget_up( $(widget)); + /** + * Remove all widgets from the grid. + * + * @method remove_all_widgets + * @param {Function} callback Function executed for each widget removed. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.remove_all_widgets = function(callback) { + this.$widgets.each($.proxy(function(i, el) { + this.remove_widget(el, true, callback); }, this)); - } - }; - - /** - * Make widgets draggable. - * - * @uses Draggable - * @method draggable - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.draggable = function() { - var self = this; - - var draggable_options = $.extend(true, {}, this.options.draggable, { - offset_left: this.options.widget_margins[0], - start: function(event, ui) { - self.$widgets.filter('.player-revert') - .removeClass('player-revert'); - - self.$player = $(this); - self.$helper = self.options.draggable.helper === 'clone' ? - $(ui.helper) : self.$player; - self.helper = !self.$helper.is(self.$player); - - self.on_start_drag.call(self, event, ui); - self.$el.trigger('gridster:dragstart'); - }, - stop: function(event, ui) { - self.on_stop_drag.call(self, event, ui); - self.$el.trigger('gridster:dragstop'); - }, - drag: throttle(function(event, ui) { - self.on_drag.call(self, event, ui); - self.$el.trigger('gridster:drag'); - }, 60) - }); - - this.drag_api = this.$el.drag(draggable_options).data('drag'); - return this; - }; - - - /** - * This function is executed when the player begins to be dragged. - * - * @method on_start_drag - * @param {Event} The original browser event - * @param {Object} A prepared ui object. - */ - fn.on_start_drag = function(event, ui) { - - this.$helper.add(this.$player).add(this.$wrapper).addClass('dragging'); - - this.$player.addClass('player'); - this.player_grid_data = this.$player.coords().grid; - this.placeholder_grid_data = $.extend({}, this.player_grid_data); - - //set new grid height along the dragging period - this.$el.css('height', this.$el.height() + - (this.player_grid_data.size_y * this.min_widget_height)); - - var colliders = this.faux_grid; - var coords = this.$player.data('coords').coords; - - this.cells_occupied_by_player = this.get_cells_occupied( - this.player_grid_data); - this.cells_occupied_by_placeholder = this.get_cells_occupied( - this.placeholder_grid_data); - - this.last_cols = []; - this.last_rows = []; - - - // see jquery.collision.js - this.collision_api = this.$helper.collision( - colliders, this.options.collision); - - this.$preview_holder = $('
  • ', { - 'class': 'preview-holder', - 'data-row': this.$player.attr('data-row'), - 'data-col': this.$player.attr('data-col'), - css: { - width: coords.width, - height: coords.height - } - }).appendTo(this.$el); - - if (this.options.draggable.start) { - this.options.draggable.start.call(this, event, ui); - } - }; - - - /** - * This function is executed when the player is being dragged. - * - * @method on_drag - * @param {Event} The original browser event - * @param {Object} A prepared ui object. - */ - fn.on_drag = function(event, ui) { - //break if dragstop has been fired - if (this.$player === null) { - return false; - } - - var abs_offset = { - left: ui.position.left + this.baseX, - top: ui.position.top + this.baseY + return this; }; - this.colliders_data = this.collision_api.get_closest_colliders( - abs_offset); + /** + * Returns a serialized array of the widgets in the grid. + * + * @method serialize + * @param {HTMLElement} [$widgets] The collection of jQuery wrapped + * HTMLElements you want to serialize. If no argument is passed all widgets + * will be serialized. + * @return {Array} Returns an Array of Objects with the data specified in + * the serialize_params option. + */ + fn.serialize = function($widgets) { + $widgets || ( $widgets = this.$widgets); + var result = []; + $widgets.each($.proxy(function(i, widget) { + if ( typeof ($(widget).coords().grid) != "undefined") { + result.push(this.options.serialize_params($(widget), $(widget).coords().grid)); + } + }, this)); - this.on_overlapped_column_change( - this.on_start_overlapping_column, - this.on_stop_overlapping_column - ); + return result; + }; - this.on_overlapped_row_change( - this.on_start_overlapping_row, - this.on_stop_overlapping_row - ); + /** + * Returns a serialized array of the widgets that have changed their + * position. + * + * @method serialize_changed + * @return {Array} Returns an Array of Objects with the data specified in + * the serialize_params option. + */ + fn.serialize_changed = function() { + return this.serialize(this.$changed); + }; - if (this.helper && this.$player) { - this.$player.css({ - 'left': ui.position.left, - 'top': ui.position.top + /** + * Creates the grid coords object representing the widget a add it to the + * mapped array of positions. + * + * @method register_widget + * @return {Array} Returns the instance of the Gridster class. + */ + fn.register_widget = function($el) { + + var wgd = { + 'col' : parseFloat($el.attr('data-col')), + 'row' : parseFloat($el.attr('data-row')), + 'size_x' : parseFloat($el.attr('data-sizex')), + 'size_y' : parseFloat($el.attr('data-sizey')), + 'el' : $el + }; + + if (this.options.avoid_overlapped_widgets && !this.can_move_to({ + size_x : wgd.size_x, + size_y : wgd.size_y + }, wgd.col, wgd.row)) { + if (!$el.hasClass('.disp_ad')) { + $el.remove(); + return false; + } + wgd = this.next_position(wgd.size_x, wgd.size_y); + wgd.el = $el; + $el.attr({ + 'data-col' : wgd.col, + 'data-row' : wgd.row, + 'data-sizex' : wgd.size_x, + 'data-sizey' : wgd.size_y + }); + } + + // attach Coord object to player data-coord attribute + $el.data('coords', $el.coords()); + + // Extend Coord object with grid position info + $el.data('coords').grid = wgd; + + this.add_to_gridmap(wgd, $el); + + return this; + }; + + /** + * Update in the mapped array of positions the value of cells represented by + * the grid coords object passed in the `grid_data` param. + * + * @param {Object} grid_data The grid coords object representing the cells + * to update in the mapped array. + * @param {HTMLElement|Boolean} value Pass `false` or the jQuery wrapped + * HTMLElement, depends if you want to delete an existing position or add + * a new one. + * @method update_widget_position + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.update_widget_position = function(grid_data, value) { + this.for_each_cell_occupied(grid_data, function(col, row) { + if (!this.gridmap[col]) { + return this; + } + this.gridmap[col][row] = value; }); - } + return this; + }; - if (this.options.draggable.drag) { - this.options.draggable.drag.call(this, event, ui); - } - }; + /** + * Remove a widget from the mapped array of positions. + * + * @method remove_from_gridmap + * @param {Object} grid_data The grid coords object representing the cells + * to update in the mapped array. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.remove_from_gridmap = function(grid_data) { + return this.update_widget_position(grid_data, false); + }; - /** - * This function is executed when the player stops being dragged. - * - * @method on_stop_drag - * @param {Event} The original browser event - * @param {Object} A prepared ui object. - */ - fn.on_stop_drag = function(event, ui) { - this.$helper.add(this.$player).add(this.$wrapper) - .removeClass('dragging'); + /** + * Add a widget to the mapped array of positions. + * + * @method add_to_gridmap + * @param {Object} grid_data The grid coords object representing the cells + * to update in the mapped array. + * @param {HTMLElement|Boolean} value The value to set in the specified + * position . + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.add_to_gridmap = function(grid_data, value) { + this.update_widget_position(grid_data, value || grid_data.el); + /*if (grid_data.el) { + var $widgets = this.widgets_below(grid_data.el); + $widgets.each($.proxy(function(i, widget) { + console.log("from_add_to_gridmap"); + this.move_widget_up( $(widget)); + }, this)); + } */ + }; - ui.position.left = ui.position.left + this.baseX; - ui.position.top = ui.position.top + this.baseY; - this.colliders_data = this.collision_api.get_closest_colliders(ui.position); + /** + * Make widgets draggable. + * + * @uses Draggable + * @method draggable + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.draggable = function() { + var self = this; + var draggable_options = $.extend(true, {}, this.options.draggable, { + offset_left : this.options.widget_margins[0], + start : function(event, ui) { + self.$widgets.filter('.player-revert').removeClass('player-revert'); - this.on_overlapped_column_change( - this.on_start_overlapping_column, - this.on_stop_overlapping_column - ); + self.$player = $(this); + self.$helper = self.options.draggable.helper === 'clone' ? $(ui.helper) : self.$player; + self.helper = !self.$helper.is(self.$player); - this.on_overlapped_row_change( - this.on_start_overlapping_row, - this.on_stop_overlapping_row - ); + self.on_start_drag.call(self, event, ui); + self.$el.trigger('gridster:dragstart'); + }, + stop : function(event, ui) { + self.on_stop_drag.call(self, event, ui); + self.$el.trigger('gridster:dragstop'); + }, + drag : throttle(function(event, ui) { + self.on_drag.call(self, event, ui); + self.$el.trigger('gridster:drag'); + }, 60) + }); - this.$player.addClass('player-revert').removeClass('player') - .attr({ - 'data-col': this.placeholder_grid_data.col, - 'data-row': this.placeholder_grid_data.row + this.drag_api = this.$el.dragg(draggable_options).data('drag'); + return this; + }; + + /** + * This function is executed when the player begins to be dragged. + * + * @method on_start_drag + * @param {Event} The original browser event + * @param {Object} A prepared ui object. + */ + fn.on_start_drag = function(event, ui) { + + this.$helper.add(this.$player).add(this.$wrapper).addClass('dragging'); + + this.$player.addClass('player'); + this.player_grid_data = this.$player.coords().grid; + this.placeholder_grid_data = $.extend({}, this.player_grid_data); + + //set new grid height along the dragging period + this.$el.css('height', this.$el.height() + (this.player_grid_data.size_y * this.min_widget_height)); + + var colliders = this.faux_grid; + var coords = this.$player.data('coords').coords; + + this.cells_occupied_by_player = this.get_cells_occupied(this.player_grid_data); + this.cells_occupied_by_placeholder = this.get_cells_occupied(this.placeholder_grid_data); + + this.last_cols = []; + this.last_rows = []; + + // see jquery.collision.js + this.collision_api = this.$helper.collision(colliders, this.options.collision); + + this.$preview_holder = $('
  • ', { + 'class' : 'preview-holder', + 'data-row' : this.$player.attr('data-row'), + 'data-col' : this.$player.attr('data-col'), + css : { + width : coords.width, + height : coords.height + } + }).appendTo(this.$el); + + if (this.options.draggable.start) { + this.options.draggable.start.call(this, event, ui); + } + }; + + /** + * This function is executed when the player is being dragged. + * + * @method on_drag + * @param {Event} The original browser event + * @param {Object} A prepared ui object. + */ + fn.on_drag = function(event, ui) { + //break if dragstop has been fired + if (this.$player === null) { + return false; + } + + var abs_offset = { + left : ui.position.left + this.baseX, + top : ui.position.top + this.baseY + }; + + this.colliders_data = this.collision_api.get_closest_colliders(abs_offset); + + this.on_overlapped_column_change(this.on_start_overlapping_column, this.on_stop_overlapping_column); + + this.on_overlapped_row_change(this.on_start_overlapping_row, this.on_stop_overlapping_row); + + if (this.helper && this.$player) { + this.$player.css({ + 'left' : ui.position.left, + 'top' : ui.position.top + }); + } + + if (this.options.draggable.drag) { + this.options.draggable.drag.call(this, event, ui); + } + }; + + /** + * This function is executed when the player stops being dragged. + * + * @method on_stop_drag + * @param {Event} The original browser event + * @param {Object} A prepared ui object. + */ + fn.on_stop_drag = function(event, ui) { + this.$helper.add(this.$player).add(this.$wrapper).removeClass('dragging'); + + ui.position.left = ui.position.left + this.baseX; + ui.position.top = ui.position.top + this.baseY; + this.colliders_data = this.collision_api.get_closest_colliders(ui.position); + + this.on_overlapped_column_change(this.on_start_overlapping_column, this.on_stop_overlapping_column); + + this.on_overlapped_row_change(this.on_start_overlapping_row, this.on_stop_overlapping_row); + + this.$player.addClass('player-revert').removeClass('player').attr({ + 'data-col' : this.placeholder_grid_data.col, + 'data-row' : this.placeholder_grid_data.row }).css({ - 'left': '', - 'top': '' + 'left' : '', + 'top' : '' }); - this.$changed = this.$changed.add(this.$player); + this.$changed = this.$changed.add(this.$player); - this.cells_occupied_by_player = this.get_cells_occupied( - this.placeholder_grid_data); - this.set_cells_player_occupies( - this.placeholder_grid_data.col, this.placeholder_grid_data.row); + this.cells_occupied_by_player = this.get_cells_occupied(this.placeholder_grid_data); + this.set_cells_player_occupies(this.placeholder_grid_data.col, this.placeholder_grid_data.row); - this.$player.coords().grid.row = this.placeholder_grid_data.row; - this.$player.coords().grid.col = this.placeholder_grid_data.col; + this.$player.coords().grid.row = this.placeholder_grid_data.row; + this.$player.coords().grid.col = this.placeholder_grid_data.col; - if (this.options.draggable.stop) { - this.options.draggable.stop.call(this, event, ui); - } - - this.$preview_holder.remove(); - - this.$player = null; - this.$helper = null; - this.placeholder_grid_data = {}; - this.player_grid_data = {}; - this.cells_occupied_by_placeholder = {}; - this.cells_occupied_by_player = {}; - - this.set_dom_grid_height(); - }; - - - /** - * Executes the callbacks passed as arguments when a column begins to be - * overlapped or stops being overlapped. - * - * @param {Function} start_callback Function executed when a new column - * begins to be overlapped. The column is passed as first argument. - * @param {Function} stop_callback Function executed when a column stops - * being overlapped. The column is passed as first argument. - * @method on_overlapped_column_change - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.on_overlapped_column_change = function(start_callback, stop_callback) { - if (!this.colliders_data.length) { - return; - } - var cols = this.get_targeted_columns( - this.colliders_data[0].el.data.col); - - var last_n_cols = this.last_cols.length; - var n_cols = cols.length; - var i; - - for (i = 0; i < n_cols; i++) { - if ($.inArray(cols[i], this.last_cols) === -1) { - (start_callback || $.noop).call(this, cols[i]); + if (this.options.draggable.stop) { + this.options.draggable.stop.call(this, event, ui); } - } - for (i = 0; i< last_n_cols; i++) { - if ($.inArray(this.last_cols[i], cols) === -1) { - (stop_callback || $.noop).call(this, this.last_cols[i]); - } - } + this.$preview_holder.remove(); - this.last_cols = cols; + this.$player = null; + this.$helper = null; + this.placeholder_grid_data = {}; + this.player_grid_data = {}; + this.cells_occupied_by_placeholder = {}; + this.cells_occupied_by_player = {}; + this.w_queue = {}; - return this; - }; - - - /** - * Executes the callbacks passed as arguments when a row starts to be - * overlapped or stops being overlapped. - * - * @param {Function} start_callback Function executed when a new row begins - * to be overlapped. The row is passed as first argument. - * @param {Function} stop_callback Function executed when a row stops being - * overlapped. The row is passed as first argument. - * @method on_overlapped_row_change - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.on_overlapped_row_change = function(start_callback, end_callback) { - if (!this.colliders_data.length) { - return; - } - var rows = this.get_targeted_rows(this.colliders_data[0].el.data.row); - var last_n_rows = this.last_rows.length; - var n_rows = rows.length; - var i; - - for (i = 0; i < n_rows; i++) { - if ($.inArray(rows[i], this.last_rows) === -1) { - (start_callback || $.noop).call(this, rows[i]); - } - } - - for (i = 0; i < last_n_rows; i++) { - if ($.inArray(this.last_rows[i], rows) === -1) { - (end_callback || $.noop).call(this, this.last_rows[i]); - } - } - - this.last_rows = rows; - }; - - - /** - * Sets the current position of the player - * - * @param {Function} start_callback Function executed when a new row begins - * to be overlapped. The row is passed as first argument. - * @param {Function} stop_callback Function executed when a row stops being - * overlapped. The row is passed as first argument. - * @method set_player - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.set_player = function(col, row, no_player) { - var self = this; - if (!no_player) { - this.empty_cells_player_occupies(); - } - var cell = !no_player ? self.colliders_data[0].el.data : {col: col}; - var to_col = cell.col; - var to_row = row || cell.row; - - this.player_grid_data = { - col: to_col, - row: to_row, - size_y : this.player_grid_data.size_y, - size_x : this.player_grid_data.size_x + this.set_dom_grid_height(); }; - this.cells_occupied_by_player = this.get_cells_occupied( - this.player_grid_data); - - var $overlapped_widgets = this.get_widgets_overlapped( - this.player_grid_data); - - var constraints = this.widgets_constraints($overlapped_widgets); - - this.manage_movements(constraints.can_go_up, to_col, to_row); - this.manage_movements(constraints.can_not_go_up, to_col, to_row); - - /* if there is not widgets overlapping in the new player position, - * update the new placeholder position. */ - if (!$overlapped_widgets.length) { - var pp = this.can_go_player_up(this.player_grid_data); - if (pp !== false) { - to_row = pp; + /** + * Executes the callbacks passed as arguments when a column begins to be + * overlapped or stops being overlapped. + * + * @param {Function} start_callback Function executed when a new column + * begins to be overlapped. The column is passed as first argument. + * @param {Function} stop_callback Function executed when a column stops + * being overlapped. The column is passed as first argument. + * @method on_overlapped_column_change + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.on_overlapped_column_change = function(start_callback, stop_callback) { + if (!this.colliders_data.length) { + return; } - this.set_placeholder(to_col, to_row); - } + var cols = this.get_targeted_columns(this.colliders_data[0].el.data.col); - return { - col: to_col, - row: to_row + var last_n_cols = this.last_cols.length; + var n_cols = cols.length; + var i; + + for ( i = 0; i < n_cols; i++) { + if ($.inArray(cols[i], this.last_cols) === -1) { + (start_callback || $.noop).call(this, cols[i]); + } + } + + for ( i = 0; i < last_n_cols; i++) { + if ($.inArray(this.last_cols[i], cols) === -1) { + (stop_callback || $.noop).call(this, this.last_cols[i]); + } + } + + this.last_cols = cols; + + return this; }; - }; - - /** - * See which of the widgets in the $widgets param collection can go to - * a upper row and which not. - * - * @method widgets_contraints - * @param {HTMLElements} $widgets A jQuery wrapped collection of - * HTMLElements. - * @return {Array} Returns a literal Object with two keys: `can_go_up` & - * `can_not_go_up`. Each contains a set of HTMLElements. - */ - fn.widgets_constraints = function($widgets) { - var $widgets_can_go_up = $([]); - var $widgets_can_not_go_up; - var wgd_can_go_up = []; - var wgd_can_not_go_up = []; - - $widgets.each($.proxy(function(i, w) { - var $w = $(w); - var wgd = $w.coords().grid; - if (this.can_go_widget_up(wgd)) { - $widgets_can_go_up = $widgets_can_go_up.add($w); - wgd_can_go_up.push(wgd); - }else{ - wgd_can_not_go_up.push(wgd); + /** + * Executes the callbacks passed as arguments when a row starts to be + * overlapped or stops being overlapped. + * + * @param {Function} start_callback Function executed when a new row begins + * to be overlapped. The row is passed as first argument. + * @param {Function} stop_callback Function executed when a row stops being + * overlapped. The row is passed as first argument. + * @method on_overlapped_row_change + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.on_overlapped_row_change = function(start_callback, end_callback) { + if (!this.colliders_data.length) { + return; } - }, this)); + var rows = this.get_targeted_rows(this.colliders_data[0].el.data.row); + var last_n_rows = this.last_rows.length; + var n_rows = rows.length; + var i; - $widgets_can_not_go_up = $widgets.not($widgets_can_go_up); + for ( i = 0; i < n_rows; i++) { + if ($.inArray(rows[i], this.last_rows) === -1) { + (start_callback || $.noop).call(this, rows[i]); + } + } - return { - can_go_up: this.sort_by_row_asc(wgd_can_go_up), - can_not_go_up: this.sort_by_row_desc(wgd_can_not_go_up) + for ( i = 0; i < last_n_rows; i++) { + if ($.inArray(this.last_rows[i], rows) === -1) { + (end_callback || $.noop).call(this, this.last_rows[i]); + } + } + + this.last_rows = rows; }; - }; + /** + * Sets the current position of the player + * + * @param {Function} start_callback Function executed when a new row begins + * to be overlapped. The row is passed as first argument. + * @param {Function} stop_callback Function executed when a row stops being + * overlapped. The row is passed as first argument. + * @method set_player + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.set_player = function(col, row, no_player) { + var self = this; + var swap = false; + if (!no_player) { + this.empty_cells_player_occupies(); + } + var cell = !no_player ? self.colliders_data[0].el.data : { + col : col + }; + var to_col = cell.col; + var to_row = cell.row || row; - /** - * Sorts an Array of grid coords objects (representing the grid coords of - * each widget) in ascending way. - * - * @method sort_by_row_asc - * @param {Array} widgets Array of grid coords objects - * @return {Array} Returns the array sorted. - */ - fn.sort_by_row_asc = function(widgets) { - widgets = widgets.sort(function(a, b) { - if (!a.row) { - a = $(a).coords().grid; - b = $(b).coords().grid; + this.player_grid_data = { + col : to_col, + row : to_row, + size_y : this.player_grid_data.size_y, + size_x : this.player_grid_data.size_x + }; + + this.cells_occupied_by_player = this.get_cells_occupied(this.player_grid_data); + + //Added placeholder for more advanced movement. + this.cells_occupied_by_placeholder = this.get_cells_occupied(this.placeholder_grid_data); + + var $overlapped_widgets = this.get_widgets_overlapped(this.player_grid_data); + + var player_size_y = this.player_grid_data.size_y; + var player_size_x = this.player_grid_data.size_x; + var placeholder_cells = this.cells_occupied_by_placeholder; + var $gr = this; + + //Queue Swaps + $overlapped_widgets.each($.proxy(function(i, w) { + var $w = $(w); + var wgd = $w.coords().grid; + var outside_col = placeholder_cells.cols[0] + player_size_x - 1; + var outside_row = placeholder_cells.rows[0] + player_size_y - 1; + if ($w.hasClass($gr.options.static_class)) { + //next iteration + return true; + } + if (wgd.size_x <= player_size_x && wgd.size_y <= player_size_y) { + if (!$gr.is_swap_occupied(placeholder_cells.cols[0], wgd.row, wgd.size_x, wgd.size_y) && !$gr.is_player_in(placeholder_cells.cols[0], wgd.row) && !$gr.is_in_queue(placeholder_cells.cols[0], wgd.row, $w)) { + swap = $gr.queue_widget(placeholder_cells.cols[0], wgd.row, $w); + } else if (!$gr.is_swap_occupied(outside_col, wgd.row, wgd.size_x, wgd.size_y) && !$gr.is_player_in(outside_col, wgd.row) && !$gr.is_in_queue(outside_col, wgd.row, $w)) { + swap = $gr.queue_widget(outside_col, wgd.row, $w); + } else if (!$gr.is_swap_occupied(wgd.col, placeholder_cells.rows[0], wgd.size_x, wgd.size_y) && !$gr.is_player_in(wgd.col, placeholder_cells.rows[0]) && !$gr.is_in_queue(wgd.col, placeholder_cells.rows[0], $w)) { + swap = $gr.queue_widget(wgd.col, placeholder_cells.rows[0], $w); + } else if (!$gr.is_swap_occupied(wgd.col, outside_row, wgd.size_x, wgd.size_y) && !$gr.is_player_in(wgd.col, outside_row) && !$gr.is_in_queue(wgd.col, outside_row, $w)) { + swap = $gr.queue_widget(wgd.col, outside_row, $w); + } else if (!$gr.is_swap_occupied(placeholder_cells.cols[0], placeholder_cells.rows[0], wgd.size_x, wgd.size_y) && !$gr.is_player_in(placeholder_cells.cols[0], placeholder_cells.rows[0]) && !$gr.is_in_queue(placeholder_cells.cols[0], placeholder_cells.rows[0], $w)) { + swap = $gr.queue_widget(placeholder_cells.cols[0], placeholder_cells.rows[0], $w); + } else { + //in one last attempt we check for any other empty spaces + for (var c = 0; c < player_size_x; c++) { + for (var r = 0; r < player_size_y; r++) { + var colc = placeholder_cells.cols[0] + c; + var rowc = placeholder_cells.rows[0] + r; + if (!$gr.is_swap_occupied(colc, rowc, wgd.size_x, wgd.size_y) && !$gr.is_player_in(colc, rowc) && !$gr.is_in_queue(colc, rowc, $w)) { + swap = $gr.queue_widget(colc, rowc, $w); + c = player_size_x; + break; + } + } + } + + } + } else if ($gr.options.shift_larger_widgets_down && !swap) { + $overlapped_widgets.each($.proxy(function(i, w) { + var $w = $(w); + var wgd = $w.coords().grid; + + if ($gr.can_go_down($w)) { + $gr.move_widget_down($w, $gr.player_grid_data.size_y); + $gr.set_placeholder(to_col, to_row); + } + })); + } + + $gr.clean_up_changed(); + })); + + /* To show queued items in console + for(var key in this.w_queue){ + console.log("key " +key); + console.log(this.w_queue[key]); + } + */ + + //Move queued widgets + if (swap && this.can_placeholder_be_set(to_col, to_row, player_size_x, player_size_y)) { + for (var key in this.w_queue) { + var col = parseInt(key.split("_")[0]); + var row = parseInt(key.split("_")[1]); + if (this.w_queue[key] != "full") { + this.new_move_widget_to_legacy(this.w_queue[key], col, row); + } + } + this.set_placeholder(to_col, to_row); } - if (a.row > b.row) { - return 1; - } - return -1; - }); - - return widgets; - }; - - - /** - * Sorts an Array of grid coords objects (representing the grid coords of - * each widget) placing first the empty cells upper left. - * - * @method sort_by_row_and_col_asc - * @param {Array} widgets Array of grid coords objects - * @return {Array} Returns the array sorted. - */ - fn.sort_by_row_and_col_asc = function(widgets) { - widgets = widgets.sort(function(a, b) { - if (a.row > b.row || a.row === b.row && a.col > b.col) { - return 1; - } - return -1; - }); - - return widgets; - }; - - - /** - * Sorts an Array of grid coords objects by column (representing the grid - * coords of each widget) in ascending way. - * - * @method sort_by_col_asc - * @param {Array} widgets Array of grid coords objects - * @return {Array} Returns the array sorted. - */ - fn.sort_by_col_asc = function(widgets) { - widgets = widgets.sort(function(a, b) { - if (a.col > b.col) { - return 1; - } - return -1; - }); - - return widgets; - }; - - - /** - * Sorts an Array of grid coords objects (representing the grid coords of - * each widget) in descending way. - * - * @method sort_by_row_desc - * @param {Array} widgets Array of grid coords objects - * @return {Array} Returns the array sorted. - */ - fn.sort_by_row_desc = function(widgets) { - widgets = widgets.sort(function(a, b) { - if (a.row + a.size_y < b.row + b.size_y) { - return 1; - } - return -1; - }); - return widgets; - }; - - - /** - * Sorts an Array of grid coords objects (representing the grid coords of - * each widget) in descending way. - * - * @method manage_movements - * @param {HTMLElements} $widgets A jQuery collection of HTMLElements - * representing the widgets you want to move. - * @param {Number} to_col The column to which we want to move the widgets. - * @param {Number} to_row The row to which we want to move the widgets. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.manage_movements = function($widgets, to_col, to_row) { - $.each($widgets, $.proxy(function(i, w) { - var wgd = w; - var $w = wgd.el; - - var can_go_widget_up = this.can_go_widget_up(wgd); - - if (can_go_widget_up) { - //target CAN go up - //so move widget up - this.move_widget_to($w, can_go_widget_up); - this.set_placeholder(to_col, can_go_widget_up + wgd.size_y); - - } else { - //target can't go up - var can_go_player_up = this.can_go_player_up( - this.player_grid_data); - - if (!can_go_player_up) { - // target can't go up - // player cant't go up - // so we need to move widget down to a position that dont - // overlaps player - var y = (to_row + this.player_grid_data.size_y) - wgd.row; - - this.move_widget_down($w, y); + /* if there is not widgets overlapping in the new player position, + * update the new placeholder position. */ + if (!$overlapped_widgets.length) { + var pp = this.can_go_player_up(this.player_grid_data); + if (pp !== false) { + to_row = pp; + } + if (this.can_placeholder_be_set(to_col, to_row, player_size_x, player_size_y)) { this.set_placeholder(to_col, to_row); } } - }, this)); - return this; - }; + this.w_queue = {}; - /** - * Determines if there is a widget in the row and col given. Or if the - * HTMLElement passed as first argument is the player. - * - * @method is_player - * @param {Number|HTMLElement} col_or_el A jQuery wrapped collection of - * HTMLElements. - * @param {Number} [row] The column to which we want to move the widgets. - * @return {Boolean} Returns true or false. - */ - fn.is_player = function(col_or_el, row) { - if (row && !this.gridmap[col_or_el]) { return false; } - var $w = row ? this.gridmap[col_or_el][row] : col_or_el; - return $w && ($w.is(this.$player) || $w.is(this.$helper)); - }; + return { + col : to_col, + row : to_row + }; + }; + fn.is_swap_occupied = function(col, row, w_size_x, w_size_y) { + var occupied = false; + for (var c = 0; c < w_size_x; c++) { + for (var r = 0; r < w_size_y; r++) { + var colc = col + c; + var rowc = row + r; + var key = colc + "_" + rowc; + if (this.is_occupied(colc, rowc)) { + occupied = true; + } else if ( key in this.w_queue) { + if (this.w_queue[key] == "full") { + occupied = true; + continue; + } + $tw = this.w_queue[key]; + tgd = $tw.coords().grid; + //remove queued items if no longer under player. + if (!this.is_widget_under_player(tgd.col, tgd.row)) { + delete this.w_queue[key]; + } + } + if (rowc > parseInt(this.options.max_rows)) { + occupied = true; + } + if (colc > parseInt(this.options.max_cols)) { + occupied = true; + } + if (this.is_player_in(colc, rowc)) { + occupied = true; + } + } + } - /** - * Determines if the widget that is being dragged is currently over the row - * and col given. - * - * @method is_player_in - * @param {Number} col The column to check. - * @param {Number} row The row to check. - * @return {Boolean} Returns true or false. - */ - fn.is_player_in = function(col, row) { - var c = this.cells_occupied_by_player || {}; - return $.inArray(col, c.cols) >= 0 && $.inArray(row, c.rows) >= 0; - }; + return occupied; + } + fn.can_placeholder_be_set = function(col, row, player_size_x, player_size_y) { + var can_set = true; + for (var c = 0; c < player_size_x; c++) { + for (var r = 0; r < player_size_y; r++) { + var colc = col + c; + var rowc = row + r; + var key = colc + "_" + rowc; + var $tw = this.is_widget(colc, rowc); + //if this space is occupied and not queued for move. + if (rowc > parseInt(this.options.max_rows)) { + can_set = false; + } + if (colc > parseInt(this.options.max_cols)) { + can_set = false; + } + if (this.is_occupied(colc, rowc) && !this.is_widget_queued_and_can_move($tw)) { + can_set = false; + } + } + } + return can_set; + } - /** - * Determines if the placeholder is currently over the row and col given. - * - * @method is_placeholder_in - * @param {Number} col The column to check. - * @param {Number} row The row to check. - * @return {Boolean} Returns true or false. - */ - fn.is_placeholder_in = function(col, row) { - var c = this.cells_occupied_by_placeholder || {}; - return this.is_placeholder_in_col(col) && $.inArray(row, c.rows) >= 0; - }; + fn.queue_widget = function(col, row, $widget) { + var $w = $widget + var wgd = $w.coords().grid; + var primary_key = col + "_" + row; + if ( primary_key in this.w_queue) { + return false; + } + this.w_queue[primary_key] = $w; - /** - * Determines if the placeholder is currently over the column given. - * - * @method is_placeholder_in_col - * @param {Number} col The column to check. - * @return {Boolean} Returns true or false. - */ - fn.is_placeholder_in_col = function(col) { - var c = this.cells_occupied_by_placeholder || []; - return $.inArray(col, c.cols) >= 0; - }; + for (var c = 0; c < wgd.size_x; c++) { + for (var r = 0; r < wgd.size_y; r++) { + var colc = col + c; + var rowc = row + r; + var key = colc + "_" + rowc; + if (key == primary_key) { + continue; + } + this.w_queue[key] = "full"; + } + } - - /** - * Determines if the cell represented by col and row params is empty. - * - * @method is_empty - * @param {Number} col The column to check. - * @param {Number} row The row to check. - * @return {Boolean} Returns true or false. - */ - fn.is_empty = function(col, row) { - if (typeof this.gridmap[col] !== 'undefined' && - typeof this.gridmap[col][row] !== 'undefined' && - this.gridmap[col][row] === false - ) { return true; } - return false; - }; + fn.is_widget_queued_and_can_move = function($widget) { + var queued = false; + if ($widget === false) { + return false; + } - /** - * Determines if the cell represented by col and row params is occupied. - * - * @method is_occupied - * @param {Number} col The column to check. - * @param {Number} row The row to check. - * @return {Boolean} Returns true or false. - */ - fn.is_occupied = function(col, row) { - if (!this.gridmap[col]) { - return false; + for (var key in this.w_queue) { + if (this.w_queue[key] == "full") { + continue; + } + if (this.w_queue[key].attr("data-col") == $widget.attr("data-col") && this.w_queue[key].attr("data-row") == $widget.attr("data-row")) { + queued = true; + //test whole space + var $w = this.w_queue[key]; + var dcol = parseInt(key.split("_")[0]); + var drow = parseInt(key.split("_")[1]); + var wgd = $w.coords().grid; + + for (var c = 0; c < wgd.size_x; c++) { + for (var r = 0; r < wgd.size_y; r++) { + var colc = dcol + c; + var rowc = drow + r; + if (this.is_player_in(colc, rowc)) { + queued = false; + } + + } + } + + } + } + + return queued } - if (this.gridmap[col][row]) { - return true; + fn.is_in_queue = function(col, row, $widget) { + var queued = false; + var key = col + "_" + row; + + if (( key in this.w_queue)) { + if (this.w_queue[key] == "full") { + queued = true; + } else { + $tw = this.w_queue[key]; + tgd = $tw.coords().grid; + if (!this.is_widget_under_player(tgd.col, tgd.row)) { + delete this.w_queue[key] + queued = false; + } else if (this.w_queue[key].attr("data-col") == $widget.attr("data-col") && this.w_queue[key].attr("data-row") == $widget.attr("data-row")) { + delete this.w_queue[key] + queued = false; + } else { + queued = true; + } + } + } + + return queued; } - return false; - }; + /** + * See which of the widgets in the $widgets param collection can go to + * a upper row and which not. + * + * @method widgets_contraints + * @param {HTMLElements} $widgets A jQuery wrapped collection of + * HTMLElements. + * @return {Array} Returns a literal Object with two keys: `can_go_up` & + * `can_not_go_up`. Each contains a set of HTMLElements. + */ + fn.widgets_constraints = function($widgets) { + var $widgets_can_go_up = $([]); + var $widgets_can_not_go_up; + var wgd_can_go_up = []; + var wgd_can_not_go_up = []; - - /** - * Determines if there is a widget in the cell represented by col/row params. - * - * @method is_widget - * @param {Number} col The column to check. - * @param {Number} row The row to check. - * @return {Boolean|HTMLElement} Returns false if there is no widget, - * else returns the jQuery HTMLElement - */ - fn.is_widget = function(col, row) { - var cell = this.gridmap[col]; - if (!cell) { - return false; - } - - cell = cell[row]; - - if (cell) { - return cell; - } - - return false; - }; - - - /** - * Determines if there is a widget in the cell represented by col/row - * params and if this is under the widget that is being dragged. - * - * @method is_widget_under_player - * @param {Number} col The column to check. - * @param {Number} row The row to check. - * @return {Boolean} Returns true or false. - */ - fn.is_widget_under_player = function(col, row) { - if (this.is_widget(col, row)) { - return this.is_player_in(col, row); - } - return false; - }; - - - /** - * Get widgets overlapping with the player or with the object passed - * representing the grid cells. - * - * @method get_widgets_under_player - * @return {HTMLElement} Returns a jQuery collection of HTMLElements - */ - fn.get_widgets_under_player = function(cells) { - cells || (cells = this.cells_occupied_by_player || {cols: [], rows: []}); - var $widgets = $([]); - - $.each(cells.cols, $.proxy(function(i, col) { - $.each(cells.rows, $.proxy(function(i, row) { - if(this.is_widget(col, row)) { - $widgets = $widgets.add(this.gridmap[col][row]); + $widgets.each($.proxy(function(i, w) { + var $w = $(w); + var wgd = $w.coords().grid; + if (this.can_go_widget_up(wgd)) { + $widgets_can_go_up = $widgets_can_go_up.add($w); + wgd_can_go_up.push(wgd); + } else { + wgd_can_not_go_up.push(wgd); } }, this)); - }, this)); - return $widgets; - }; + $widgets_can_not_go_up = $widgets.not($widgets_can_go_up); + return { + can_go_up : this.sort_by_row_asc(wgd_can_go_up), + can_not_go_up : this.sort_by_row_desc(wgd_can_not_go_up) + }; + }; - /** - * Put placeholder at the row and column specified. - * - * @method set_placeholder - * @param {Number} col The column to which we want to move the - * placeholder. - * @param {Number} row The row to which we want to move the - * placeholder. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.set_placeholder = function(col, row) { - var phgd = $.extend({}, this.placeholder_grid_data); - var $nexts = this.widgets_below({ - col: phgd.col, - row: phgd.row, - size_y: phgd.size_y, - size_x: phgd.size_x + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) in ascending way. + * + * @method sort_by_row_asc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + fn.sort_by_row_asc = function(widgets) { + widgets = widgets.sort(function(a, b) { + if (!a.row) { + a = $(a).coords().grid; + b = $(b).coords().grid; + } + + if (a.row > b.row) { + return 1; + } + return -1; }); - // Prevents widgets go out of the grid - var right_col = (col + phgd.size_x - 1); - if (right_col > this.cols) { - col = col - (right_col - col); - } + return widgets; + }; - var moved_down = this.placeholder_grid_data.row < row; - var changed_column = this.placeholder_grid_data.col !== col; + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) placing first the empty cells upper left. + * + * @method sort_by_row_and_col_asc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + fn.sort_by_row_and_col_asc = function(widgets) { + widgets = widgets.sort(function(a, b) { + if (a.row > b.row || a.row === b.row && a.col > b.col) { + return 1; + } + return -1; + }); - this.placeholder_grid_data.col = col; - this.placeholder_grid_data.row = row; + return widgets; + }; - this.cells_occupied_by_placeholder = this.get_cells_occupied( - this.placeholder_grid_data); + /** + * Sorts an Array of grid coords objects by column (representing the grid + * coords of each widget) in ascending way. + * + * @method sort_by_col_asc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + fn.sort_by_col_asc = function(widgets) { + widgets = widgets.sort(function(a, b) { + if (a.col > b.col) { + return 1; + } + return -1; + }); - this.$preview_holder.attr({ - 'data-row' : row, - 'data-col' : col - }); + return widgets; + }; - if (moved_down || changed_column) { - $nexts.each($.proxy(function(i, widget) { - this.move_widget_up( - $(widget), this.placeholder_grid_data.col - col + phgd.size_y); + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) in descending way. + * + * @method sort_by_row_desc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + fn.sort_by_row_desc = function(widgets) { + widgets = widgets.sort(function(a, b) { + if (a.row + a.size_y < b.row + b.size_y) { + return 1; + } + return -1; + }); + return widgets; + }; + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) in descending way. + + * Depreciated. + * + * @method manage_movements + * @param {HTMLElements} $widgets A jQuery collection of HTMLElements + * representing the widgets you want to move. + * @param {Number} to_col The column to which we want to move the widgets. + * @param {Number} to_row The row to which we want to move the widgets. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.manage_movements = function($widgets, to_col, to_row) { + $.each($widgets, $.proxy(function(i, w) { + var wgd = w; + var $w = wgd.el; + + var can_go_widget_up = this.can_go_widget_up(wgd); + + if (can_go_widget_up) { + //target CAN go up + //so move widget up + this.move_widget_to($w, can_go_widget_up); + this.set_placeholder(to_col, can_go_widget_up + wgd.size_y); + + } else { + //target can't go up + var can_go_player_up = this.can_go_player_up(this.player_grid_data); + + if (!can_go_player_up) { + // target can't go up + // player cant't go up + // so we need to move widget down to a position that dont + // overlaps player + var y = (to_row + this.player_grid_data.size_y) - wgd.row; + if (this.can_go_down($w)) { + console.log("In Move Down!") + this.move_widget_down($w, y); + this.set_placeholder(to_col, to_row); + } + } + } }, this)); - } + return this; + }; - var $widgets_under_ph = this.get_widgets_under_player(this.cells_occupied_by_placeholder); - if ($widgets_under_ph.length) { - $widgets_under_ph.each($.proxy(function(i, widget) { - var $w = $(widget); - this.move_widget_down( - $w, row + phgd.size_y - $w.data('coords').grid.row); - }, this)); - } + /** + * Determines if there is a widget in the row and col given. Or if the + * HTMLElement passed as first argument is the player. + * + * @method is_player + * @param {Number|HTMLElement} col_or_el A jQuery wrapped collection of + * HTMLElements. + * @param {Number} [row] The column to which we want to move the widgets. + * @return {Boolean} Returns true or false. + */ + fn.is_player = function(col_or_el, row) { + if (row && !this.gridmap[col_or_el]) { + return false; + } + var $w = row ? this.gridmap[col_or_el][row] : col_or_el; + return $w && ($w.is(this.$player) || $w.is(this.$helper)); + }; - }; + /** + * Determines if the widget that is being dragged is currently over the row + * and col given. + * + * @method is_player_in + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_player_in = function(col, row) { + var c = this.cells_occupied_by_player || {}; + return $.inArray(col, c.cols) >= 0 && $.inArray(row, c.rows) >= 0; + }; + /** + * Determines if the placeholder is currently over the row and col given. + * + * @method is_placeholder_in + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_placeholder_in = function(col, row) { + var c = this.cells_occupied_by_placeholder || {}; + return this.is_placeholder_in_col(col) && $.inArray(row, c.rows) >= 0; + }; - /** - * Determines whether the player can move to a position above. - * - * @method can_go_player_up - * @param {Object} widget_grid_data The actual grid coords object of the - * player. - * @return {Number|Boolean} If the player can be moved to an upper row - * returns the row number, else returns false. - */ - fn.can_go_player_up = function(widget_grid_data) { - var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; - var result = true; - var upper_rows = []; - var min_row = 10000; - var $widgets_under_player = this.get_widgets_under_player(); + /** + * Determines if the placeholder is currently over the column given. + * + * @method is_placeholder_in_col + * @param {Number} col The column to check. + * @return {Boolean} Returns true or false. + */ + fn.is_placeholder_in_col = function(col) { + var c = this.cells_occupied_by_placeholder || []; + return $.inArray(col, c.cols) >= 0; + }; - /* generate an array with columns as index and array with upper rows - * empty as value */ - this.for_each_column_occupied(widget_grid_data, function(tcol) { - var grid_col = this.gridmap[tcol]; - var r = p_bottom_row + 1; - upper_rows[tcol] = []; - - while (--r > 0) { - if (this.is_empty(tcol, r) || this.is_player(tcol, r) || - this.is_widget(tcol, r) && - grid_col[r].is($widgets_under_player) + /** + * Determines if the cell represented by col and row params is empty. + * + * @method is_empty + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_empty = function(col, row) { + if (typeof this.gridmap[col] !== 'undefined') { + if(typeof this.gridmap[col][row] !== 'undefined' && + this.gridmap[col][row] === false ) { - upper_rows[tcol].push(r); - min_row = r < min_row ? r : min_row; - }else{ - break; + return true; + } + return false; + } + return true; + }; + + /** + * Determines if the cell represented by col and row params is occupied. + * + * @method is_occupied + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_occupied = function(col, row) { + if (!this.gridmap[col]) { + return false; + } + + if (this.gridmap[col][row]) { + return true; + } + return false; + }; + + /** + * Determines if there is a widget in the cell represented by col/row params. + * + * @method is_widget + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean|HTMLElement} Returns false if there is no widget, + * else returns the jQuery HTMLElement + */ + fn.is_widget = function(col, row) { + var cell = this.gridmap[col]; + if (!cell) { + return false; + } + + cell = cell[row]; + + if (cell) { + return cell; + } + + return false; + }; + + /** + * Determines if widget is supposed to be static. + * @method is_static + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true if widget exists and has static class, + * else returns false + */ + + fn.is_static = function(col, row) { + var cell = this.gridmap[col]; + if (!cell) { + return false; + } + + cell = cell[row]; + + if (cell) { + if (cell.hasClass(this.options.static_class)) { + return true; } } - if (upper_rows[tcol].length === 0) { - result = false; - return true; //break + return false; + }; + + /** + * Determines if there is a widget in the cell represented by col/row + * params and if this is under the widget that is being dragged. + * + * @method is_widget_under_player + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_widget_under_player = function(col, row) { + if (this.is_widget(col, row)) { + return this.is_player_in(col, row); + } + return false; + }; + + /** + * Get widgets overlapping with the player or with the object passed + * representing the grid cells. + * + * @method get_widgets_under_player + * @return {HTMLElement} Returns a jQuery collection of HTMLElements + */ + fn.get_widgets_under_player = function(cells) { + cells || ( cells = this.cells_occupied_by_player || { + cols : [], + rows : [] + }); + var $widgets = $([]); + + $.each(cells.cols, $.proxy(function(i, col) { + $.each(cells.rows, $.proxy(function(i, row) { + if (this.is_widget(col, row)) { + $widgets = $widgets.add(this.gridmap[col][row]); + } + }, this)); + }, this)); + + return $widgets; + }; + + /** + * Put placeholder at the row and column specified. + * + * @method set_placeholder + * @param {Number} col The column to which we want to move the + * placeholder. + * @param {Number} row The row to which we want to move the + * placeholder. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.set_placeholder = function(col, row) { + var phgd = $.extend({}, this.placeholder_grid_data); + var $nexts = this.widgets_below({ + col : phgd.col, + row : phgd.row, + size_y : phgd.size_y, + size_x : phgd.size_x + }); + + // Prevents widgets go out of the grid + var right_col = (col + phgd.size_x - 1); + if (right_col > this.cols) { + col = col - (right_col - col); } - upper_rows[tcol].sort(); - }); + var moved_down = this.placeholder_grid_data.row < row; + var changed_column = this.placeholder_grid_data.col !== col; - if (!result) { return false; } + this.placeholder_grid_data.col = col; + this.placeholder_grid_data.row = row; - return this.get_valid_rows(widget_grid_data, upper_rows, min_row); - }; + this.cells_occupied_by_placeholder = this.get_cells_occupied(this.placeholder_grid_data); + this.$preview_holder.attr({ + 'data-row' : row, + 'data-col' : col + }); - /** - * Determines whether a widget can move to a position above. - * - * @method can_go_widget_up - * @param {Object} widget_grid_data The actual grid coords object of the - * widget we want to check. - * @return {Number|Boolean} If the widget can be moved to an upper row - * returns the row number, else returns false. - */ - fn.can_go_widget_up = function(widget_grid_data) { - var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; - var result = true; - var upper_rows = []; - var min_row = 10000; + if (moved_down || changed_column) { + $nexts.each($.proxy(function(i, widget) { + //Make sure widget is at it's topmost position + $w = $(widget); + wgd = $w.coords().grid; - /* generate an array with columns as index and array with topmost rows - * empty as value */ - this.for_each_column_occupied(widget_grid_data, function(tcol) { - var grid_col = this.gridmap[tcol]; - upper_rows[tcol] = []; + var can_go_widget_up = this.can_go_widget_up(wgd); - var r = p_bottom_row + 1; - // iterate over each row - while (--r > 0) { - if (this.is_widget(tcol, r) && !this.is_player_in(tcol, r)) { - if (!grid_col[r].is(widget_grid_data.el)) { + if (can_go_widget_up) { + this.move_widget_to($w, can_go_widget_up); + } + + }, this)); + } + + var $widgets_under_ph = this.get_widgets_under_player(this.cells_occupied_by_placeholder); + if ($widgets_under_ph.length) { + $widgets_under_ph.each($.proxy(function(i, widget) { + var $w = $(widget); + this.move_widget_down($w, row + phgd.size_y - $w.data('coords').grid.row); + }, this)); + } + + }; + + /** + * Determines whether the player can move to a position above. + * + * @method can_go_player_up + * @param {Object} widget_grid_data The actual grid coords object of the + * player. + * @return {Number|Boolean} If the player can be moved to an upper row + * returns the row number, else returns false. + */ + fn.can_go_player_up = function(widget_grid_data) { + var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; + var result = true; + var upper_rows = []; + var min_row = 10000; + var $widgets_under_player = this.get_widgets_under_player(); + + /* generate an array with columns as index and array with upper rows + * empty as value */ + this.for_each_column_occupied(widget_grid_data, function(tcol) { + var grid_col = this.gridmap[tcol]; + var r = p_bottom_row + 1; + upper_rows[tcol] = []; + + while (--r > 0) { + if (this.is_empty(tcol, r) || this.is_player(tcol, r) || this.is_widget(tcol, r) && grid_col[r].is($widgets_under_player)) { + upper_rows[tcol].push(r); + min_row = r < min_row ? r : min_row; + } else { break; } } - if (!this.is_player(tcol, r) && - !this.is_placeholder_in(tcol, r) && - !this.is_player_in(tcol, r)) { - upper_rows[tcol].push(r); - } - - if (r < min_row) { - min_row = r; - } - } - - if (upper_rows[tcol].length === 0) { - result = false; - return true; //break - } - - upper_rows[tcol].sort(); - }); - - if (!result) { return false; } - - return this.get_valid_rows(widget_grid_data, upper_rows, min_row); - }; - - - /** - * Search a valid row for the widget represented by `widget_grid_data' in - * the `upper_rows` array. Iteration starts from row specified in `min_row`. - * - * @method get_valid_rows - * @param {Object} widget_grid_data The actual grid coords object of the - * player. - * @param {Array} upper_rows An array with columns as index and arrays - * of valid rows as values. - * @param {Number} min_row The upper row from which the iteration will start. - * @return {Number|Boolean} Returns the upper row valid from the `upper_rows` - * for the widget in question. - */ - fn.get_valid_rows = function(widget_grid_data, upper_rows, min_row) { - var p_top_row = widget_grid_data.row; - var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; - var size_y = widget_grid_data.size_y; - var r = min_row - 1; - var valid_rows = []; - - while (++r <= p_bottom_row ) { - var common = true; - $.each(upper_rows, function(col, rows) { - if ($.isArray(rows) && $.inArray(r, rows) === -1) { - common = false; - } - }); - - if (common === true) { - valid_rows.push(r); - if (valid_rows.length === size_y) { - break; - } - } - } - - var new_row = false; - if (size_y === 1) { - if (valid_rows[0] !== p_top_row) { - new_row = valid_rows[0] || false; - } - }else{ - if (valid_rows[0] !== p_top_row) { - new_row = this.get_consecutive_numbers_index( - valid_rows, size_y); - } - } - - return new_row; - }; - - - fn.get_consecutive_numbers_index = function(arr, size_y) { - var max = arr.length; - var result = []; - var first = true; - var prev = -1; // or null? - - for (var i=0; i < max; i++) { - if (first || arr[i] === prev + 1) { - result.push(i); - if (result.length === size_y) { - break; - } - first = false; - }else{ - result = []; - first = true; - } - - prev = arr[i]; - } - - return result.length >= size_y ? arr[result[0]] : false; - }; - - - /** - * Get widgets overlapping with the player. - * - * @method get_widgets_overlapped - * @return {HTMLElements} Returns a jQuery collection of HTMLElements. - */ - fn.get_widgets_overlapped = function() { - var $w; - var $widgets = $([]); - var used = []; - var rows_from_bottom = this.cells_occupied_by_player.rows.slice(0); - rows_from_bottom.reverse(); - - $.each(this.cells_occupied_by_player.cols, $.proxy(function(i, col) { - $.each(rows_from_bottom, $.proxy(function(i, row) { - // if there is a widget in the player position - if (!this.gridmap[col]) { return true; } //next iteration - var $w = this.gridmap[col][row]; - if (this.is_occupied(col, row) && !this.is_player($w) && - $.inArray($w, used) === -1 - ) { - $widgets = $widgets.add($w); - used.push($w); - } - - }, this)); - }, this)); - - return $widgets; - }; - - - /** - * This callback is executed when the player begins to collide with a column. - * - * @method on_start_overlapping_column - * @param {Number} col The collided column. - * @return {HTMLElements} Returns a jQuery collection of HTMLElements. - */ - fn.on_start_overlapping_column = function(col) { - this.set_player(col, false); - }; - - - /** - * A callback executed when the player begins to collide with a row. - * - * @method on_start_overlapping_row - * @param {Number} col The collided row. - * @return {HTMLElements} Returns a jQuery collection of HTMLElements. - */ - fn.on_start_overlapping_row = function(row) { - this.set_player(false, row); - }; - - - /** - * A callback executed when the the player ends to collide with a column. - * - * @method on_stop_overlapping_column - * @param {Number} col The collided row. - * @return {HTMLElements} Returns a jQuery collection of HTMLElements. - */ - fn.on_stop_overlapping_column = function(col) { - this.set_player(col, false); - - var self = this; - this.for_each_widget_below(col, this.cells_occupied_by_player.rows[0], - function(tcol, trow) { - self.move_widget_up(this, self.player_grid_data.size_y); - }); - }; - - - /** - * This callback is executed when the player ends to collide with a row. - * - * @method on_stop_overlapping_row - * @param {Number} row The collided row. - * @return {HTMLElements} Returns a jQuery collection of HTMLElements. - */ - fn.on_stop_overlapping_row = function(row) { - this.set_player(false, row); - - var self = this; - var cols = this.cells_occupied_by_player.cols; - for (var c = 0, cl = cols.length; c < cl; c++) { - this.for_each_widget_below(cols[c], row, function(tcol, trow) { - self.move_widget_up(this, self.player_grid_data.size_y); - }); - } - }; - - - /** - * Move a widget to a specific row. The cell or cells must be empty. - * If the widget has widgets below, all of these widgets will be moved also - * if they can. - * - * @method move_widget_to - * @param {HTMLElement} $widget The jQuery wrapped HTMLElement of the - * widget is going to be moved. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.move_widget_to = function($widget, row) { - var self = this; - var widget_grid_data = $widget.coords().grid; - var diff = row - widget_grid_data.row; - var $next_widgets = this.widgets_below($widget); - - var can_move_to_new_cell = this.can_move_to( - widget_grid_data, widget_grid_data.col, row, $widget); - - if (can_move_to_new_cell === false) { - return false; - } - - this.remove_from_gridmap(widget_grid_data); - widget_grid_data.row = row; - this.add_to_gridmap(widget_grid_data); - $widget.attr('data-row', row); - this.$changed = this.$changed.add($widget); - - - $next_widgets.each(function(i, widget) { - var $w = $(widget); - var wgd = $w.coords().grid; - var can_go_up = self.can_go_widget_up(wgd); - if (can_go_up && can_go_up !== wgd.row) { - self.move_widget_to($w, can_go_up); - } - }); - - return this; - }; - - - /** - * Move up the specified widget and all below it. - * - * @method move_widget_up - * @param {HTMLElement} $widget The widget you want to move. - * @param {Number} [y_units] The number of cells that the widget has to move. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.move_widget_up = function($widget, y_units) { - var el_grid_data = $widget.coords().grid; - var actual_row = el_grid_data.row; - var moved = []; - var can_go_up = true; - y_units || (y_units = 1); - - if (!this.can_go_up($widget)) { return false; } //break; - - this.for_each_column_occupied(el_grid_data, function(col) { - // can_go_up - if ($.inArray($widget, moved) === -1) { - var widget_grid_data = $widget.coords().grid; - var next_row = actual_row - y_units; - next_row = this.can_go_up_to_row( - widget_grid_data, col, next_row); - - if (!next_row) { + if (upper_rows[tcol].length === 0) { + result = false; return true; + //break } + upper_rows[tcol].sort(); + }); + + if (!result) { + return false; + } + + return this.get_valid_rows(widget_grid_data, upper_rows, min_row); + }; + + /** + * Determines whether a widget can move to a position above. + * + * @method can_go_widget_up + * @param {Object} widget_grid_data The actual grid coords object of the + * widget we want to check. + * @return {Number|Boolean} If the widget can be moved to an upper row + * returns the row number, else returns false. + */ + fn.can_go_widget_up = function(widget_grid_data) { + var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; + var result = true; + var upper_rows = []; + var min_row = 10000; + + /* generate an array with columns as index and array with topmost rows + * empty as value */ + this.for_each_column_occupied(widget_grid_data, function(tcol) { + var grid_col = this.gridmap[tcol]; + upper_rows[tcol] = []; + + var r = p_bottom_row + 1; + // iterate over each row + while (--r > 0) { + if (this.is_widget(tcol, r) && !this.is_player_in(tcol, r)) { + if (!grid_col[r].is(widget_grid_data.el)) { + break; + } + } + + if (!this.is_player(tcol, r) && !this.is_placeholder_in(tcol, r) && !this.is_player_in(tcol, r)) { + upper_rows[tcol].push(r); + } + + if (r < min_row) { + min_row = r; + } + } + + if (upper_rows[tcol].length === 0) { + result = false; + return true; + //break + } + + upper_rows[tcol].sort(); + }); + + if (!result) { + return false; + } + + return this.get_valid_rows(widget_grid_data, upper_rows, min_row); + }; + + /** + * Search a valid row for the widget represented by `widget_grid_data' in + * the `upper_rows` array. Iteration starts from row specified in `min_row`. + * + * @method get_valid_rows + * @param {Object} widget_grid_data The actual grid coords object of the + * player. + * @param {Array} upper_rows An array with columns as index and arrays + * of valid rows as values. + * @param {Number} min_row The upper row from which the iteration will start. + * @return {Number|Boolean} Returns the upper row valid from the `upper_rows` + * for the widget in question. + */ + fn.get_valid_rows = function(widget_grid_data, upper_rows, min_row) { + var p_top_row = widget_grid_data.row; + var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; + var size_y = widget_grid_data.size_y; + var r = min_row - 1; + var valid_rows = []; + + while (++r <= p_bottom_row) { + var common = true; + $.each(upper_rows, function(col, rows) { + if ($.isArray(rows) && $.inArray(r, rows) === -1) { + common = false; + } + }); + + if (common === true) { + valid_rows.push(r); + if (valid_rows.length === size_y) { + break; + } + } + } + + var new_row = false; + if (size_y === 1) { + if (valid_rows[0] !== p_top_row) { + new_row = valid_rows[0] || false; + } + } else { + if (valid_rows[0] !== p_top_row) { + new_row = this.get_consecutive_numbers_index(valid_rows, size_y); + } + } + + return new_row; + }; + + fn.get_consecutive_numbers_index = function(arr, size_y) { + var max = arr.length; + var result = []; + var first = true; + var prev = -1; + // or null? + + for (var i = 0; i < max; i++) { + if (first || arr[i] === prev + 1) { + result.push(i); + if (result.length === size_y) { + break; + } + first = false; + } else { + result = []; + first = true; + } + + prev = arr[i]; + } + + return result.length >= size_y ? arr[result[0]] : false; + }; + + /** + * Get widgets overlapping with the player. + * + * @method get_widgets_overlapped + * @return {HTMLElements} Returns a jQuery collection of HTMLElements. + */ + fn.get_widgets_overlapped = function() { + var $w; + var $widgets = $([]); + var used = []; + var rows_from_bottom = this.cells_occupied_by_player.rows.slice(0); + rows_from_bottom.reverse(); + + $.each(this.cells_occupied_by_player.cols, $.proxy(function(i, col) { + $.each(rows_from_bottom, $.proxy(function(i, row) { + // if there is a widget in the player position + if (!this.gridmap[col]) { + return true; + }//next iteration + var $w = this.gridmap[col][row]; + if (this.is_occupied(col, row) && !this.is_player($w) && $.inArray($w, used) === -1) { + $widgets = $widgets.add($w); + used.push($w); + } + + }, this)); + }, this)); + + return $widgets; + }; + + /** + * This callback is executed when the player begins to collide with a column. + * + * @method on_start_overlapping_column + * @param {Number} col The collided column. + * @return {HTMLElements} Returns a jQuery collection of HTMLElements. + */ + fn.on_start_overlapping_column = function(col) { + this.set_player(col, false); + }; + + /** + * A callback executed when the player begins to collide with a row. + * + * @method on_start_overlapping_row + * @param {Number} col The collided row. + * @return {HTMLElements} Returns a jQuery collection of HTMLElements. + */ + fn.on_start_overlapping_row = function(row) { + this.set_player(false, row); + }; + + /** + * A callback executed when the the player ends to collide with a column. + * + * @method on_stop_overlapping_column + * @param {Number} col The collided row. + * @return {HTMLElements} Returns a jQuery collection of HTMLElements. + */ + fn.on_stop_overlapping_column = function(col) { + //this.set_player(col, false); + var self = this; + if (this.options.shift_larger_widgets_down) { + this.for_each_widget_below(col, this.cells_occupied_by_player.rows[0], function(tcol, trow) { + self.move_widget_up(this, self.player_grid_data.size_y); + }); + } + }; + + /** + * This callback is executed when the player ends to collide with a row. + * + * @method on_stop_overlapping_row + * @param {Number} row The collided row. + * @return {HTMLElements} Returns a jQuery collection of HTMLElements. + */ + fn.on_stop_overlapping_row = function(row) { + //this.set_player(false, row); + var self = this; + var cols = this.cells_occupied_by_player.cols; + if (this.options.shift_larger_widgets_down) { + for (var c = 0, cl = cols.length; c < cl; c++) { + this.for_each_widget_below(cols[c], row, function(tcol, trow) { + console.log("from_on_stop_overlapping_row"); + self.move_widget_up(this, self.player_grid_data.size_y); + }); + } + } + }; + + //Must call this before changing positions + //Clears out all previous positions + fn.clear_grid_data = function() { + var self = this; + this.$widgets.each(function() { + var widget_grid_data = $(this).coords().grid; + if (widget_grid_data) { + self.remove_from_gridmap(widget_grid_data); + } + }); + + for (var i = 0; i < page.base.gridster.gridmap.length; i++) { + if (page.base.gridster.gridmap[i]) { + for (var j = 0; j < page.base.gridster.gridmap[i].length; j++) { + page.base.gridster.gridmap[i][j] = false; + } + } + } + }; + + fn.new_move_widget_to_legacy = function($widget, col, row) { + var self = this; + var widget_grid_data = $widget.coords().grid; + + this.remove_from_gridmap(widget_grid_data); + + widget_grid_data.row = row; + widget_grid_data.col = col; + + this.gridmap[col][row] = $widget; + + this.add_to_gridmap(widget_grid_data); + $widget.attr('data-row', row); + $widget.attr('data-col', col); + this.update_widget_position(widget_grid_data, $widget); + this.$changed = this.$changed.add($widget); + + this.set_dom_grid_height(); + + return this; + } + + fn.new_move_widget_to = function($widget, col, row, clearData) { + var self = this; + var widget_grid_data = $widget.coords().grid; + + if (clearData) { + self.remove_from_gridmap(widget_grid_data); + } + + widget_grid_data.row = row; + widget_grid_data.col = col; + + this.add_to_gridmap(widget_grid_data); + + this.update_widget_position(widget_grid_data, $widget); + $widget.attr('data-row', row); + $widget.attr('data-col', col); + + this.set_dom_grid_height(); + + return this; + } + /** + * Move a widget to a specific row. The cell or cells must be empty. + * If the widget has widgets below, all of these widgets will be moved also + * if they can. + * + * @method move_widget_to + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement of the + * widget is going to be moved. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.move_widget_to = function($widget, row) { + var self = this; + var widget_grid_data = $widget.coords().grid; + var diff = row - widget_grid_data.row; + var $next_widgets = this.widgets_below($widget); + + var can_move_to_new_cell = this.can_move_to(widget_grid_data, widget_grid_data.col, row, $widget); + + if (can_move_to_new_cell === false) { + return false; + } + + this.remove_from_gridmap(widget_grid_data); + widget_grid_data.row = row; + this.add_to_gridmap(widget_grid_data); + $widget.attr('data-row', row); + this.$changed = this.$changed.add($widget); + + $next_widgets.each(function(i, widget) { + var $w = $(widget); + var wgd = $w.coords().grid; + var can_go_up = self.can_go_widget_up(wgd); + if (can_go_up && can_go_up !== wgd.row) { + self.move_widget_to($w, can_go_up); + } + }); + + return this; + }; + + /** + * Move up the specified widget and all below it. + * + * @method move_widget_up + * @param {HTMLElement} $widget The widget you want to move. + * @param {Number} [y_units] The number of cells that the widget has to move. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.move_widget_up = function($widget, y_units) { + var el_grid_data = $widget.coords().grid; + var actual_row = Math.floor(el_grid_data.row); + var moved = []; + var can_go_up = true; + y_units || ( y_units = 1); + + if (!this.can_go_up($widget)) { + return false; + }//break; + + this.for_each_column_occupied(el_grid_data, function(col) { + // can_go_up + if ($.inArray($widget, moved) === -1) { + var widget_grid_data = $widget.coords().grid; + var next_row = actual_row - y_units; + next_row = this.can_go_up_to_row(widget_grid_data, col, next_row); + if (!next_row) { + return true; + } + + var $next_widgets = this.widgets_below($widget); + + this.remove_from_gridmap(widget_grid_data); + widget_grid_data.row = next_row; + this.add_to_gridmap(widget_grid_data); + $widget.attr('data-row', widget_grid_data.row); + this.$changed = this.$changed.add($widget); + + moved.push($widget); + + /* $next_widgets.each($.proxy(function(i, widget) { + console.log("from_within_move_widget_up"); + this.move_widget_up($(widget), y_units); + }, this)); */ + } + }); + + }; + + /** + * Move down the specified widget and all below it. + * + * @method move_widget_down + * @param {HTMLElement} $widget The jQuery object representing the widget + * you want to move. + * @param {Number} The number of cells that the widget has to move. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.move_widget_down = function($widget, y_units) { + + if (y_units <= 0) { + return false; + } + var el_grid_data = $widget.coords().grid; + var actual_row = el_grid_data.row; + var moved = []; + var y_diff = y_units; + + if (!$widget) { + return false; + } + + if ($.inArray($widget, moved) === -1) { + + var widget_grid_data = $widget.coords().grid; + var next_row = actual_row + y_units; var $next_widgets = this.widgets_below($widget); this.remove_from_gridmap(widget_grid_data); + + $next_widgets.each($.proxy(function(i, widget) { + var $w = $(widget); + var wd = $w.coords().grid; + var tmp_y = this.displacement_diff(wd, widget_grid_data, y_diff); + + if (tmp_y > 0) { + this.move_widget_down($w, tmp_y); + } + }, this)); + widget_grid_data.row = next_row; - this.add_to_gridmap(widget_grid_data); + this.update_widget_position(widget_grid_data, $widget); $widget.attr('data-row', widget_grid_data.row); this.$changed = this.$changed.add($widget); moved.push($widget); - - $next_widgets.each($.proxy(function(i, widget) { - this.move_widget_up($(widget), y_units); - }, this)); } - }); + }; - }; + /** + * Check if the widget can move to the specified row, else returns the + * upper row possible. + * + * @method can_go_up_to_row + * @param {Number} widget_grid_data The current grid coords object of the + * widget. + * @param {Number} col The target column. + * @param {Number} row The target row. + * @return {Boolean|Number} Returns the row number if the widget can move + * to the target position, else returns false. + */ + fn.can_go_up_to_row = function(widget_grid_data, col, row) { + var ga = this.gridmap; + var result = true; + var urc = []; + // upper_rows_in_columns + var actual_row = widget_grid_data.row; + var r; + row = Math.floor(row); + /* generate an array with columns as index and array with + * upper rows empty in the column */ + this.for_each_column_occupied(widget_grid_data, function(tcol) { + var grid_col = ga[tcol]; + urc[tcol] = []; - /** - * Move down the specified widget and all below it. - * - * @method move_widget_down - * @param {HTMLElement} $widget The jQuery object representing the widget - * you want to move. - * @param {Number} The number of cells that the widget has to move. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.move_widget_down = function($widget, y_units) { - var el_grid_data = $widget.coords().grid; - var actual_row = el_grid_data.row; - var moved = []; - var y_diff = y_units; - - if (!$widget) { return false; } - - if ($.inArray($widget, moved) === -1) { - - var widget_grid_data = $widget.coords().grid; - var next_row = actual_row + y_units; - var $next_widgets = this.widgets_below($widget); - - this.remove_from_gridmap(widget_grid_data); - - $next_widgets.each($.proxy(function(i, widget) { - var $w = $(widget); - var wd = $w.coords().grid; - var tmp_y = this.displacement_diff( - wd, widget_grid_data, y_diff); - - if (tmp_y > 0) { - this.move_widget_down($w, tmp_y); + r = Math.floor(actual_row); + while (r-- >= 0) { + if (this.is_empty(tcol, r) && !this.is_placeholder_in(tcol, r)) { + urc[tcol].push(r); + } else { + break; + } } - }, this)); - widget_grid_data.row = next_row; - this.update_widget_position(widget_grid_data, $widget); - $widget.attr('data-row', widget_grid_data.row); - this.$changed = this.$changed.add($widget); + if (!urc[tcol].length) { + result = false; + return true; + } - moved.push($widget); - } - }; + }); + if (!result) { + return false; + } - /** - * Check if the widget can move to the specified row, else returns the - * upper row possible. - * - * @method can_go_up_to_row - * @param {Number} widget_grid_data The current grid coords object of the - * widget. - * @param {Number} col The target column. - * @param {Number} row The target row. - * @return {Boolean|Number} Returns the row number if the widget can move - * to the target position, else returns false. - */ - fn.can_go_up_to_row = function(widget_grid_data, col, row) { - var ga = this.gridmap; - var result = true; - var urc = []; // upper_rows_in_columns - var actual_row = widget_grid_data.row; - var r; + /* get common rows starting from upper position in all the columns + * that widget occupies */ + r = row; + for ( r = 1; r < actual_row; r++) { + var common = true; - /* generate an array with columns as index and array with - * upper rows empty in the column */ - this.for_each_column_occupied(widget_grid_data, function(tcol) { - var grid_col = ga[tcol]; - urc[tcol] = []; + for (var uc = 0, ucl = urc.length; uc < ucl; uc++) { + if (urc[uc] && $.inArray(r, urc[uc]) === -1) { + common = false; + } + } - r = actual_row; - while (r--) { - if (this.is_empty(tcol, r) && - !this.is_placeholder_in(tcol, r) - ) { - urc[tcol].push(r); - }else{ + if (common === true) { + result = r; break; } } - if (!urc[tcol].length) { - result = false; - return true; - } - - }); - - if (!result) { return false; } - - /* get common rows starting from upper position in all the columns - * that widget occupies */ - r = row; - for (r = 1; r < actual_row; r++) { - var common = true; - - for (var uc = 0, ucl = urc.length; uc < ucl; uc++) { - if (urc[uc] && $.inArray(r, urc[uc]) === -1) { - common = false; - } - } - - if (common === true) { - result = r; - break; - } - } - - return result; - }; - - - fn.displacement_diff = function(widget_grid_data, parent_bgd, y_units) { - var actual_row = widget_grid_data.row; - var diffs = []; - var parent_max_y = parent_bgd.row + parent_bgd.size_y; - - this.for_each_column_occupied(widget_grid_data, function(col) { - var temp_y_units = 0; - - for (var r = parent_max_y; r < actual_row; r++) { - if (this.is_empty(col, r)) { - temp_y_units = temp_y_units + 1; - } - } - - diffs.push(temp_y_units); - }); - - var max_diff = Math.max.apply(Math, diffs); - y_units = (y_units - max_diff); - - return y_units > 0 ? y_units : 0; - }; - - - /** - * Get widgets below a widget. - * - * @method widgets_below - * @param {HTMLElement} $el The jQuery wrapped HTMLElement. - * @return {HTMLElements} A jQuery collection of HTMLElements. - */ - fn.widgets_below = function($el) { - var el_grid_data = $.isPlainObject($el) ? $el : $el.coords().grid; - var self = this; - var ga = this.gridmap; - var next_row = el_grid_data.row + el_grid_data.size_y - 1; - var $nexts = $([]); - - this.for_each_column_occupied(el_grid_data, function(col) { - self.for_each_widget_below(col, next_row, function(tcol, trow) { - if (!self.is_player(this) && $.inArray(this, $nexts) === -1) { - $nexts = $nexts.add(this); - return true; // break - } - }); - }); - - return this.sort_by_row_asc($nexts); - }; - - - /** - * Update the array of mapped positions with the new player position. - * - * @method set_cells_player_occupies - * @param {Number} col The new player col. - * @param {Number} col The new player row. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.set_cells_player_occupies = function(col, row) { - this.remove_from_gridmap(this.placeholder_grid_data); - this.placeholder_grid_data.col = col; - this.placeholder_grid_data.row = row; - this.add_to_gridmap(this.placeholder_grid_data, this.$player); - return this; - }; - - - /** - * Remove from the array of mapped positions the reference to the player. - * - * @method empty_cells_player_occupies - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.empty_cells_player_occupies = function() { - this.remove_from_gridmap(this.placeholder_grid_data); - return this; - }; - - - fn.can_go_up = function($el) { - var el_grid_data = $el.coords().grid; - var initial_row = el_grid_data.row; - var prev_row = initial_row - 1; - var ga = this.gridmap; - var upper_rows_by_column = []; - - var result = true; - if (initial_row === 1) { return false; } - - this.for_each_column_occupied(el_grid_data, function(col) { - var $w = this.is_widget(col, prev_row); - - if (this.is_occupied(col, prev_row) || - this.is_player(col, prev_row) || - this.is_placeholder_in(col, prev_row) || - this.is_player_in(col, prev_row) - ) { - result = false; - return true; //break - } - }); - - return result; - }; - - - - /** - * Check if it's possible to move a widget to a specific col/row. It takes - * into account the dimensions (`size_y` and `size_x` attrs. of the grid - * coords object) the widget occupies. - * - * @method can_move_to - * @param {Object} widget_grid_data The grid coords object that represents - * the widget. - * @param {Object} col The col to check. - * @param {Object} row The row to check. - * @param {Number} [max_row] The max row allowed. - * @return {Boolean} Returns true if all cells are empty, else return false. - */ - fn.can_move_to = function(widget_grid_data, col, row, max_row) { - var ga = this.gridmap; - var $w = widget_grid_data.el; - var future_wd = { - size_y: widget_grid_data.size_y, - size_x: widget_grid_data.size_x, - col: col, - row: row + return result; }; - var result = true; - //Prevents widgets go out of the grid - var right_col = col + widget_grid_data.size_x - 1; - if (right_col > this.cols) { - return false; - } + fn.displacement_diff = function(widget_grid_data, parent_bgd, y_units) { + var actual_row = widget_grid_data.row; + var diffs = []; + var parent_max_y = parent_bgd.row + parent_bgd.size_y; - if (max_row && max_row < row + widget_grid_data.size_y - 1) { - return false; - } + this.for_each_column_occupied(widget_grid_data, function(col) { + var temp_y_units = 0; - this.for_each_cell_occupied(future_wd, function(tcol, trow) { - var $tw = this.is_widget(tcol, trow); - if ($tw && (!widget_grid_data.el || $tw.is($w))) { - result = false; - } - }); - - return result; - }; - - - /** - * Given the leftmost column returns all columns that are overlapping - * with the player. - * - * @method get_targeted_columns - * @param {Number} [from_col] The leftmost column. - * @return {Array} Returns an array with column numbers. - */ - fn.get_targeted_columns = function(from_col) { - var max = (from_col || this.player_grid_data.col) + - (this.player_grid_data.size_x - 1); - var cols = []; - for (var col = from_col; col <= max; col++) { - cols.push(col); - } - return cols; - }; - - - /** - * Given the upper row returns all rows that are overlapping with the player. - * - * @method get_targeted_rows - * @param {Number} [from_row] The upper row. - * @return {Array} Returns an array with row numbers. - */ - fn.get_targeted_rows = function(from_row) { - var max = (from_row || this.player_grid_data.row) + - (this.player_grid_data.size_y - 1); - var rows = []; - for (var row = from_row; row <= max; row++) { - rows.push(row); - } - return rows; - }; - - /** - * Get all columns and rows that a widget occupies. - * - * @method get_cells_occupied - * @param {Object} el_grid_data The grid coords object of the widget. - * @return {Object} Returns an object like `{ cols: [], rows: []}`. - */ - fn.get_cells_occupied = function(el_grid_data) { - var cells = { cols: [], rows: []}; - var i; - if (arguments[1] instanceof jQuery) { - el_grid_data = arguments[1].coords().grid; - } - - for (i = 0; i < el_grid_data.size_x; i++) { - var col = el_grid_data.col + i; - cells.cols.push(col); - } - - for (i = 0; i < el_grid_data.size_y; i++) { - var row = el_grid_data.row + i; - cells.rows.push(row); - } - - return cells; - }; - - - /** - * Iterate over the cells occupied by a widget executing a function for - * each one. - * - * @method for_each_cell_occupied - * @param {Object} el_grid_data The grid coords object that represents the - * widget. - * @param {Function} callback The function to execute on each column - * iteration. Column and row are passed as arguments. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.for_each_cell_occupied = function(grid_data, callback) { - this.for_each_column_occupied(grid_data, function(col) { - this.for_each_row_occupied(grid_data, function(row) { - callback.call(this, col, row); - }); - }); - return this; - }; - - - /** - * Iterate over the columns occupied by a widget executing a function for - * each one. - * - * @method for_each_column_occupied - * @param {Object} el_grid_data The grid coords object that represents - * the widget. - * @param {Function} callback The function to execute on each column - * iteration. The column number is passed as first argument. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.for_each_column_occupied = function(el_grid_data, callback) { - for (var i = 0; i < el_grid_data.size_x; i++) { - var col = el_grid_data.col + i; - callback.call(this, col, el_grid_data); - } - }; - - - /** - * Iterate over the rows occupied by a widget executing a function for - * each one. - * - * @method for_each_row_occupied - * @param {Object} el_grid_data The grid coords object that represents - * the widget. - * @param {Function} callback The function to execute on each column - * iteration. The row number is passed as first argument. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.for_each_row_occupied = function(el_grid_data, callback) { - for (var i = 0; i < el_grid_data.size_y; i++) { - var row = el_grid_data.row + i; - callback.call(this, row, el_grid_data); - } - }; - - - - fn._traversing_widgets = function(type, direction, col, row, callback) { - var ga = this.gridmap; - if (!ga[col]) { return; } - - var cr, max; - var action = type + '/' + direction; - if (arguments[2] instanceof jQuery) { - var el_grid_data = arguments[2].coords().grid; - col = el_grid_data.col; - row = el_grid_data.row; - callback = arguments[3]; - } - var matched = []; - var trow = row; - - - var methods = { - 'for_each/above': function() { - while (trow--) { - if (trow > 0 && this.is_widget(col, trow) && - $.inArray(ga[col][trow], matched) === -1 - ) { - cr = callback.call(ga[col][trow], col, trow); - matched.push(ga[col][trow]); - if (cr) { break; } + for (var r = parent_max_y; r < actual_row; r++) { + if (this.is_empty(col, r)) { + temp_y_units = temp_y_units + 1; } } - }, - 'for_each/below': function() { - for (trow = row + 1, max = ga[col].length; trow < max; trow++) { - if (this.is_widget(col, trow) && - $.inArray(ga[col][trow], matched) === -1 - ) { - cr = callback.call(ga[col][trow], col, trow); - matched.push(ga[col][trow]); - if (cr) { break; } + + diffs.push(temp_y_units); + }); + + var max_diff = Math.max.apply(Math, diffs); + y_units = (y_units - max_diff); + + return y_units > 0 ? y_units : 0; + }; + + /** + * Get widgets below a widget. + * + * @method widgets_below + * @param {HTMLElement} $el The jQuery wrapped HTMLElement. + * @return {HTMLElements} A jQuery collection of HTMLElements. + */ + fn.widgets_below = function($el) { + var el_grid_data = $.isPlainObject($el) ? $el : $el.coords().grid; + //if (!el_grid_data || !el_grid_data.row) + // return $([]); + + var self = this; + var ga = this.gridmap; + var next_row = el_grid_data.row + el_grid_data.size_y - 1; + var $nexts = $([]); + + this.for_each_column_occupied(el_grid_data, function(col) { + self.for_each_widget_below(col, next_row, function(tcol, trow) { + if (!self.is_player(this) && $.inArray(this, $nexts) === -1) { + $nexts = $nexts.add(this); + return true; + // break + } + }); + }); + + return this.sort_by_row_asc($nexts); + }; + + /** + * Update the array of mapped positions with the new player position. + * + * @method set_cells_player_occupies + * @param {Number} col The new player col. + * @param {Number} col The new player row. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.set_cells_player_occupies = function(col, row) { + this.remove_from_gridmap(this.placeholder_grid_data); + this.placeholder_grid_data.col = col; + this.placeholder_grid_data.row = row; + this.add_to_gridmap(this.placeholder_grid_data, this.$player); + return this; + }; + + /** + * Remove from the array of mapped positions the reference to the player. + * + * @method empty_cells_player_occupies + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.empty_cells_player_occupies = function() { + this.remove_from_gridmap(this.placeholder_grid_data); + return this; + }; + + fn.can_go_down = function($el) { + var can_go_down = true; + var $gr = this; + + if ($el.hasClass(this.options.static_class)) { + can_go_down = false; + } + + this.widgets_below($el).each(function() { + if ($(this).hasClass($gr.options.static_class)) { + can_go_down = false; + } + }) + + return can_go_down; + } + + fn.can_go_up = function($el) { + var el_grid_data = $el.coords().grid; + var initial_row = el_grid_data.row; + var prev_row = initial_row - 1; + var ga = this.gridmap; + var upper_rows_by_column = []; + + var result = true; + if (initial_row === 1) { + return false; + } + + this.for_each_column_occupied(el_grid_data, function(col) { + var $w = this.is_widget(col, prev_row); + + if (this.is_occupied(col, prev_row) || this.is_player(col, prev_row) || this.is_placeholder_in(col, prev_row) || this.is_player_in(col, prev_row)) { + result = false; + return true; + //break + } + }); + + return result; + }; + + /** + * Check if it's possible to move a widget to a specific col/row. It takes + * into account the dimensions (`size_y` and `size_x` attrs. of the grid + * coords object) the widget occupies. + * + * @method can_move_to + * @param {Object} widget_grid_data The grid coords object that represents + * the widget. + * @param {Object} col The col to check. + * @param {Object} row The row to check. + * @param {Number} [max_row] The max row allowed. + * @return {Boolean} Returns true if all cells are empty, else return false. + */ + fn.can_move_to = function(widget_grid_data, col, row, max_row) { + var ga = this.gridmap; + var $w = widget_grid_data.el; + var future_wd = { + size_y : widget_grid_data.size_y, + size_x : widget_grid_data.size_x, + col : col, + row : row + }; + var result = true; + + //Prevents widgets go out of the grid + var right_col = col + widget_grid_data.size_x - 1; + if (right_col > this.cols) { + return false; + } + + if (max_row && max_row < row + widget_grid_data.size_y - 1) { + return false; + } + + this.for_each_cell_occupied(future_wd, function(tcol, trow) { + var $tw = this.is_widget(tcol, trow); + if ($tw && (!widget_grid_data.el || $tw.is($w))) { + result = false; + } + }); + + return result; + }; + + /** + * Given the leftmost column returns all columns that are overlapping + * with the player. + * + * @method get_targeted_columns + * @param {Number} [from_col] The leftmost column. + * @return {Array} Returns an array with column numbers. + */ + fn.get_targeted_columns = function(from_col) { + var max = (from_col || this.player_grid_data.col) + (this.player_grid_data.size_x - 1); + var cols = []; + for (var col = from_col; col <= max; col++) { + cols.push(col); + } + return cols; + }; + + /** + * Given the upper row returns all rows that are overlapping with the player. + * + * @method get_targeted_rows + * @param {Number} [from_row] The upper row. + * @return {Array} Returns an array with row numbers. + */ + fn.get_targeted_rows = function(from_row) { + var max = (from_row || this.player_grid_data.row) + (this.player_grid_data.size_y - 1); + var rows = []; + for (var row = from_row; row <= max; row++) { + rows.push(row); + } + return rows; + }; + + /** + * Get all columns and rows that a widget occupies. + * + * @method get_cells_occupied + * @param {Object} el_grid_data The grid coords object of the widget. + * @return {Object} Returns an object like `{ cols: [], rows: []}`. + */ + fn.get_cells_occupied = function(el_grid_data) { + var cells = { + cols : [], + rows : [] + }; + var i; + if (arguments[1] instanceof jQuery) { + el_grid_data = arguments[1].coords().grid; + } + + for ( i = 0; i < el_grid_data.size_x; i++) { + var col = el_grid_data.col + i; + cells.cols.push(col); + } + + for ( i = 0; i < el_grid_data.size_y; i++) { + var row = el_grid_data.row + i; + cells.rows.push(row); + } + + return cells; + }; + + /** + * Iterate over the cells occupied by a widget executing a function for + * each one. + * + * @method for_each_cell_occupied + * @param {Object} el_grid_data The grid coords object that represents the + * widget. + * @param {Function} callback The function to execute on each column + * iteration. Column and row are passed as arguments. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.for_each_cell_occupied = function(grid_data, callback) { + this.for_each_column_occupied(grid_data, function(col) { + this.for_each_row_occupied(grid_data, function(row) { + callback.call(this, col, row); + }); + }); + return this; + }; + + /** + * Iterate over the columns occupied by a widget executing a function for + * each one. + * + * @method for_each_column_occupied + * @param {Object} el_grid_data The grid coords object that represents + * the widget. + * @param {Function} callback The function to execute on each column + * iteration. The column number is passed as first argument. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.for_each_column_occupied = function(el_grid_data, callback) { + //if (!el_grid_data) + //return; + for (var i = 0; i < el_grid_data.size_x; i++) { + var col = el_grid_data.col + i; + callback.call(this, col, el_grid_data); + } + }; + + /** + * Iterate over the rows occupied by a widget executing a function for + * each one. + * + * @method for_each_row_occupied + * @param {Object} el_grid_data The grid coords object that represents + * the widget. + * @param {Function} callback The function to execute on each column + * iteration. The row number is passed as first argument. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.for_each_row_occupied = function(el_grid_data, callback) { + for (var i = 0; i < el_grid_data.size_y; i++) { + var row = el_grid_data.row + i; + callback.call(this, row, el_grid_data); + } + }; + + fn.clean_up_changed = function() { + $gr = this; + $gr.$changed.each(function() { + if ($gr.options.shift_larger_widgets_down) { + $gr.move_widget_up($(this)); + } + }); + } + + fn._traversing_widgets = function(type, direction, col, row, callback) { + var ga = this.gridmap; + if (!ga[col]) { + return; + } + + var cr, max; + var action = type + '/' + direction; + if (arguments[2] instanceof jQuery) { + var el_grid_data = arguments[2].coords().grid; + col = el_grid_data.col; + row = el_grid_data.row; + callback = arguments[3]; + } + var matched = []; + var trow = row; + + var methods = { + 'for_each/above' : function() { + while (--trow >= 0) { + if (trow > 0 && this.is_widget(col, trow) && $.inArray(ga[col][trow], matched) === -1) { + cr = callback.call(ga[col][trow], col, trow); + matched.push(ga[col][trow]); + if (cr) { + break; + } + } + } + }, + 'for_each/below' : function() { + for ( trow = row + 1, max = ga[col].length; trow < max; trow += 0.5) { + if (this.is_widget(col, trow) && $.inArray(ga[col][trow], matched) === -1) { + cr = callback.call(ga[col][trow], col, trow); + matched.push(ga[col][trow]); + //break was causing problems, leaving for testing. + //if (cr) { break; } + } + } + } + }; + + if (methods[action]) { + methods[action].call(this); + } + }; + + /** + * Iterate over each widget above the column and row specified. + * + * @method for_each_widget_above + * @param {Number} col The column to start iterating. + * @param {Number} row The row to start iterating. + * @param {Function} callback The function to execute on each widget + * iteration. The value of `this` inside the function is the jQuery + * wrapped HTMLElement. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.for_each_widget_above = function(col, row, callback) { + this._traversing_widgets('for_each', 'above', col, row, callback); + return this; + }; + + /** + * Iterate over each widget below the column and row specified. + * + * @method for_each_widget_below + * @param {Number} col The column to start iterating. + * @param {Number} row The row to start iterating. + * @param {Function} callback The function to execute on each widget + * iteration. The value of `this` inside the function is the jQuery wrapped + * HTMLElement. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.for_each_widget_below = function(col, row, callback) { + this._traversing_widgets('for_each', 'below', col, row, callback); + return this; + }; + + /** + * Returns the highest occupied cell in the grid. + * + * @method get_highest_occupied_cell + * @return {Object} Returns an object with `col` and `row` numbers. + */ + fn.get_highest_occupied_cell = function() { + var r; + var gm = this.gridmap; + var rows = []; + var row_in_col = []; + for (var c = gm.length - 1; c >= 1; c--) { + for ( r = gm[c].length - 1; r >= 1; r--) { + if (this.is_widget(c, r)) { + rows.push(r); + row_in_col[r] = c; + break; } } } + + var highest_row = Math.max.apply(Math, rows); + + this.highest_occupied_cell = { + col : row_in_col[highest_row], + row : highest_row + }; + + return this.highest_occupied_cell; }; - if (methods[action]) { - methods[action].call(this); - } - }; + fn.get_widgets_from = function(col, row) { + var ga = this.gridmap; + var $widgets = $(); - - /** - * Iterate over each widget above the column and row specified. - * - * @method for_each_widget_above - * @param {Number} col The column to start iterating. - * @param {Number} row The row to start iterating. - * @param {Function} callback The function to execute on each widget - * iteration. The value of `this` inside the function is the jQuery - * wrapped HTMLElement. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.for_each_widget_above = function(col, row, callback) { - this._traversing_widgets('for_each', 'above', col, row, callback); - return this; - }; - - - /** - * Iterate over each widget below the column and row specified. - * - * @method for_each_widget_below - * @param {Number} col The column to start iterating. - * @param {Number} row The row to start iterating. - * @param {Function} callback The function to execute on each widget - * iteration. The value of `this` inside the function is the jQuery wrapped - * HTMLElement. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.for_each_widget_below = function(col, row, callback) { - this._traversing_widgets('for_each', 'below', col, row, callback); - return this; - }; - - - /** - * Returns the highest occupied cell in the grid. - * - * @method get_highest_occupied_cell - * @return {Object} Returns an object with `col` and `row` numbers. - */ - fn.get_highest_occupied_cell = function() { - var r; - var gm = this.gridmap; - var rows = []; - var row_in_col = []; - for (var c = gm.length - 1; c >= 1; c--) { - for (r = gm[c].length - 1; r >= 1; r--) { - if (this.is_widget(c, r)) { - rows.push(r); - row_in_col[r] = c; - break; - } - } - } - - var highest_row = Math.max.apply(Math, rows); - - this.highest_occupied_cell = { - col: row_in_col[highest_row], - row: highest_row - }; - - return this.highest_occupied_cell; - }; - - - fn.get_widgets_from = function(col, row) { - var ga = this.gridmap; - var $widgets = $(); - - if (col) { - $widgets = $widgets.add( - this.$widgets.filter(function() { + if (col) { + $widgets = $widgets.add(this.$widgets.filter(function() { var tcol = $(this).attr('data-col'); return (tcol === col || tcol > col); - }) - ); - } + })); + } - if (row) { - $widgets = $widgets.add( - this.$widgets.filter(function() { + if (row) { + $widgets = $widgets.add(this.$widgets.filter(function() { var trow = $(this).attr('data-row'); return (trow === row || trow > row); - }) - ); - } - - return $widgets; - }; - - - /** - * Set the current height of the parent grid. - * - * @method set_dom_grid_height - * @return {Object} Returns the instance of the Gridster class. - */ - fn.set_dom_grid_height = function() { - var r = this.get_highest_occupied_cell().row; - this.$el.css('height', r * this.min_widget_height); - return this; - }; - - - /** - * It generates the neccessary styles to position the widgets. - * - * @method generate_stylesheet - * @param {Number} rows Number of columns. - * @param {Number} cols Number of rows. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.generate_stylesheet = function(opts) { - var styles = ''; - var max_size_x = this.options.max_size_x; - var max_rows = 0; - var max_cols = 0; - var i; - var rules; - - opts || (opts = {}); - opts.cols || (opts.cols = this.cols); - opts.rows || (opts.rows = this.rows); - opts.namespace || (opts.namespace = this.options.namespace); - opts.widget_base_dimensions || - (opts.widget_base_dimensions = this.options.widget_base_dimensions); - opts.widget_margins || - (opts.widget_margins = this.options.widget_margins); - opts.min_widget_width = (opts.widget_margins[0] * 2) + - opts.widget_base_dimensions[0]; - opts.min_widget_height = (opts.widget_margins[1] * 2) + - opts.widget_base_dimensions[1]; - - // don't duplicate stylesheets for the same configuration - var serialized_opts = $.param(opts); - if ($.inArray(serialized_opts, Gridster.generated_stylesheets) >= 0) { - return false; - } - - Gridster.generated_stylesheets.push(serialized_opts); - - /* generate CSS styles for cols */ - for (i = opts.cols; i >= 0; i--) { - styles += (opts.namespace + ' [data-col="'+ (i + 1) + '"] { left:' + - ((i * opts.widget_base_dimensions[0]) + - (i * opts.widget_margins[0]) + - ((i + 1) * opts.widget_margins[0])) + 'px;} '); - } - - /* generate CSS styles for rows */ - for (i = opts.rows; i >= 0; i--) { - styles += (opts.namespace + ' [data-row="' + (i + 1) + '"] { top:' + - ((i * opts.widget_base_dimensions[1]) + - (i * opts.widget_margins[1]) + - ((i + 1) * opts.widget_margins[1]) ) + 'px;} '); - } - - for (var y = 1; y <= opts.rows; y++) { - styles += (opts.namespace + ' [data-sizey="' + y + '"] { height:' + - (y * opts.widget_base_dimensions[1] + - (y - 1) * (opts.widget_margins[1] * 2)) + 'px;}'); - } - - for (var x = 1; x <= max_size_x; x++) { - styles += (opts.namespace + ' [data-sizex="' + x + '"] { width:' + - (x * opts.widget_base_dimensions[0] + - (x - 1) * (opts.widget_margins[0] * 2)) + 'px;}'); - } - - return this.add_style_tag(styles); - }; - - - /** - * Injects the given CSS as string to the head of the document. - * - * @method add_style_tag - * @param {String} css The styles to apply. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.add_style_tag = function(css) { - var d = document; - var tag = d.createElement('style'); - - d.getElementsByTagName('head')[0].appendChild(tag); - tag.setAttribute('type', 'text/css'); - - if (tag.styleSheet) { - tag.styleSheet.cssText = css; - }else{ - tag.appendChild(document.createTextNode(css)); - } - return this; - }; - - - /** - * Generates a faux grid to collide with it when a widget is dragged and - * detect row or column that we want to go. - * - * @method generate_faux_grid - * @param {Number} rows Number of columns. - * @param {Number} cols Number of rows. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.generate_faux_grid = function(rows, cols) { - this.faux_grid = []; - this.gridmap = []; - var col; - var row; - for (col = cols; col > 0; col--) { - this.gridmap[col] = []; - for (row = rows; row > 0; row--) { - this.add_faux_cell(row, col); + })); } - } - return this; - }; + return $widgets; + }; - /** - * Add cell to the faux grid. - * - * @method add_faux_cell - * @param {Number} row The row for the new faux cell. - * @param {Number} col The col for the new faux cell. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.add_faux_cell = function(row, col) { - var coords = $({ - left: this.baseX + ((col - 1) * this.min_widget_width), - top: this.baseY + (row -1) * this.min_widget_height, - width: this.min_widget_width, - height: this.min_widget_height, - col: col, - row: row, - original_col: col, - original_row: row - }).coords(); + /** + * Set the current height of the parent grid. + * + * @method set_dom_grid_height + * @return {Object} Returns the instance of the Gridster class. + */ + fn.set_dom_grid_height = function() { + var maxPosY = 0; + var $liMaxPosY = $(); + + var self = this; + this.$widgets.each(function() { + var $li = $(this); + var posY = parseFloat($li.attr('data-row')) + parseFloat($li.attr('data-sizey')); + if(maxPosY < posY) { + maxPosY = posY; + $liMaxPosY = $li; + } + }); + + var blockHeight = this.options.widget_base_dimensions[1] + this.options.widget_margins[1] * 2; + var newHeight = ( parseFloat($liMaxPosY.attr('data-row')) - 1 + parseFloat($liMaxPosY.attr('data-sizey')) ) * blockHeight; + + this.$el.height(newHeight); + return this; + }; - if (!$.isArray(this.gridmap[col])) { - this.gridmap[col] = []; - } + /** + * It generates the neccessary styles to position the widgets. + * + * @method generate_stylesheet + * @param {Number} rows Number of columns. + * @param {Number} cols Number of rows. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.generate_stylesheet = function(opts) { + var styles = ''; + var max_size_x = this.options.max_size_x; + var max_rows = 0; + var max_cols = 0; + var i; + var rules; - this.gridmap[col][row] = false; - this.faux_grid.push(coords); + opts || ( opts = {}); + opts.cols || (opts.cols = this.cols); + opts.rows || (opts.rows = this.rows); + opts.namespace || (opts.namespace = this.options.namespace); + opts.widget_base_dimensions || (opts.widget_base_dimensions = this.options.widget_base_dimensions); + opts.widget_margins || (opts.widget_margins = this.options.widget_margins); + opts.min_widget_width = (opts.widget_margins[0] * 2) + opts.widget_base_dimensions[0]; + opts.min_widget_height = (opts.widget_margins[1] * 2) + opts.widget_base_dimensions[1]; - return this; - }; + // don't duplicate stylesheets for the same configuration + var serialized_opts = $.param(opts); + //if ($.inArray(serialized_opts, Gridster.generated_stylesheets) >= 0) { + // return false; + // } + Gridster.generated_stylesheets.push(serialized_opts); - /** - * Add rows to the faux grid. - * - * @method add_faux_rows - * @param {Number} rows The number of rows you want to add to the faux grid. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.add_faux_rows = function(rows) { - var actual_rows = this.rows; - var max_rows = actual_rows + (rows || 1); + /* generate CSS styles for cols */ + for ( i = opts.cols; i >= 0; i--) { + styles += (opts.namespace + ' [data-col="' + (i + 1) + '"] { left:' + ((i * opts.widget_base_dimensions[0]) + (i * opts.widget_margins[0]) + ((i + 1) * opts.widget_margins[0])) + 'px;} '); - for (var r = max_rows; r > actual_rows; r--) { - for (var c = this.cols; c >= 1; c--) { - this.add_faux_cell(r, c); + styles += (opts.namespace + ' [data-col="' + (i + 1) + '.5"] { left:' + (((i + 0.5) * opts.widget_base_dimensions[0]) + ((i + 0.5) * opts.widget_margins[0]) + (((i + 0.5) + 1) * opts.widget_margins[0])) + 'px;} '); } - } - this.rows = max_rows; + /* generate CSS styles for rows */ + for ( i = opts.rows; i >= 0; i--) { + styles += (opts.namespace + ' [data-row="' + (i + 1) + '"] { top:' + ((i * opts.widget_base_dimensions[1]) + (i * opts.widget_margins[1]) + ((i + 1) * opts.widget_margins[1]) ) + 'px;} '); - if (this.options.autogenerate_stylesheet) { - this.generate_stylesheet(); - } - - return this; - }; - - /** - * Add cols to the faux grid. - * - * @method add_faux_cols - * @param {Number} cols The number of cols you want to add to the faux grid. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.add_faux_cols = function(cols) { - var actual_cols = this.cols; - var max_cols = actual_cols + (cols || 1); - - for (var c = actual_cols; c < max_cols; c++) { - for (var r = this.rows; r >= 1; r--) { - this.add_faux_cell(r, c); + styles += (opts.namespace + ' [data-row="' + (i + 1) + '.5"] { top:' + (((i + 0.5) * opts.widget_base_dimensions[1]) + ((i + 0.5) * opts.widget_margins[1]) + (((i + 0.5) + 1) * opts.widget_margins[1]) ) + 'px;} '); } - } - this.cols = max_cols; + for (var y = 1; y <= opts.rows; y++) { + styles += (opts.namespace + ' [data-sizey="' + y + '"] { height:' + (y * opts.widget_base_dimensions[1] + (y - 1) * (opts.widget_margins[1] * 2)) + 'px;}'); - if (this.options.autogenerate_stylesheet) { - this.generate_stylesheet(); - } + styles += (opts.namespace + ' [data-sizey="' + y + '.5"] { height:' + ((y + 0.5) * opts.widget_base_dimensions[1] + ((y + 0.5) - 1) * (opts.widget_margins[1] * 2)) + 'px;}'); + } - return this; - }; + for (var x = 1; x <= max_size_x; x++) { + styles += (opts.namespace + ' [data-sizex="' + x + '"] { width:' + (x * opts.widget_base_dimensions[0] + (x - 1) * (opts.widget_margins[0] * 2)) + 'px;}'); + styles += (opts.namespace + ' [data-sizex="' + x + '.5"] { width:' + ((x + 0.5) * opts.widget_base_dimensions[0] + ((x + 0.5) - 1) * (opts.widget_margins[0] * 2)) + 'px;}'); + } - /** - * Recalculates the offsets for the faux grid. You need to use it when - * the browser is resized. - * - * @method recalculate_faux_grid - * @return {Object} Returns the instance of the Gridster class. - */ - fn.recalculate_faux_grid = function() { - var aw = this.$wrapper.width(); - this.baseX = ($(window).width() - aw) / 2; - this.baseY = this.$wrapper.offset().top; + return this.add_style_tag(styles); + }; - $.each(this.faux_grid, $.proxy(function(i, coords) { - this.faux_grid[i] = coords.update({ - left: this.baseX + (coords.data.col -1) * this.min_widget_width, - top: this.baseY + (coords.data.row -1) * this.min_widget_height + /** + * Injects the given CSS as string to the head of the document. + * + * @method add_style_tag + * @param {String} css The styles to apply. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_style_tag = function(css) { + + $('style#gridster-style').remove(); + + var d = document; + var tag = d.createElement('style'); + + tag.setAttribute('id', 'gridster-style'); + tag.setAttribute('type', 'text/css'); + d.getElementsByTagName('head')[0].appendChild(tag); + + if (tag.styleSheet) { + tag.styleSheet.cssText = css; + } else { + tag.appendChild(document.createTextNode(css)); + } + + return this; + }; + + /** + * Generates a faux grid to collide with it when a widget is dragged and + * detect row or column that we want to go. + * + * @method generate_faux_grid + * @param {Number} rows Number of columns. + * @param {Number} cols Number of rows. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.generate_faux_grid = function(rows, cols) { + this.faux_grid = []; + this.gridmap = []; + var col; + var row; + for ( col = cols; col > 0; col--) { + this.gridmap[col] = []; + for ( row = rows; row > 0; row--) { + this.add_faux_cell(row, col); + } + } + return this; + }; + + /** + * Add cell to the faux grid. + * + * @method add_faux_cell + * @param {Number} row The row for the new faux cell. + * @param {Number} col The col for the new faux cell. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_faux_cell = function(row, col) { + var coords = $({ + left : this.baseX + ((col - 1) * this.min_widget_width), + top : this.baseY + (row - 1) * this.min_widget_height, + width : this.min_widget_width, + height : this.min_widget_height, + col : col, + row : row, + original_col : col, + original_row : row + }).coords(); + + if (!$.isArray(this.gridmap[col])) { + this.gridmap[col] = []; + } + + this.gridmap[col][row] = false; + this.faux_grid.push(coords); + + return this; + }; + + /** + * Add rows to the faux grid. + * + * @method add_faux_rows + * @param {Number} rows The number of rows you want to add to the faux grid. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_faux_rows = function(rows) { + var actual_rows = this.rows; + var max_rows = actual_rows + (rows || 1); + + for (var r = max_rows; r > actual_rows; r--) { + for (var c = this.cols; c >= 1; c--) { + this.add_faux_cell(r, c); + } + } + + this.rows = max_rows; + + if (this.options.autogenerate_stylesheet) { + this.generate_stylesheet(); + } + + return this; + }; + + /** + * Add cols to the faux grid. + * + * @method add_faux_cols + * @param {Number} cols The number of cols you want to add to the faux grid. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_faux_cols = function(cols) { + var actual_cols = this.cols; + var max_cols = actual_cols + (cols || 1); + + for (var c = actual_cols; c < max_cols; c++) { + for (var r = this.rows; r >= 1; r--) { + this.add_faux_cell(r, c); + } + } + + this.cols = max_cols; + + if (this.options.autogenerate_stylesheet) { + this.generate_stylesheet(); + } + + return this; + }; + + /** + * Recalculates the offsets for the faux grid. You need to use it when + * the browser is resized. + * + * @method recalculate_faux_grid + * @return {Object} Returns the instance of the Gridster class. + */ + fn.recalculate_faux_grid = function() { + var aw = this.$wrapper.width(); + this.baseX = ($(window).width() - aw) / 2; + this.baseY = this.$wrapper.offset().top; + + $.each(this.faux_grid, $.proxy(function(i, coords) { + this.faux_grid[i] = coords.update({ + left : this.baseX + (coords.data.col - 1) * this.min_widget_width, + top : this.baseY + (coords.data.row - 1) * this.min_widget_height + }); + + }, this)); + + return this; + }; + + /** + * Get all widgets in the DOM and register them. + * + * @method get_widgets_from_DOM + * @return {Object} Returns the instance of the Gridster class. + */ + fn.get_widgets_from_DOM = function() { + this.$widgets.each($.proxy(function(i, widget) { + this.register_widget($(widget)); + }, this)); + return this; + }; + + /** + * Calculate columns and rows to be set based on the configuration + * parameters, grid dimensions, etc ... + * + * @method generate_grid_and_stylesheet + * @return {Object} Returns the instance of the Gridster class. + */ + fn.generate_grid_and_stylesheet = function() { + var aw = this.$wrapper.width(); + var ah = this.$wrapper.height(); + + var cols = Math.floor(aw / this.min_widget_width) + this.options.extra_cols; + + var actual_cols = this.$widgets.map(function() { + return $(this).attr('data-col'); + }); + actual_cols = Array.prototype.slice.call(actual_cols, 0); + //needed to pass tests with phantomjs + actual_cols.length || ( actual_cols = [0]); + + var min_cols = Math.max.apply(Math, actual_cols); + + // get all rows that could be occupied by the current widgets + var max_rows = this.options.extra_rows; + this.$widgets.each(function(i, w) { + max_rows += (+$(w).attr('data-sizey')); }); - }, this)); + this.cols = Math.max(min_cols, cols, this.options.min_cols); + var max_cols = this.options.max_cols; + if (max_cols >= -1 && max_cols >= min_cols) { + if (max_cols < this.cols) { + this.cols = max_cols; + } + } + //this.rows = Math.max(max_rows, this.options.min_rows); + this.rows = this.options.max_rows; - return this; - }; + this.baseX = ($(window).width() - aw) / 2; + this.baseY = this.$wrapper.offset().top; + if (this.options.autogenerate_stylesheet) { + this.generate_stylesheet(); + } - /** - * Get all widgets in the DOM and register them. - * - * @method get_widgets_from_DOM - * @return {Object} Returns the instance of the Gridster class. - */ - fn.get_widgets_from_DOM = function() { - this.$widgets.each($.proxy(function(i, widget) { - this.register_widget($(widget)); - }, this)); - return this; - }; + return this.generate_faux_grid(this.rows, this.cols); + }; + //jQuery adapter + $.fn.gridster = function(options) { + return this.each(function() { + if (!$(this).data('gridster')) { + $(this).data('gridster', new Gridster(this, options)); + } + }); + }; - /** - * Calculate columns and rows to be set based on the configuration - * parameters, grid dimensions, etc ... - * - * @method generate_grid_and_stylesheet - * @return {Object} Returns the instance of the Gridster class. - */ - fn.generate_grid_and_stylesheet = function() { - var aw = this.$wrapper.width(); - var ah = this.$wrapper.height(); + $.Gridster = fn; - var cols = Math.floor(aw / this.min_widget_width) + - this.options.extra_cols; - - var actual_cols = this.$widgets.map(function() { - return $(this).attr('data-col'); - }); - actual_cols = Array.prototype.slice.call(actual_cols, 0); - //needed to pass tests with phantomjs - actual_cols.length || (actual_cols = [0]); - - var min_cols = Math.max.apply(Math, actual_cols); - - // get all rows that could be occupied by the current widgets - var max_rows = this.options.extra_rows; - this.$widgets.each(function(i, w) { - max_rows += (+$(w).attr('data-sizey')); - }); - - - this.cols = Math.max(min_cols, cols, this.options.min_cols); - this.rows = Math.max(max_rows, this.options.min_rows); - - this.baseX = ($(window).width() - aw) / 2; - this.baseY = this.$wrapper.offset().top; - - if (this.options.autogenerate_stylesheet) { - this.generate_stylesheet(); - } - - return this.generate_faux_grid(this.rows, this.cols); - }; - - - //jQuery adapter - $.fn.gridster = function(options) { - return this.each(function() { - if (!$(this).data('gridster')) { - $(this).data('gridster', new Gridster( this, options )); - } - }); - }; - - $.Gridster = fn; - -}(jQuery, window, document)); + }(jQuery, window, document)); \ No newline at end of file diff --git a/app/assets/Archive/javascripts/orbitdesktop.js b/app/assets/Archive/javascripts/orbitdesktop.js index 6897144d..72f23f43 100755 --- a/app/assets/Archive/javascripts/orbitdesktop.js +++ b/app/assets/Archive/javascripts/orbitdesktop.js @@ -324,7 +324,9 @@ var orbitDesktop = function(dom){ $(window).resize(function(){ // var ww = $(window).width(); // $("img#thmbackground").attr({"width":ww}); - if( $('.tinycanvas').length > 0 ){ $('.tinycanvas').tinyscrollbar_update('relative'); } + // if( $('.tinycanvas').length > 0 ){ + $('.tinycanvas').tinyscrollbar_update('relative'); + // } if($(o.contentHolder).find("div.app_frame").length > 0){ $(o.contentHolder).find("div.app_frame").each(function(){ var app_holder_height = $(this).height() - 72; @@ -378,7 +380,7 @@ var orbitDesktop = function(dom){ var temp_div = $("
    "); var entries = []; switch (layout){ - case "simple": + case "simple": total_columns++; temp_div.append(column_container.html()); total_width = "auto"; @@ -444,7 +446,7 @@ var orbitDesktop = function(dom){ } $("div[container=true]") .find("div.tinycanvas") - .prepend('
    '); + .prepend('
    '); var f = o.layout_data.generate_layout_html(h) $("div[container=true] div.overview").html(f.markup); var settings = {main : ".tinycanvas"}; @@ -471,6 +473,7 @@ var orbitDesktop = function(dom){ o.simple_drop_down(); o.autocomplete(); o.use_select2(); + o.check_simple_layout(); } this.paging = true; this.pagination = function(link,variable,page_no){ @@ -652,6 +655,10 @@ var orbitDesktop = function(dom){ target = (!target) ? 'desktop' : target; var bindHandlers = function(){ // this function will bind all the handlers in the desktop var dragged = null,draggable,lastpos = []; + /* Hotfix - Part 1 */ + var items = $(".gridster ul li"); + items.detach(); + o.gridvar = $(".grid ul").find("> li ") .mousedown(function(e){ !draggable; @@ -663,6 +670,8 @@ var orbitDesktop = function(dom){ }) .end() .gridster({ + max_rows: 4, + shift_larger_widgets_down: false, widget_margins: [6, 6], widget_base_dimensions: [120, 120], serialize_params: function($w, wgd) { return { col: wgd.col, row: wgd.row, id: $w.data("id") } }, @@ -671,21 +680,33 @@ var orbitDesktop = function(dom){ dragged.addClass("noClick"); }, stop: function(event, ui){ - for (var i = 1; i <= 30; i++) { - // var celement = $(".grid ul .widget[data-col="+i+"]:last"); - $(".grid ul .widget[data-col="+i+"]").each(function(){ - var pos = $(this).position(); - if(pos && (pos.top + $(this).height() + 6) > 550){ - revertbacktiles(); - } - }); - } - var newpos = o.gridvar.serialize(); - $.post("/desktop/newpositions",{"newpositions":newpos}); + // for (var i = 1; i <= 30; i++) { + // // var celement = $(".grid ul .widget[data-col="+i+"]:last"); + // $(".grid ul .widget[data-col="+i+"]").each(function(){ + // var pos = $(this).position(); + // if(pos && (pos.top + $(this).height() + 6) > 550){ + // revertbacktiles(); + // } + // }); + // } + $('.tinycanvas').tinyscrollbar_update(); + var newpos = o.gridvar.serialize(); + $.post("/desktop/newpositions",{"newpositions":newpos}); } } }) .data('gridster'); + + /* Hotfix - Part 2 */ + $.each(items , function (i, e) { + var item = $(this); + var columns = parseInt(item.attr("data-sizex")); + var rows = parseInt(item.attr("data-sizey")); + var col = parseInt(item.attr("data-col")); + var row = parseInt(item.attr("data-row")); + o.gridvar.add_widget(item, columns, rows, col, row); + }); + var revertbacktiles = function(){ lastpos.push({"col":"","row":""}); $(".grid ul li").each(function(i){ @@ -841,26 +862,37 @@ var orbitDesktop = function(dom){ var $elements = $("#app_manager .element"), $result = $("#app_manager .search_result"), $appinfo = $("#app_info"), - $apptitle = $("#app_info .app_info_name"), - $appicon = $("#app_info .app_info_icon") - $appinfolist = $("#app_info .app_info_list"); + $applist = $("#app_list"), + $apptitle = $appinfo.find(".app_info_name"), + $appicon = $appinfo.find(".app_info_icon"), + $appinfolist = $appinfo.find(".app_info_list"), + $canvas = $result.parents('.overview'), + current_width = $canvas.css('width'); $("#app_manager #searchbox") .focus(function(){ $(this).val(""); }) .keyup(function(e){ - if($(this).val()){ - $result.empty(); - $elements.hide(); + var query = $(this).val(); + if(query){ + $appinfo.hide().attr('style',''); + $result.empty().show(); + $applist.hide(); - searchArray = $elements.filter(":containsi("+$(this).val()+")"); + // searchArray = $elements.filter(":containsi("+$(this).val()+")").clone(1,1); + // searchArray = $('#app_list').find('[data-title*="'+query+'"]').clone(1,1); + searchArray = $elements.filter(function(){ + var matcher = new RegExp(query, "i"); + return matcher.test( $(this).attr('data-title') ); + }).clone(1,1); + if(searchArray.length > 0){ // searchArray.each(function(){ // $(this).hide("fold","fast"); // }) // $("#app_manager #seperator").show(); - var i = 0; + var i = 0, c = 1; // c for numbers of columns var $column; searchArray.each(function(){ i++; @@ -872,23 +904,50 @@ var orbitDesktop = function(dom){ if(i == 4){ $result.append($column); i = 0; + c++; } - }) + }); if(i != 0){ $result.append($column); + c*=132; + searchArray + .eq(0) + .parents('.overview') + .animate({ + 'width': c + },0, function(){ + $(this) + .parents('.tinycanvas') + .tinyscrollbar_update(); + }); } - elementSetting(); } else { - $result.text("No Result Found."); + var $no_result = $('
    No result for
    '); + $no_result + .find('b') + .text('"'+$(this).val()+'"') + .end() + .show() + .appendTo($result); + $canvas.animate({ + 'width':252 + }, 0, function(){ + $(this) + .parents('.tinycanvas') + .tinyscrollbar_update(); + }); } - - } else { + } else if( query == "" ) { + info_close(); $result.empty(); - $elements.show(); + $applist.show(); + $canvas.css('width',current_width); } }) .blur(function(){ - if($(this).val()=="") $(this).val("Search"); + if($(this).val()==""){ + $(this).val("Search"); + } }); //for Alphabet sorting @@ -921,15 +980,21 @@ var orbitDesktop = function(dom){ }); var elementSetting = function(){// for element setting load - $(".element").click(function(){ - $e = $(this); + $elements.click(function(){ + var $e = $(this); $apptitle.text($e.data("title")); $appicon.attr("src",$e.find("img").attr("src")); - $appinfolist.find("li:eq(2) div").text($e.data("version")); + $appinfolist.find("[info=app_version]").text($e.data("version")); var dt = new Date($e.data("update")); - $appinfolist.find("li:eq(3) div").text(dt.toUTCString()); - $appinfolist.find("li:eq(4) div").text($e.data("author")); - + $appinfolist.find("[info=app_last_update]").text(dt.toUTCString()); + $appinfolist.find("[info=app_author]").text($e.data("author")); + $appinfo + .find(".app_info_header") + .attr('style','') + .css({ + "background-color": $e.data("background"), + "color": $e.data("text-color") + }); var sections = elementSettingsData[$e.data("id")].sections; var sectionids = new Array(); @@ -937,7 +1002,7 @@ var orbitDesktop = function(dom){ sectionids.push(sec.id); }) - $appinfolist.find("[info=section_activation]").empty(); + $appinfolist.find("[info=app_section_activation]").empty(); $.each(o.sectionList,function(i,sec){ var present = $.inArray(sec._id,sectionids); @@ -946,9 +1011,9 @@ var orbitDesktop = function(dom){ }else{ $sectionswitch = $('
    On
    '); } - $appinfolist.find("[info=section_activation]").append($sectionswitch); - }) - $appinfo.show(); + $appinfolist.find("[info=app_section_activation]").append($sectionswitch); + }); + info_open(); o.simple_switch(function(dom){ var options = { "status" : dom.is(":checked"), @@ -974,39 +1039,91 @@ var orbitDesktop = function(dom){ } } } - }) + }); }); - }) + return false; + }); } $appinfo.find("a.panel_close").click(function(){ - $appinfo.hide(); + info_close(); return false; - }) + }); + + var info_open = function(){ + if($appinfo.is(":hidden")){ + $('.overview').stop().animate({'width': '+=480'},0 , function(){ + $('.tinycanvas').tinyscrollbar_update(); + }); + $appinfo + .css({ + 'display': 'block', + 'margin-left': -480 + }) + .delay(300) + .animate({ + 'margin-left': 0 + }, 500, 'easeInOutQuint'); + } else { + $('.tinycanvas').tinyscrollbar_update(); + } + } + + var info_close = function(){ + if( $appinfo.is(":visible") ){ + $appinfo + .stop() + .animate({ + 'margin-left': -480 + },500 , 'easeInOutQuint', function(){ + $('.overview').animate({'width': '-=480'}, 0, function(){ + $('.tinycanvas').tinyscrollbar_update(); + }); + $(this).attr("style",""); + }); + } else { + $('.tinycanvas').tinyscrollbar_update(); + } + } + $(window).on('keydown', function(){ + if($('#searchbox').val() == 'Search'){ + $('#searchbox').focus(); + } + }); elementSetting(); o.appname_substr('#group_wrapper .element'); - + o.check_simple_layout(); } var elementSettingsData = {}; var loadApps = function(){ //this load apps for sorting and searching - $('#app_list').empty(); + $('#app_info').attr('style',''); + $('.search_result').hide().text(""); + $('#app_list').empty().show(); $.getJSON("/desktop/getapplistforManager",{desktopid:o.desktopId},function(apps){ - var count = 0, colindex = 0; - $('#app_list').append('
    '); - $.each(apps,function(i,app){ - var $app; - count++; - elementSettingsData[app.id] = {"sections" : app.sections}; - o.t[app.id] = {"sections" : app.sections}; - $app = $('

    '+app.title+'

    '); - if( count > 4 ){ - count = 1, colindex+=1; - $('#app_list').append('
    '); - $('.g_col.col'+ colindex).append($app); - } else { - $('.g_col.col'+ colindex).append($app); - } - }); + var count = 0, + colindex = 0, + apps_sum = apps.length, + col_sum; + + col_sum = (apps_sum - (apps_sum % 4)) / 4 + 1; + + $('#app_list') + .css('width',col_sum * 132) + .append('
    '); + + $.each(apps,function(i,app){ + var $app; + count++; + elementSettingsData[app.id] = {"sections" : app.sections}; + $app = $('

    '+app.title+'

    '); + if( count > 4 ){ + count = 1, colindex+=1; + $('#app_list').append('
    '); + $('.g_col.col'+ colindex).append($app); + } else { + $('.g_col.col'+ colindex).append($app); + } + }); bindHandlers(); }); } @@ -1627,6 +1744,25 @@ var orbitDesktop = function(dom){ o.tinyscrollbar = target.main.tinyscrollbar( settings ); }; + this.check_simple_layout = function(){ + // use for various width of columns inside canvas(.overview) + // only count width for visible element which has "s_column" class + // then set width to canvas + // ** not sure to update canvas here ( $.tinyscrollbar_update() ) + var sw = 0, + $simple_layout = $('#content .overview[content-layout=simple]'); + if($simple_layout.find(".s_column").length > 0){ + $simple_layout.each(function(){ + $(this).find(".s_column").filter(":visible").each(function(){ + sw += $(this).outerWidth(); + }); + $simple_layout.css('width',sw); + }); + } + // if($simple_layout.find(".s_column").length > 0){ + // $simple_layout.css('width',sw); + // } + }; this.simple_drop_down = function(){ // simple dropdown menu var $sdm = $('.sdm'); @@ -1686,8 +1822,9 @@ var orbitDesktop = function(dom){ $status.text(status_off); break; } - if(typeof func == "function") + if(typeof func == "function"){ func.call(this,$(this)); + } }); }); }; diff --git a/app/assets/Archive/stylesheets/desktop/desktop-component.css b/app/assets/Archive/stylesheets/desktop/desktop-component.css index 44474b32..86fdc7b7 100644 --- a/app/assets/Archive/stylesheets/desktop/desktop-component.css +++ b/app/assets/Archive/stylesheets/desktop/desktop-component.css @@ -424,7 +424,7 @@ width: 6px; } .tinycanvas .scrollbar { position: absolute; - z-index: 9; + z-index: 11; visibility: hidden; opacity: 0; -webkit-transition: opacity 0.3s ease; @@ -435,7 +435,7 @@ visibility: visible; } .tinycanvas .scrollbar.sb_h { left: 0; - bottom: -6px; + bottom: 0; height: 6px; } .tinycanvas .scrollbar.sb_v { right: 0; diff --git a/app/assets/Archive/stylesheets/desktop/desktop-component.scss b/app/assets/Archive/stylesheets/desktop/desktop-component.scss index 4f7331e1..ea6ccd14 100644 --- a/app/assets/Archive/stylesheets/desktop/desktop-component.scss +++ b/app/assets/Archive/stylesheets/desktop/desktop-component.scss @@ -424,7 +424,7 @@ } .scrollbar { position: absolute; - z-index: 9; + z-index: 11; visibility: hidden; opacity: 0; @include transition-type(opacity, 0.3); @@ -435,7 +435,7 @@ } &.sb_h { left: 0; - bottom: -6px; + bottom: 0; height: 6px; } &.sb_v { diff --git a/app/models/desktop/desktop_app.rb b/app/models/desktop/desktop_app.rb index 85813dbb..25eae317 100644 --- a/app/models/desktop/desktop_app.rb +++ b/app/models/desktop/desktop_app.rb @@ -6,8 +6,8 @@ class DesktopApp field :author field :shape field :version, :type => String - field :text_color, :type => String, default: "#fff" - field :bg_color, :type => String, default: "#fff" + field :text_color, :type => String, default: "" + field :bg_color, :type => String, default: "" field :icon, default: "icon.png" field :url