From 5edfe0a4880e11c58f8c0eba537f3df0d1b53a02 Mon Sep 17 00:00:00 2001 From: vieron Date: Mon, 23 Jul 2012 11:01:05 +0200 Subject: [PATCH] new methods, fixes and improvements: - new disable, enable methods - don't generate duplicated stylesheets for the same origin config - new configuration option called 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. - Removed jquery ui dependencie. - Fix to prevent widgets go out of the grid. - New methods to get all widgets from a column or row. - generate_stylesheets method now supports namespaces to generate multiple stylesheets based on a parent class. --- src/jquery.gridster.js | 227 +++++++++++++++++++++++++++-------------- 1 file changed, 151 insertions(+), 76 deletions(-) diff --git a/src/jquery.gridster.js b/src/jquery.gridster.js index f83c5d741..e481b642e 100644 --- a/src/jquery.gridster.js +++ b/src/jquery.gridster.js @@ -16,6 +16,7 @@ min_cols: 1, min_rows: 10, autogenerate_stylesheet: true, + avoid_overlapped_widgets: true, serialize_params: function($w, wgd) { return { col: wgd.col, @@ -23,47 +24,9 @@ }; }, collision: {}, - draggable: {} - }; - - - /* Debounce and throttle functions taken from underscore.js */ - var debounce = function(func, wait, immediate) { - var timeout; - return function() { - var context = this, args = arguments; - var later = function() { - timeout = null; - if (!immediate) func.apply(context, args); - }; - if (immediate && !timeout) func.apply(context, args); - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; - }; - - - var throttle = function(func, wait) { - var context, args, timeout, throttling, more, result; - var whenDone = debounce( - function(){ more = throttling = false; }, wait, true); - return function() { - context = this; args = arguments; - var later = function() { - timeout = null; - if (more) func.apply(context, args); - whenDone(); - }; - if (!timeout) timeout = setTimeout(later, wait); - if (throttling) { - more = true; - } else { - result = func.apply(context, args); - } - whenDone(); - throttling = true; - return result; - }; + draggable: { + distance: 4 + } }; @@ -123,6 +86,8 @@ this.init(); } + Gridster.generated_stylesheets = []; + var fn = Gridster.prototype; fn.init = function() { @@ -137,6 +102,30 @@ }; + /** + * Disable dragging. + * + * @method enable + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.disable = function(){ + this.drag_api.disable(); + return this; + } + + + /** + * Enable 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. * @@ -144,7 +133,7 @@ * @param {String} html The string representing the HTML of the widget. * @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 {HTMLElement} Returns the jQuery wrapped HTMLElement representing + * @return {HTMLElement} Returns the jQuery wrapped HTMLElement representing. * the widget that was just created. */ fn.add_widget = function(html, size_x, size_y) { @@ -161,12 +150,9 @@ this.register_widget($w); - this.$widgets.draggable('destroy'); - this.draggable(); - this.set_dom_grid_height(); - $w.fadeIn(); + return $w.fadeIn(); }; @@ -284,7 +270,8 @@ * @return {Array} Returns the instance of the Gridster class. */ fn.register_widget = function($el) { - var widget_grid_data = { + + var wgd = { 'col': parseInt($el.attr('data-col'), 10), 'row': parseInt($el.attr('data-row'), 10), 'size_x': parseInt($el.attr('data-sizex'), 10), @@ -292,13 +279,27 @@ '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 = widget_grid_data; + $el.data('coords').grid = wgd; - this.add_to_gridmap(widget_grid_data, $el); + this.add_to_gridmap(wgd, $el); this.widgets.push($el); return this; }; @@ -369,8 +370,10 @@ fn.draggable = function() { var self = this; var draggable_options = $.extend(true, {}, this.options.draggable, { - // containment : this.$wrapper, + offset_left: this.options.widget_margins[0], + items: '.gs_w', 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; @@ -380,16 +383,16 @@ self.$el.trigger('gridster:dragstart'); }, stop: function(event, ui) { - self.on_stop_drag.call(self, ui); + self.on_stop_drag.call(self, event, ui); self.$el.trigger('gridster:dragstop'); }, - drag: throttle(function(event, ui) { + drag: function(event, ui) { self.on_drag.call(self, event, ui); self.$el.trigger('gridster:drag'); - }, 100, true) + } }); - this.$widgets.draggable(draggable_options); + this.drag_api = this.$el.draggable(draggable_options).data('draggable'); return this; }; @@ -403,8 +406,10 @@ * See http://jqueryui.com/demos/draggable/ for more info. */ fn.on_start_drag = function(event, ui) { + + this.$helper.add(this.$player).add(this.$wrapper).addClass('dragging'); + this.$player.addClass('player'); - this.$wrapper.addClass('dragging'); this.player_grid_data = this.$player.coords().grid; this.placeholder_grid_data = $.extend({}, this.player_grid_data); @@ -423,8 +428,9 @@ this.last_cols = []; this.last_rows = []; + // see jquery.collision.js - this.drag_api = this.$helper.collision( + this.collision_api = this.$helper.collision( colliders, this.options.collision); this.$preview_holder = $('
  • ', { @@ -452,7 +458,11 @@ * See http://jqueryui.com/demos/draggable/ for more info. */ fn.on_drag = function(event, ui) { - this.colliders_data = this.drag_api.get_closest_colliders(); + 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, @@ -485,8 +495,11 @@ * See http://jqueryui.com/demos/draggable/ for more info. */ fn.on_stop_drag = function(event, ui) { - this.$wrapper.removeClass('dragging'); - this.colliders_data = this.drag_api.get_closest_colliders(); + 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, @@ -498,13 +511,14 @@ this.on_stop_overlapping_row ); - this.$player.add(this.$helper).attr({ + 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': '' - }).removeClass('player'); + }); this.$changed = this.$changed.add(this.$player); @@ -998,12 +1012,21 @@ 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); + }; + var moved_down = this.placeholder_grid_data.row < row; var changed_column = this.placeholder_grid_data.col !== col; this.placeholder_grid_data.col = col; this.placeholder_grid_data.row = row; + + this.cells_occupied_by_placeholder = this.get_cells_occupied( this.placeholder_grid_data); @@ -1624,6 +1647,12 @@ }; 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; + }; + 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))) { @@ -1868,6 +1897,33 @@ }; + + fn.get_widgets_from = function(col, row) { + var ga = this.gridmap; + var $widgets = $(); + + 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(){ + var trow = $(this).attr('data-row'); + return (trow == row || trow > row); + }) + ); + }; + + return $widgets; + } + + /** * Set the current height of the parent grid. * @@ -1890,7 +1946,7 @@ * @param {Number} cols Number of rows. * @return {Object} Returns the instance of the Gridster class. */ - fn.generate_stylesheet = function(rows, cols) { + fn.generate_stylesheet = function(opts) { var styles = ''; var extra_cells = 10; var max_size_y = 6; @@ -1898,28 +1954,47 @@ var i; var rules; + opts || (opts = {}); + opts.cols || (opts.cols = this.cols); + opts.rows || (opts.rows = this.rows); + opts.namespace || (opts.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]; + + var serialized_opts = $.param(opts); + // don't duplicate stylesheets for the same configuration + if ($.inArray(serialized_opts, Gridster.generated_stylesheets) >= 0) { + return false; + }; + + Gridster.generated_stylesheets.push(serialized_opts); + /* generate CSS styles for cols */ - for (i = cols + extra_cells; i >= 0; i--) { - styles += '[data-col="'+ (i + 1) +'"] { left: ' + - (i * this.min_widget_width) + + for (i = opts.cols + extra_cells; 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 = rows + extra_cells; i >= 0; i--) { - styles += '[data-row="' + (i + 1) + '"] { top: ' + - (i * this.min_widget_height) + 'px;} '; + for (i = opts.rows + extra_cells; 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 < max_size_y; y++) { - styles += '[data-sizey="' + (y) + '"] { height: ' + - (y * this.options.widget_base_dimensions[1] + (y-1)*(this.options.widget_margins[1]*2)) + 'px;}'; + 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 += '[data-sizex="' + (x) + '"] { width: ' + - (x * this.options.widget_base_dimensions[0] + (x-1)*(this.options.widget_margins[0]*2)) + 'px;}'; + 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); @@ -2050,19 +2125,19 @@ var min_cols = Math.max.apply(null, actual_cols); var min_rows = Math.max.apply(null, actual_rows); - cols = Math.max(min_cols, cols, this.options.min_cols); - rows = Math.max(min_rows, rows, this.options.min_rows); + this.cols = Math.max(min_cols, cols, this.options.min_cols); + this.rows = Math.max(min_rows, 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(rows, cols); + this.generate_stylesheet(); } /* more faux rows that needed are created so that there are cells * where drag beyond the limits */ - return this.generate_faux_grid(rows, cols); + return this.generate_faux_grid(this.rows, this.cols); };