From dbf98cc9bdcad136f1e1d62261b5d5a2e1b4e4d8 Mon Sep 17 00:00:00 2001 From: vieron Date: Mon, 20 Aug 2012 00:45:04 +0200 Subject: [PATCH] Resize widgets feature. Closes #15. By popular demand, now gridster allows to change the size of widgets dynamically. Usage. - gridster_api.resize_wiget($widget, [size_x], [size_y]); E.g. (try in gridster.net): - gridster.resize_widget($('.gs_w:eq(3)'), 6); - gridster.resize_widget($('.gs_w:eq(3)'), 1, 4); To do this, two new methods were added to gridster: - empty_cells(col, row, size_x, size_y, $exclude) Move down widgets in cells represented by the arguments col, row, size_x, size_y. - remove_empty_cells(col, row, size_x, size_y, $exclude) Move up widgets below cells represented by the arguments col, row, size_x, size_y. Also, add_widget method supports specifying a row and column. This was easy through empty_cells method. Related to #24. Thanks to @parkerd for the help! The code should be improved and make it more reusable when the feature of "move widgets dynamically" is added (related to #20). --- src/jquery.gridster.js | 262 ++++++++++++++++++++++++++++------------- 1 file changed, 179 insertions(+), 83 deletions(-) diff --git a/src/jquery.gridster.js b/src/jquery.gridster.js index 20ef36ac4..fa8a157ca 100644 --- a/src/jquery.gridster.js +++ b/src/jquery.gridster.js @@ -16,13 +16,14 @@ min_cols: 1, min_rows: 15, max_size_x: 6, - max_size_y: 6, autogenerate_stylesheet: true, avoid_overlapped_widgets: true, serialize_params: function($w, wgd) { return { col: wgd.col, - row: wgd.row + row: wgd.row, + size_x: wgd.size_x, + size_y: wgd.size_y }; }, collision: {}, @@ -56,8 +57,6 @@ * @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 {Number} [options.max_size_y] The maximum number of rows - * 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 @@ -142,17 +141,22 @@ * @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} [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) { - var pos = this.next_position(size_x, size_y); + pos = this.next_position(size_x, size_y); }else{ - var pos = { + pos = { col: col, row: row }; @@ -171,11 +175,7 @@ this.register_widget($w); - this.add_faux_rows(next_pos.size_y); - - if (this.options.autogenerate_stylesheet) { - this.generate_stylesheet(); - } + this.add_faux_rows(pos.size_y); this.set_dom_grid_height(); @@ -198,79 +198,127 @@ 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; - var $nexts = this.widgets_below({ - col: wgd.col, - row: wgd.row, - size_x: size_x, - size_y: size_y - }); - wgd.size_x = size_x; - wgd.size_y = size_y; - - if (wgd.col+wgd.size_x > this.cols) { - var diff = wgd.col+wgd.size_x-this.cols; - var c = wgd.col - diff; - wgd.col = c > 1 ? c : 1; + 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); }; - this.$player = this.$preview_holder = $widget; - this.player_grid_data = { - col: wgd.col, - row: wgd.row, - size_x: size_x, - size_y: size_y, - el: $widget - } - - this.placeholder_grid_data = $.extend({}, this.player_grid_data); - - $widget.attr({ - 'data-sizex': size_x, - 'data-sizey': size_y - }) - - this.set_player(wgd.col, wgd.row, true); - - this.placeholder_grid_data = this.player_grid_data = {}; - - this.remove_from_gridmap({ - col: old_col, - row: wgd.row, - size_x: old_size_x, - size_y: old_size_y - }); - - this.add_to_gridmap({ - col: wgd.col, + var new_grid_data = { + col: new_col, row: wgd.row, size_x: size_x, size_y: size_y - }, $widget); + }; - // if (!taller || !wider) { - $nexts.each($.proxy(function(i, widget) { - this.move_widget_up( - $(widget), old_size_y - size_y); - }, this)); - // }; + var new_cells_occupied = this.get_cells_occupied(new_grid_data); - this.set_dom_grid_height(); + 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); + + if (size_y > old_size_y) { + this.add_faux_rows(size_y - old_size_y); + }; + + $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; }; - - fn.empty_cells = function(col, row, size_x, size_y) { - + /** + * 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, @@ -278,17 +326,51 @@ size_y: size_y }); - $nexts.filter(function() { - console.log($(this).coords().grid.row, row + size_y -1 ); - return $(this).coords().grid.row <= (row + size_y - 1); - }).each($.proxy(function(i, w) { - this.move_widget_down($(w), 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. * @@ -680,7 +762,13 @@ } 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(); }; @@ -862,6 +950,11 @@ */ 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; } @@ -1123,7 +1216,7 @@ * @return {HTMLElement} Returns a jQuery collection of HTMLElements */ fn.get_widgets_under_player = function() { - var cells = this.cells_occupied_by_player; + var cells = this.cells_occupied_by_player || {cols: [], rows: []}; var $widgets = $([]); $.each(cells.cols, $.proxy(function(i, col) { @@ -1712,14 +1805,12 @@ 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 - } - }); + 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); @@ -1767,6 +1858,7 @@ 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) || @@ -2108,7 +2200,6 @@ */ fn.generate_stylesheet = function(opts) { var styles = ''; - var max_size_y = this.options.max_size_y; var max_size_x = this.options.max_size_x; var max_rows = 0; var max_cols = 0; @@ -2152,7 +2243,7 @@ ((i + 1) * opts.widget_margins[1]) ) + 'px;} '); } - for (var y = 1; y <= max_size_y; y++) { + 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;}'); @@ -2218,7 +2309,7 @@ /** * Add cell to the faux grid. * - * @method add_faux_rows + * @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. @@ -2260,6 +2351,11 @@ }; this.rows = max_rows; + + if (this.options.autogenerate_stylesheet) { + this.generate_stylesheet(); + } + return this; };