dashing-rails 2.4.3 → 2.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b0baba9fcea61f87fd46c60800b98db617de7058
4
- data.tar.gz: 474308f677df7b227d1ec4ec6f821db780504285
3
+ metadata.gz: 9333080f44e686f2ea430c16aacc839617384755
4
+ data.tar.gz: 1d876a9d631409fa33832e201a783310268a0839
5
5
  SHA512:
6
- metadata.gz: e65160a902d5e6bedfa6f87f53271084c8004fbade66a4173e0e5386a7a02483d1b6932815b08c4bf4252e8a6bc4e62f6dff4c386006e3148ef86a78cd30a057
7
- data.tar.gz: 99effb720ea594014df0cfdd6c56afad08f6097acefdf0342cac59d53b420bcb5b85dfb0578e3e3bf046ab0054fb9ddb6501a138e4dbfe3054fb5d8e3c5f73c6
6
+ metadata.gz: 518e8283c53c5e76b702073b062b7aceba01ada5634a9416b4711975f7804b102e15749663ca1132a086ee5317bffaf9c3a550734ad4b6c8721c69e6b1d32999
7
+ data.tar.gz: 10a8eb97f7a805f1a5f3108c96fe5631a7ad088c653fa96c2ce6d424b2b46eb896a55a2581647affd92362aeae22580c83ea436e3b98c0d825d6bb2bbb3aeeca
@@ -1,3 +1,10 @@
1
+ ## 2.4.4
2
+
3
+ * Upgrade jquery.gridster to 0.5.6
4
+ * Upgrade Rickshaw to 1.5.1
5
+ * Cherry pick https://github.com/Shopify/dashing/pull/438
6
+ * Cherry pick https://github.com/Shopify/dashing/pull/521
7
+
1
8
  ## 2.4.3
2
9
 
3
10
  * Fix bugs in `2.4.2` related to upgrades on graph widget without upgrading dashing-src and downgrading batman.js
data/Gemfile CHANGED
@@ -11,3 +11,10 @@ gemspec
11
11
  # your gem to rubygems.org.
12
12
 
13
13
  gem 'sqlite3'
14
+
15
+ group :test, :development do
16
+ gem 'rspec-rails', '~> 2.14'
17
+ gem 'simplecov', require: false
18
+ gem 'coveralls', require: false
19
+ gem 'generator_spec', require: false
20
+ end
@@ -1,5 +1,5 @@
1
1
  <h1 class="title" data-bind="title"></h1>
2
2
 
3
- <h2 class="value" data-bind="current | prettyNumber | prepend prefix"></h2>
3
+ <h2 class="value" data-bind="current | prettyNumber | prepend prefix | append suffix"></h2>
4
4
 
5
5
  <p class="more-info" data-bind="moreinfo"></p>
@@ -24,10 +24,6 @@ Gem::Specification.new do |spec|
24
24
  spec.add_dependency 'redis', '~> 3.2'
25
25
  spec.add_dependency 'connection_pool', '~> 2.1'
26
26
 
27
- spec.add_development_dependency 'rspec-rails', '~> 2.14'
28
27
  spec.add_development_dependency 'pry-rails'
29
28
  spec.add_development_dependency 'better_errors'
30
- spec.add_development_dependency 'coveralls'
31
- spec.add_development_dependency 'simplecov'
32
- spec.add_development_dependency 'generator_spec'
33
29
  end
@@ -1,3 +1,3 @@
1
1
  module Dashing
2
- VERSION = '2.4.3'
2
+ VERSION = '2.4.4'
3
3
  end
@@ -37,7 +37,6 @@ class Dashing.Widget extends Batman.View
37
37
  @mixin($(@node).data())
38
38
  Dashing.widgets[@id] ||= []
39
39
  Dashing.widgets[@id].push(@)
40
- @mixin(Dashing.lastEvents[@id]) # in case the events from the server came before the widget was rendered
41
40
 
42
41
  type = Batman.Filters.dashize(@view)
43
42
  $(@node).addClass("widget widget-#{type} #{@id}")
@@ -52,6 +51,12 @@ class Dashing.Widget extends Batman.View
52
51
  @::on 'ready', ->
53
52
  Dashing.Widget.fire 'ready'
54
53
 
54
+ # In case the events from the server came before the widget was rendered
55
+ lastData = Dashing.lastEvents[@id]
56
+ if lastData
57
+ @mixin(lastData)
58
+ @onData(lastData)
59
+
55
60
  receiveData: (data) =>
56
61
  @mixin(data)
57
62
  @onData(data)
@@ -106,8 +111,8 @@ source.addEventListener 'message', (e) ->
106
111
  if lastEvents[data.id]?.updatedAt != data.updatedAt
107
112
  if Dashing.debugMode
108
113
  console.log("Received data for #{data.id}", data)
114
+ lastEvents[data.id] = data
109
115
  if widgets[data.id]?.length > 0
110
- lastEvents[data.id] = data
111
116
  for widget in widgets[data.id]
112
117
  widget.receiveData(data)
113
118
 
@@ -16,7 +16,7 @@
16
16
  //= require jquery.leanModal.min
17
17
  //= require jquery.timeago
18
18
  //= require moment.min
19
- //= require rickshaw-1.4.3.min
19
+ //= require rickshaw-1.5.1.min
20
20
  //= require batman
21
21
  //= require batman.jquery
22
22
  //= require d3-3.2.8.min
@@ -1,8 +1,16 @@
1
- /*! gridster.js - v0.1.0 - 2012-08-14
1
+ /*! gridster.js - v0.5.6 - 2014-09-25
2
2
  * http://gridster.net/
3
- * Copyright (c) 2012 ducksboard; Licensed MIT */
3
+ * Copyright (c) 2014 ducksboard; Licensed MIT */
4
4
 
5
- ;(function($, window, document, undefined){
5
+ ;(function(root, factory) {
6
+
7
+ if (typeof define === 'function' && define.amd) {
8
+ define('gridster-coords', ['jquery'], factory);
9
+ } else {
10
+ root.GridsterCoords = factory(root.$ || root.jQuery);
11
+ }
12
+
13
+ }(this, function($) {
6
14
  /**
7
15
  * Creates objects with coordinates (x1, y1, x2, y2, cx, cy, width, height)
8
16
  * to simulate DOM elements on the screen.
@@ -55,6 +63,9 @@
55
63
 
56
64
  var d = this.data;
57
65
 
66
+ typeof d.left === 'undefined' && (d.left = d.x1);
67
+ typeof d.top === 'undefined' && (d.top = d.y1);
68
+
58
69
  this.coords.x1 = d.left;
59
70
  this.coords.y1 = d.top;
60
71
  this.coords.x2 = d.left + d.width;
@@ -89,6 +100,10 @@
89
100
  return this.coords;
90
101
  };
91
102
 
103
+ fn.destroy = function() {
104
+ this.el.removeData('coords');
105
+ delete this.el;
106
+ };
92
107
 
93
108
  //jQuery adapter
94
109
  $.fn.coords = function() {
@@ -101,12 +116,24 @@
101
116
  return ins;
102
117
  };
103
118
 
104
- }(jQuery, window, document));
119
+ return Coords;
120
+
121
+ }));
122
+
123
+ ;(function(root, factory) {
124
+
125
+ if (typeof define === 'function' && define.amd) {
126
+ define('gridster-collision', ['jquery', 'gridster-coords'], factory);
127
+ } else {
128
+ root.GridsterCollision = factory(root.$ || root.jQuery,
129
+ root.GridsterCoords);
130
+ }
105
131
 
106
- ;(function($, window, document, undefined){
132
+ }(this, function($, Coords) {
107
133
 
108
134
  var defaults = {
109
- colliders_context: document.body
135
+ colliders_context: document.body,
136
+ overlapping_region: 'C'
110
137
  // ,on_overlap: function(collider_data){},
111
138
  // on_overlap_start : function(collider_data){},
112
139
  // on_overlap_stop : function(collider_data){}
@@ -124,6 +151,9 @@
124
151
  * of HTMLElements or an Array of Coords instances.
125
152
  * @param {Object} [options] An Object with all options you want to
126
153
  * overwrite:
154
+ * @param {String} [options.overlapping_region] Determines when collision
155
+ * is valid, depending on the overlapped area. Values can be: 'N', 'S',
156
+ * 'W', 'E', 'C' or 'all'. Default is 'C'.
127
157
  * @param {Function} [options.on_overlap_start] Executes a function the first
128
158
  * time each `collider ` is overlapped.
129
159
  * @param {Function} [options.on_overlap_stop] Executes a function when a
@@ -138,16 +168,12 @@
138
168
  this.$element = el;
139
169
  this.last_colliders = [];
140
170
  this.last_colliders_coords = [];
141
- if (typeof colliders === 'string' || colliders instanceof jQuery) {
142
- this.$colliders = $(colliders,
143
- this.options.colliders_context).not(this.$element);
144
- }else{
145
- this.colliders = $(colliders);
146
- }
171
+ this.set_colliders(colliders);
147
172
 
148
173
  this.init();
149
174
  }
150
175
 
176
+ Collision.defaults = defaults;
151
177
 
152
178
  var fn = Collision.prototype;
153
179
 
@@ -228,6 +254,7 @@
228
254
 
229
255
  fn.find_collisions = function(player_data_coords){
230
256
  var self = this;
257
+ var overlapping_region = this.options.overlapping_region;
231
258
  var colliders_coords = [];
232
259
  var colliders_data = [];
233
260
  var $colliders = (this.colliders || this.$colliders);
@@ -251,7 +278,8 @@
251
278
  player_coords, collider_coords);
252
279
 
253
280
  //todo: make this an option
254
- if (region === 'C'){
281
+ if (region === overlapping_region || overlapping_region === 'all') {
282
+
255
283
  var area_coords = self.calculate_overlapped_area_coords(
256
284
  player_coords, collider_coords);
257
285
  var area = self.calculate_overlapped_area(area_coords);
@@ -274,7 +302,7 @@
274
302
 
275
303
  if (self.options.on_overlap_stop || self.options.on_overlap_start) {
276
304
  this.manage_colliders_start_stop(colliders_coords,
277
- self.options.on_overlap_stop, self.options.on_overlap_start);
305
+ self.options.on_overlap_start, self.options.on_overlap_stop);
278
306
  }
279
307
 
280
308
  this.last_colliders_coords = colliders_coords;
@@ -285,12 +313,8 @@
285
313
 
286
314
  fn.get_closest_colliders = function(player_data_coords){
287
315
  var colliders = this.find_collisions(player_data_coords);
288
- var min_area = 100;
289
- colliders.sort(function(a, b){
290
- if (a.area <= min_area) {
291
- return 1;
292
- }
293
316
 
317
+ colliders.sort(function(a, b) {
294
318
  /* if colliders are being overlapped by the "C" (center) region,
295
319
  * we have to set a lower index in the array to which they are placed
296
320
  * above in the grid. */
@@ -302,7 +326,7 @@
302
326
  }
303
327
  }
304
328
 
305
- if (a.area < b.area){
329
+ if (a.area < b.area) {
306
330
  return 1;
307
331
  }
308
332
 
@@ -312,16 +336,59 @@
312
336
  };
313
337
 
314
338
 
339
+ fn.set_colliders = function(colliders) {
340
+ if (typeof colliders === 'string' || colliders instanceof $) {
341
+ this.$colliders = $(colliders,
342
+ this.options.colliders_context).not(this.$element);
343
+ }else{
344
+ this.colliders = $(colliders);
345
+ }
346
+ };
347
+
348
+
315
349
  //jQuery adapter
316
350
  $.fn.collision = function(collider, options) {
317
351
  return new Collision( this, collider, options );
318
352
  };
319
353
 
354
+ return Collision;
320
355
 
321
- }(jQuery, window, document));
356
+ }));
322
357
 
323
358
  ;(function(window, undefined) {
324
- /* Debounce and throttle functions taken from underscore.js */
359
+
360
+ /* Delay, debounce and throttle functions taken from underscore.js
361
+ *
362
+ * Copyright (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and
363
+ * Investigative Reporters & Editors
364
+ *
365
+ * Permission is hereby granted, free of charge, to any person
366
+ * obtaining a copy of this software and associated documentation
367
+ * files (the "Software"), to deal in the Software without
368
+ * restriction, including without limitation the rights to use,
369
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
370
+ * copies of the Software, and to permit persons to whom the
371
+ * Software is furnished to do so, subject to the following
372
+ * conditions:
373
+ *
374
+ * The above copyright notice and this permission notice shall be
375
+ * included in all copies or substantial portions of the Software.
376
+ *
377
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
378
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
379
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
380
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
381
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
382
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
383
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
384
+ * OTHER DEALINGS IN THE SOFTWARE.
385
+ */
386
+
387
+ window.delay = function(func, wait) {
388
+ var args = Array.prototype.slice.call(arguments, 2);
389
+ return setTimeout(function(){ return func.apply(null, args); }, wait);
390
+ };
391
+
325
392
  window.debounce = function(func, wait, immediate) {
326
393
  var timeout;
327
394
  return function() {
@@ -336,7 +403,6 @@
336
403
  };
337
404
  };
338
405
 
339
-
340
406
  window.throttle = function(func, wait) {
341
407
  var context, args, timeout, throttling, more, result;
342
408
  var whenDone = debounce(
@@ -362,27 +428,46 @@
362
428
 
363
429
  })(window);
364
430
 
365
- ;(function($, window, document, undefined){
431
+ ;(function(root, factory) {
432
+
433
+ if (typeof define === 'function' && define.amd) {
434
+ define('gridster-draggable', ['jquery'], factory);
435
+ } else {
436
+ root.GridsterDraggable = factory(root.$ || root.jQuery);
437
+ }
438
+
439
+ }(this, function($) {
366
440
 
367
441
  var defaults = {
368
- items: '.gs_w',
442
+ items: 'li',
369
443
  distance: 1,
370
444
  limit: true,
371
445
  offset_left: 0,
372
- autoscroll: true
373
- // ,drag: function(e){},
374
- // start : function(e, ui){},
375
- // stop : function(e){}
446
+ autoscroll: true,
447
+ ignore_dragging: ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON'], // or function
448
+ handle: null,
449
+ container_width: 0, // 0 == auto
450
+ move_element: true,
451
+ helper: false, // or 'clone'
452
+ remove_helper: true
453
+ // drag: function(e) {},
454
+ // start : function(e, ui) {},
455
+ // stop : function(e) {}
376
456
  };
377
457
 
378
458
  var $window = $(window);
459
+ var dir_map = { x : 'left', y : 'top' };
379
460
  var isTouch = !!('ontouchstart' in window);
380
- var pointer_events = {
381
- start: isTouch ? 'touchstart' : 'mousedown.draggable',
382
- move: isTouch ? 'touchmove' : 'mousemove.draggable',
383
- end: isTouch ? 'touchend' : 'mouseup.draggable'
461
+
462
+ var capitalize = function(str) {
463
+ return str.charAt(0).toUpperCase() + str.slice(1);
384
464
  };
385
465
 
466
+ var idCounter = 0;
467
+ var uniqId = function() {
468
+ return ++idCounter + '';
469
+ }
470
+
386
471
  /**
387
472
  * Basic drag implementation for DOM elements inside a container.
388
473
  * Provide start/stop/drag callbacks.
@@ -399,6 +484,9 @@
399
484
  * the mouse must move before dragging should start.
400
485
  * @param {Boolean} [options.limit] Constrains dragging to the width of
401
486
  * the container
487
+ * @param {Object|Function} [options.ignore_dragging] Array of node names
488
+ * that sould not trigger dragging, by default is `['INPUT', 'TEXTAREA',
489
+ * 'SELECT', 'BUTTON']`. If a function is used return true to ignore dragging.
402
490
  * @param {offset_left} [options.offset_left] Offset added to the item
403
491
  * that is being dragged.
404
492
  * @param {Number} [options.drag] Executes a callback when the mouse is
@@ -411,25 +499,57 @@
411
499
  */
412
500
  function Draggable(el, options) {
413
501
  this.options = $.extend({}, defaults, options);
414
- this.$body = $(document.body);
502
+ this.$document = $(document);
415
503
  this.$container = $(el);
416
504
  this.$dragitems = $(this.options.items, this.$container);
417
505
  this.is_dragging = false;
418
506
  this.player_min_left = 0 + this.options.offset_left;
507
+ this.id = uniqId();
508
+ this.ns = '.gridster-draggable-' + this.id;
419
509
  this.init();
420
510
  }
421
511
 
512
+ Draggable.defaults = defaults;
513
+
422
514
  var fn = Draggable.prototype;
423
515
 
424
516
  fn.init = function() {
425
- this.calculate_positions();
426
- this.$container.css('position', 'relative');
427
- this.enable();
517
+ var pos = this.$container.css('position');
518
+ this.calculate_dimensions();
519
+ this.$container.css('position', pos === 'static' ? 'relative' : pos);
520
+ this.disabled = false;
521
+ this.events();
522
+
523
+ $(window).bind(this.nsEvent('resize'),
524
+ throttle($.proxy(this.calculate_dimensions, this), 200));
525
+ };
428
526
 
429
- $(window).bind('resize',
430
- throttle($.proxy(this.calculate_positions, this), 200));
527
+ fn.nsEvent = function(ev) {
528
+ return (ev || '') + this.ns;
431
529
  };
432
530
 
531
+ fn.events = function() {
532
+ this.pointer_events = {
533
+ start: this.nsEvent('touchstart') + ' ' + this.nsEvent('mousedown'),
534
+ move: this.nsEvent('touchmove') + ' ' + this.nsEvent('mousemove'),
535
+ end: this.nsEvent('touchend') + ' ' + this.nsEvent('mouseup'),
536
+ };
537
+
538
+ this.$container.on(this.nsEvent('selectstart'),
539
+ $.proxy(this.on_select_start, this));
540
+
541
+ this.$container.on(this.pointer_events.start, this.options.items,
542
+ $.proxy(this.drag_handler, this));
543
+
544
+ this.$document.on(this.pointer_events.end, $.proxy(function(e) {
545
+ this.is_dragging = false;
546
+ if (this.disabled) { return; }
547
+ this.$document.off(this.pointer_events.move);
548
+ if (this.drag_start) {
549
+ this.on_dragstop(e);
550
+ }
551
+ }, this));
552
+ };
433
553
 
434
554
  fn.get_actual_pos = function($el) {
435
555
  var pos = $el.position();
@@ -438,10 +558,10 @@
438
558
 
439
559
 
440
560
  fn.get_mouse_pos = function(e) {
441
- if (isTouch) {
561
+ if (e.originalEvent && e.originalEvent.touches) {
442
562
  var oe = e.originalEvent;
443
563
  e = oe.touches.length ? oe.touches[0] : oe.changedTouches[0];
444
- };
564
+ }
445
565
 
446
566
  return {
447
567
  left: e.clientX,
@@ -457,77 +577,119 @@
457
577
  mouse_actual_pos.left - this.mouse_init_pos.left);
458
578
  var diff_y = Math.round(mouse_actual_pos.top - this.mouse_init_pos.top);
459
579
 
460
- var left = Math.round(this.el_init_offset.left + diff_x - this.baseX);
461
- var top = Math.round(
462
- this.el_init_offset.top + diff_y - this.baseY + this.scrollOffset);
580
+ var left = Math.round(this.el_init_offset.left +
581
+ diff_x - this.baseX + $(window).scrollLeft() - this.win_offset_x);
582
+ var top = Math.round(this.el_init_offset.top +
583
+ diff_y - this.baseY + $(window).scrollTop() - this.win_offset_y);
463
584
 
464
585
  if (this.options.limit) {
465
586
  if (left > this.player_max_left) {
466
587
  left = this.player_max_left;
467
- }else if(left < this.player_min_left) {
588
+ } else if(left < this.player_min_left) {
468
589
  left = this.player_min_left;
469
590
  }
470
591
  }
471
592
 
472
593
  return {
473
- left: left,
474
- top: top,
475
- mouse_left: mouse_actual_pos.left,
476
- mouse_top: mouse_actual_pos.top
594
+ position: {
595
+ left: left,
596
+ top: top
597
+ },
598
+ pointer: {
599
+ left: mouse_actual_pos.left,
600
+ top: mouse_actual_pos.top,
601
+ diff_left: diff_x + ($(window).scrollLeft() - this.win_offset_x),
602
+ diff_top: diff_y + ($(window).scrollTop() - this.win_offset_y)
603
+ }
477
604
  };
478
605
  };
479
606
 
480
607
 
481
- fn.manage_scroll = function(offset) {
482
- /* scroll document */
483
- var nextScrollTop;
484
- var scrollTop = $window.scrollTop();
485
- var min_window_y = scrollTop;
486
- var max_window_y = min_window_y + this.window_height;
608
+ fn.get_drag_data = function(e) {
609
+ var offset = this.get_offset(e);
610
+ offset.$player = this.$player;
611
+ offset.$helper = this.helper ? this.$helper : this.$player;
612
+
613
+ return offset;
614
+ };
615
+
616
+
617
+ fn.set_limits = function(container_width) {
618
+ container_width || (container_width = this.$container.width());
619
+ this.player_max_left = (container_width - this.player_width +
620
+ - this.options.offset_left);
621
+
622
+ this.options.container_width = container_width;
623
+
624
+ return this;
625
+ };
626
+
627
+
628
+ fn.scroll_in = function(axis, data) {
629
+ var dir_prop = dir_map[axis];
630
+
631
+ var area_size = 50;
632
+ var scroll_inc = 30;
633
+
634
+ var is_x = axis === 'x';
635
+ var window_size = is_x ? this.window_width : this.window_height;
636
+ var doc_size = is_x ? $(document).width() : $(document).height();
637
+ var player_size = is_x ? this.$player.width() : this.$player.height();
487
638
 
488
- var mouse_down_zone = max_window_y - 50;
489
- var mouse_up_zone = min_window_y + 50;
639
+ var next_scroll;
640
+ var scroll_offset = $window['scroll' + capitalize(dir_prop)]();
641
+ var min_window_pos = scroll_offset;
642
+ var max_window_pos = min_window_pos + window_size;
490
643
 
491
- var abs_mouse_left = offset.mouse_left;
492
- var abs_mouse_top = min_window_y + offset.mouse_top;
644
+ var mouse_next_zone = max_window_pos - area_size; // down/right
645
+ var mouse_prev_zone = min_window_pos + area_size; // up/left
493
646
 
494
- var max_player_y = (this.doc_height - this.window_height +
495
- this.player_height);
647
+ var abs_mouse_pos = min_window_pos + data.pointer[dir_prop];
496
648
 
497
- if (abs_mouse_top >= mouse_down_zone) {
498
- nextScrollTop = scrollTop + 30;
499
- if (nextScrollTop < max_player_y) {
500
- $window.scrollTop(nextScrollTop);
501
- this.scrollOffset = this.scrollOffset + 30;
649
+ var max_player_pos = (doc_size - window_size + player_size);
650
+
651
+ if (abs_mouse_pos >= mouse_next_zone) {
652
+ next_scroll = scroll_offset + scroll_inc;
653
+ if (next_scroll < max_player_pos) {
654
+ $window['scroll' + capitalize(dir_prop)](next_scroll);
655
+ this['scroll_offset_' + axis] += scroll_inc;
502
656
  }
503
- };
657
+ }
504
658
 
505
- if (abs_mouse_top <= mouse_up_zone) {
506
- nextScrollTop = scrollTop - 30;
507
- if (nextScrollTop > 0) {
508
- $window.scrollTop(nextScrollTop);
509
- this.scrollOffset = this.scrollOffset - 30;
659
+ if (abs_mouse_pos <= mouse_prev_zone) {
660
+ next_scroll = scroll_offset - scroll_inc;
661
+ if (next_scroll > 0) {
662
+ $window['scroll' + capitalize(dir_prop)](next_scroll);
663
+ this['scroll_offset_' + axis] -= scroll_inc;
510
664
  }
511
- };
512
- }
665
+ }
666
+
667
+ return this;
668
+ };
669
+
670
+
671
+ fn.manage_scroll = function(data) {
672
+ this.scroll_in('x', data);
673
+ this.scroll_in('y', data);
674
+ };
513
675
 
514
676
 
515
- fn.calculate_positions = function(e) {
677
+ fn.calculate_dimensions = function(e) {
516
678
  this.window_height = $window.height();
517
- }
679
+ this.window_width = $window.width();
680
+ };
518
681
 
519
682
 
520
683
  fn.drag_handler = function(e) {
521
684
  var node = e.target.nodeName;
522
-
523
- if (e.which !== 1 && !isTouch) {
685
+ // skip if drag is disabled, or click was not done with the mouse primary button
686
+ if (this.disabled || e.which !== 1 && !isTouch) {
524
687
  return;
525
688
  }
526
689
 
527
- if (node === 'INPUT' || node === 'TEXTAREA' || node === 'SELECT' ||
528
- node === 'BUTTON') {
690
+ if (this.ignore_drag(e)) {
529
691
  return;
530
- };
692
+ }
531
693
 
532
694
  var self = this;
533
695
  var first = true;
@@ -537,7 +699,7 @@
537
699
  this.mouse_init_pos = this.get_mouse_pos(e);
538
700
  this.offsetY = this.mouse_init_pos.top - this.el_init_pos.top;
539
701
 
540
- this.$body.on(pointer_events.move, function(mme){
702
+ this.$document.on(this.pointer_events.move, function(mme) {
541
703
  var mouse_actual_pos = self.get_mouse_pos(mme);
542
704
  var diff_x = Math.abs(
543
705
  mouse_actual_pos.left - self.mouse_init_pos.left);
@@ -545,7 +707,7 @@
545
707
  mouse_actual_pos.top - self.mouse_init_pos.top);
546
708
  if (!(diff_x > self.options.distance ||
547
709
  diff_y > self.options.distance)
548
- ) {
710
+ ) {
549
711
  return false;
550
712
  }
551
713
 
@@ -555,90 +717,87 @@
555
717
  return false;
556
718
  }
557
719
 
558
- if (self.is_dragging == true) {
720
+ if (self.is_dragging === true) {
559
721
  self.on_dragmove.call(self, mme);
560
722
  }
561
723
 
562
724
  return false;
563
725
  });
564
726
 
565
- return false;
727
+ if (!isTouch) { return false; }
566
728
  };
567
729
 
568
730
 
569
731
  fn.on_dragstart = function(e) {
570
732
  e.preventDefault();
571
- this.drag_start = true;
572
- this.is_dragging = true;
733
+
734
+ if (this.is_dragging) { return this; }
735
+
736
+ this.drag_start = this.is_dragging = true;
573
737
  var offset = this.$container.offset();
574
738
  this.baseX = Math.round(offset.left);
575
739
  this.baseY = Math.round(offset.top);
576
- this.doc_height = $(document).height();
740
+ this.initial_container_width = this.options.container_width || this.$container.width();
577
741
 
578
742
  if (this.options.helper === 'clone') {
579
743
  this.$helper = this.$player.clone()
580
744
  .appendTo(this.$container).addClass('helper');
581
745
  this.helper = true;
582
- }else{
746
+ } else {
583
747
  this.helper = false;
584
748
  }
585
- this.scrollOffset = 0;
749
+
750
+ this.win_offset_y = $(window).scrollTop();
751
+ this.win_offset_x = $(window).scrollLeft();
752
+ this.scroll_offset_y = 0;
753
+ this.scroll_offset_x = 0;
586
754
  this.el_init_offset = this.$player.offset();
587
755
  this.player_width = this.$player.width();
588
756
  this.player_height = this.$player.height();
589
- this.player_max_left = (this.$container.width() - this.player_width +
590
- this.options.offset_left);
757
+
758
+ this.set_limits(this.options.container_width);
591
759
 
592
760
  if (this.options.start) {
593
- this.options.start.call(this.$player, e, {
594
- helper: this.helper ? this.$helper : this.$player
595
- });
761
+ this.options.start.call(this.$player, e, this.get_drag_data(e));
596
762
  }
597
763
  return false;
598
764
  };
599
765
 
600
766
 
601
767
  fn.on_dragmove = function(e) {
602
- var offset = this.get_offset(e);
768
+ var data = this.get_drag_data(e);
603
769
 
604
- this.options.autoscroll && this.manage_scroll(offset);
770
+ this.options.autoscroll && this.manage_scroll(data);
605
771
 
606
- (this.helper ? this.$helper : this.$player).css({
607
- 'position': 'absolute',
608
- 'left' : offset.left,
609
- 'top' : offset.top
610
- });
772
+ if (this.options.move_element) {
773
+ (this.helper ? this.$helper : this.$player).css({
774
+ 'position': 'absolute',
775
+ 'left' : data.position.left,
776
+ 'top' : data.position.top
777
+ });
778
+ }
611
779
 
612
- var ui = {
613
- 'position': {
614
- 'left': offset.left,
615
- 'top': offset.top
616
- }
617
- };
780
+ var last_position = this.last_position || data.position;
781
+ data.prev_position = last_position;
618
782
 
619
783
  if (this.options.drag) {
620
- this.options.drag.call(this.$player, e, ui);
784
+ this.options.drag.call(this.$player, e, data);
621
785
  }
786
+
787
+ this.last_position = data.position;
622
788
  return false;
623
789
  };
624
790
 
625
791
 
626
792
  fn.on_dragstop = function(e) {
627
- var offset = this.get_offset(e);
793
+ var data = this.get_drag_data(e);
628
794
  this.drag_start = false;
629
795
 
630
- var ui = {
631
- 'position': {
632
- 'left': offset.left,
633
- 'top': offset.top
634
- }
635
- };
636
-
637
796
  if (this.options.stop) {
638
- this.options.stop.call(this.$player, e, ui);
797
+ this.options.stop.call(this.$player, e, data);
639
798
  }
640
799
 
641
- if (this.helper) {
800
+ if (this.helper && this.options.remove_helper) {
642
801
  this.$helper.remove();
643
802
  }
644
803
 
@@ -646,78 +805,104 @@
646
805
  };
647
806
 
648
807
  fn.on_select_start = function(e) {
649
- return false;
650
- }
651
-
808
+ if (this.disabled) { return; }
652
809
 
653
- fn.enable = function(){
654
- this.$container.on('selectstart', this.on_select_start);
655
-
656
- this.$container.on(pointer_events.start, this.options.items, $.proxy(
657
- this.drag_handler, this));
810
+ if (this.ignore_drag(e)) {
811
+ return;
812
+ }
658
813
 
659
- this.$body.on(pointer_events.end, $.proxy(function(e) {
660
- this.is_dragging = false;
661
- this.$body.off(pointer_events.move);
662
- if (this.drag_start) {
663
- this.on_dragstop(e);
664
- }
665
- }, this));
814
+ return false;
666
815
  };
667
816
 
668
-
669
- fn.disable = function(){
670
- this.$container.off(pointer_events.start);
671
- this.$body.off(pointer_events.end);
672
- this.$container.off('selectstart', this.on_select_start);
817
+ fn.enable = function() {
818
+ this.disabled = false;
673
819
  };
674
820
 
821
+ fn.disable = function() {
822
+ this.disabled = true;
823
+ };
675
824
 
676
- fn.destroy = function(){
825
+ fn.destroy = function() {
677
826
  this.disable();
827
+
828
+ this.$container.off(this.ns);
829
+ this.$document.off(this.ns);
830
+ $(window).off(this.ns);
831
+
678
832
  $.removeData(this.$container, 'drag');
679
833
  };
680
834
 
835
+ fn.ignore_drag = function(event) {
836
+ if (this.options.handle) {
837
+ return !$(event.target).is(this.options.handle);
838
+ }
839
+
840
+ if ($.isFunction(this.options.ignore_dragging)) {
841
+ return this.options.ignore_dragging(event);
842
+ }
843
+
844
+ return $(event.target).is(this.options.ignore_dragging.join(', '));
845
+ };
681
846
 
682
847
  //jQuery adapter
683
848
  $.fn.drag = function ( options ) {
684
- return this.each(function () {
685
- if (!$.data(this, 'drag')) {
686
- $.data(this, 'drag', new Draggable( this, options ));
687
- }
688
- });
849
+ return new Draggable(this, options);
689
850
  };
690
851
 
852
+ return Draggable;
853
+
854
+ }));
855
+
856
+ ;(function(root, factory) {
691
857
 
692
- }(jQuery, window, document));
858
+ if (typeof define === 'function' && define.amd) {
859
+ define(['jquery', 'gridster-draggable', 'gridster-collision'], factory);
860
+ } else {
861
+ root.Gridster = factory(root.$ || root.jQuery, root.GridsterDraggable,
862
+ root.GridsterCollision);
863
+ }
693
864
 
694
- ;(function($, window, document, undefined) {
865
+ }(this, function($, Draggable, Collision) {
695
866
 
696
867
  var defaults = {
697
- widget_selector: '> li',
868
+ namespace: '',
869
+ widget_selector: 'li',
698
870
  widget_margins: [10, 10],
699
871
  widget_base_dimensions: [400, 225],
700
872
  extra_rows: 0,
701
873
  extra_cols: 0,
702
874
  min_cols: 1,
875
+ max_cols: Infinity,
703
876
  min_rows: 15,
704
- max_size_x: 6,
705
- max_size_y: 6,
877
+ max_size_x: false,
878
+ autogrow_cols: false,
706
879
  autogenerate_stylesheet: true,
707
880
  avoid_overlapped_widgets: true,
881
+ auto_init: true,
708
882
  serialize_params: function($w, wgd) {
709
883
  return {
710
884
  col: wgd.col,
711
- row: wgd.row
885
+ row: wgd.row,
886
+ size_x: wgd.size_x,
887
+ size_y: wgd.size_y
712
888
  };
713
889
  },
714
890
  collision: {},
715
891
  draggable: {
716
- distance: 4
892
+ items: '.gs-w',
893
+ distance: 4,
894
+ ignore_dragging: Draggable.defaults.ignore_dragging.slice(0)
895
+ },
896
+ resize: {
897
+ enabled: false,
898
+ axes: ['both'],
899
+ handle_append_to: '',
900
+ handle_class: 'gs-resize-handle',
901
+ max_size: [Infinity, Infinity],
902
+ min_size: [1, 1]
717
903
  }
718
904
  };
719
905
 
720
-
721
906
  /**
722
907
  * @class Gridster
723
908
  * @uses Draggable
@@ -739,11 +924,11 @@
739
924
  * @param {Number} [options.extra_rows] Add more rows in addition to
740
925
  * those that have been calculated.
741
926
  * @param {Number} [options.min_cols] The minimum required columns.
927
+ * @param {Number} [options.max_cols] The maximum columns possible (set to null
928
+ * for no maximum).
742
929
  * @param {Number} [options.min_rows] The minimum required rows.
743
930
  * @param {Number} [options.max_size_x] The maximum number of columns
744
931
  * that a widget can span.
745
- * @param {Number} [options.max_size_y] The maximum number of rows
746
- * that a widget can span.
747
932
  * @param {Boolean} [options.autogenerate_stylesheet] If true, all the
748
933
  * CSS required to position all widgets in their respective columns
749
934
  * and rows will be generated automatically and injected to the
@@ -753,6 +938,8 @@
753
938
  * @param {Boolean} [options.avoid_overlapped_widgets] Avoid that widgets loaded
754
939
  * from the DOM can be overlapped. It is helpful if the positions were
755
940
  * bad stored in the database or if there was any conflict.
941
+ * @param {Boolean} [options.auto_init] Automatically call gridster init
942
+ * method or not when the plugin is instantiated.
756
943
  * @param {Function} [options.serialize_params] Return the data you want
757
944
  * for each widget in the serialization. Two arguments are passed:
758
945
  * `$w`: the jQuery wrapped HTMLElement, and `wgd`: the grid
@@ -763,37 +950,158 @@
763
950
  * @param {Object} [options.draggable] An Object with all options for
764
951
  * Draggable class you want to overwrite. See Draggable docs for more
765
952
  * info.
953
+ * @param {Object|Function} [options.draggable.ignore_dragging] Note that
954
+ * if you use a Function, and resize is enabled, you should ignore the
955
+ * resize handlers manually (options.resize.handle_class).
956
+ * @param {Object} [options.resize] An Object with resize config options.
957
+ * @param {Boolean} [options.resize.enabled] Set to true to enable
958
+ * resizing.
959
+ * @param {Array} [options.resize.axes] Axes in which widgets can be
960
+ * resized. Possible values: ['x', 'y', 'both'].
961
+ * @param {String} [options.resize.handle_append_to] Set a valid CSS
962
+ * selector to append resize handles to.
963
+ * @param {String} [options.resize.handle_class] CSS class name used
964
+ * by resize handles.
965
+ * @param {Array} [options.resize.max_size] Limit widget dimensions
966
+ * when resizing. Array values should be integers:
967
+ * `[max_cols_occupied, max_rows_occupied]`
968
+ * @param {Array} [options.resize.min_size] Limit widget dimensions
969
+ * when resizing. Array values should be integers:
970
+ * `[min_cols_occupied, min_rows_occupied]`
971
+ * @param {Function} [options.resize.start] Function executed
972
+ * when resizing starts.
973
+ * @param {Function} [otions.resize.resize] Function executed
974
+ * during the resizing.
975
+ * @param {Function} [options.resize.stop] Function executed
976
+ * when resizing stops.
766
977
  *
767
978
  * @constructor
768
979
  */
769
980
  function Gridster(el, options) {
770
- this.options = $.extend(true, defaults, options);
771
- this.$el = $(el);
772
- this.$wrapper = this.$el.parent();
773
- this.$widgets = $(this.options.widget_selector, this.$el).addClass('gs_w');
774
- this.widgets = [];
775
- this.$changed = $([]);
776
- this.wrapper_width = this.$wrapper.width();
777
- this.min_widget_width = (this.options.widget_margins[0] * 2) +
778
- this.options.widget_base_dimensions[0];
779
- this.min_widget_height = (this.options.widget_margins[1] * 2) +
780
- this.options.widget_base_dimensions[1];
781
- this.init();
981
+ this.options = $.extend(true, {}, defaults, options);
982
+ this.$el = $(el);
983
+ this.$wrapper = this.$el.parent();
984
+ this.$widgets = this.$el.children(
985
+ this.options.widget_selector).addClass('gs-w');
986
+ this.widgets = [];
987
+ this.$changed = $([]);
988
+ this.wrapper_width = this.$wrapper.width();
989
+ this.min_widget_width = (this.options.widget_margins[0] * 2) +
990
+ this.options.widget_base_dimensions[0];
991
+ this.min_widget_height = (this.options.widget_margins[1] * 2) +
992
+ this.options.widget_base_dimensions[1];
993
+
994
+ this.generated_stylesheets = [];
995
+ this.$style_tags = $([]);
996
+
997
+ this.options.auto_init && this.init();
782
998
  }
783
999
 
1000
+ Gridster.defaults = defaults;
784
1001
  Gridster.generated_stylesheets = [];
785
1002
 
1003
+
1004
+ /**
1005
+ * Sorts an Array of grid coords objects (representing the grid coords of
1006
+ * each widget) in ascending way.
1007
+ *
1008
+ * @method sort_by_row_asc
1009
+ * @param {Array} widgets Array of grid coords objects
1010
+ * @return {Array} Returns the array sorted.
1011
+ */
1012
+ Gridster.sort_by_row_asc = function(widgets) {
1013
+ widgets = widgets.sort(function(a, b) {
1014
+ if (!a.row) {
1015
+ a = $(a).coords().grid;
1016
+ b = $(b).coords().grid;
1017
+ }
1018
+
1019
+ if (a.row > b.row) {
1020
+ return 1;
1021
+ }
1022
+ return -1;
1023
+ });
1024
+
1025
+ return widgets;
1026
+ };
1027
+
1028
+
1029
+ /**
1030
+ * Sorts an Array of grid coords objects (representing the grid coords of
1031
+ * each widget) placing first the empty cells upper left.
1032
+ *
1033
+ * @method sort_by_row_and_col_asc
1034
+ * @param {Array} widgets Array of grid coords objects
1035
+ * @return {Array} Returns the array sorted.
1036
+ */
1037
+ Gridster.sort_by_row_and_col_asc = function(widgets) {
1038
+ widgets = widgets.sort(function(a, b) {
1039
+ if (a.row > b.row || a.row === b.row && a.col > b.col) {
1040
+ return 1;
1041
+ }
1042
+ return -1;
1043
+ });
1044
+
1045
+ return widgets;
1046
+ };
1047
+
1048
+
1049
+ /**
1050
+ * Sorts an Array of grid coords objects by column (representing the grid
1051
+ * coords of each widget) in ascending way.
1052
+ *
1053
+ * @method sort_by_col_asc
1054
+ * @param {Array} widgets Array of grid coords objects
1055
+ * @return {Array} Returns the array sorted.
1056
+ */
1057
+ Gridster.sort_by_col_asc = function(widgets) {
1058
+ widgets = widgets.sort(function(a, b) {
1059
+ if (a.col > b.col) {
1060
+ return 1;
1061
+ }
1062
+ return -1;
1063
+ });
1064
+
1065
+ return widgets;
1066
+ };
1067
+
1068
+
1069
+ /**
1070
+ * Sorts an Array of grid coords objects (representing the grid coords of
1071
+ * each widget) in descending way.
1072
+ *
1073
+ * @method sort_by_row_desc
1074
+ * @param {Array} widgets Array of grid coords objects
1075
+ * @return {Array} Returns the array sorted.
1076
+ */
1077
+ Gridster.sort_by_row_desc = function(widgets) {
1078
+ widgets = widgets.sort(function(a, b) {
1079
+ if (a.row + a.size_y < b.row + b.size_y) {
1080
+ return 1;
1081
+ }
1082
+ return -1;
1083
+ });
1084
+ return widgets;
1085
+ };
1086
+
1087
+
1088
+
1089
+ /** Instance Methods **/
1090
+
786
1091
  var fn = Gridster.prototype;
787
1092
 
788
1093
  fn.init = function() {
789
- this.generate_grid_and_stylesheet();
790
- this.get_widgets_from_DOM();
791
- this.set_dom_grid_height();
792
- this.$wrapper.addClass('ready');
793
- this.draggable();
1094
+ this.options.resize.enabled && this.setup_resize();
1095
+ this.generate_grid_and_stylesheet();
1096
+ this.get_widgets_from_DOM();
1097
+ this.set_dom_grid_height();
1098
+ this.set_dom_grid_width();
1099
+ this.$wrapper.addClass('ready');
1100
+ this.draggable();
1101
+ this.options.resize.enabled && this.resizable();
794
1102
 
795
- $(window).bind(
796
- 'resize', throttle($.proxy(this.recalculate_faux_grid, this), 200));
1103
+ $(window).bind('resize.gridster', throttle(
1104
+ $.proxy(this.recalculate_faux_grid, this), 200));
797
1105
  };
798
1106
 
799
1107
 
@@ -822,54 +1130,410 @@
822
1130
  };
823
1131
 
824
1132
 
1133
+
1134
+ /**
1135
+ * Disables drag-and-drop widget resizing.
1136
+ *
1137
+ * @method disable
1138
+ * @return {Class} Returns instance of gridster Class.
1139
+ */
1140
+ fn.disable_resize = function() {
1141
+ this.$el.addClass('gs-resize-disabled');
1142
+ this.resize_api.disable();
1143
+ return this;
1144
+ };
1145
+
1146
+
1147
+ /**
1148
+ * Enables drag-and-drop widget resizing.
1149
+ *
1150
+ * @method enable
1151
+ * @return {Class} Returns instance of gridster Class.
1152
+ */
1153
+ fn.enable_resize = function() {
1154
+ this.$el.removeClass('gs-resize-disabled');
1155
+ this.resize_api.enable();
1156
+ return this;
1157
+ };
1158
+
1159
+
825
1160
  /**
826
1161
  * Add a new widget to the grid.
827
1162
  *
828
1163
  * @method add_widget
829
- * @param {String} html The string representing the HTML of the widget.
830
- * @param {Number} size_x The nº of rows the widget occupies horizontally.
831
- * @param {Number} size_y The nº of columns the widget occupies vertically.
1164
+ * @param {String|HTMLElement} html The string representing the HTML of the widget
1165
+ * or the HTMLElement.
1166
+ * @param {Number} [size_x] The nº of rows the widget occupies horizontally.
1167
+ * @param {Number} [size_y] The nº of columns the widget occupies vertically.
1168
+ * @param {Number} [col] The column the widget should start in.
1169
+ * @param {Number} [row] The row the widget should start in.
1170
+ * @param {Array} [max_size] max_size Maximun size (in units) for width and height.
1171
+ * @param {Array} [min_size] min_size Minimum size (in units) for width and height.
832
1172
  * @return {HTMLElement} Returns the jQuery wrapped HTMLElement representing.
833
1173
  * the widget that was just created.
834
1174
  */
835
- fn.add_widget = function(html, size_x, size_y) {
836
- var next_pos = this.next_position(size_x, size_y);
1175
+ fn.add_widget = function(html, size_x, size_y, col, row, max_size, min_size) {
1176
+ var pos;
1177
+ size_x || (size_x = 1);
1178
+ size_y || (size_y = 1);
1179
+
1180
+ if (!col & !row) {
1181
+ pos = this.next_position(size_x, size_y);
1182
+ } else {
1183
+ pos = {
1184
+ col: col,
1185
+ row: row,
1186
+ size_x: size_x,
1187
+ size_y: size_y
1188
+ };
1189
+
1190
+ this.empty_cells(col, row, size_x, size_y);
1191
+ }
837
1192
 
838
1193
  var $w = $(html).attr({
839
- 'data-col': next_pos.col,
840
- 'data-row': next_pos.row,
841
- 'data-sizex' : next_pos.size_x,
842
- 'data-sizey' : next_pos.size_y
843
- }).addClass('gs_w').appendTo(this.$el).hide();
1194
+ 'data-col': pos.col,
1195
+ 'data-row': pos.row,
1196
+ 'data-sizex' : size_x,
1197
+ 'data-sizey' : size_y
1198
+ }).addClass('gs-w').appendTo(this.$el).hide();
844
1199
 
845
1200
  this.$widgets = this.$widgets.add($w);
846
1201
 
847
1202
  this.register_widget($w);
848
1203
 
1204
+ this.add_faux_rows(pos.size_y);
1205
+ //this.add_faux_cols(pos.size_x);
1206
+
1207
+ if (max_size) {
1208
+ this.set_widget_max_size($w, max_size);
1209
+ }
1210
+
1211
+ if (min_size) {
1212
+ this.set_widget_min_size($w, min_size);
1213
+ }
1214
+
1215
+ this.set_dom_grid_width();
849
1216
  this.set_dom_grid_height();
850
1217
 
1218
+ this.drag_api.set_limits(this.cols * this.min_widget_width);
1219
+
851
1220
  return $w.fadeIn();
852
1221
  };
853
1222
 
854
1223
 
855
1224
  /**
856
- * Get the most left column below to add a new widget.
1225
+ * Change widget size limits.
857
1226
  *
858
- * @method next_position
859
- * @param {Number} size_x The of rows the widget occupies horizontally.
860
- * @param {Number} size_y The of columns the widget occupies vertically.
861
- * @return {Object} Returns a grid coords object representing the future
862
- * widget coords.
1227
+ * @method set_widget_min_size
1228
+ * @param {HTMLElement|Number} $widget The jQuery wrapped HTMLElement
1229
+ * representing the widget or an index representing the desired widget.
1230
+ * @param {Array} min_size Minimum size (in units) for width and height.
1231
+ * @return {HTMLElement} Returns instance of gridster Class.
863
1232
  */
864
- fn.next_position = function(size_x, size_y) {
865
- size_x || (size_x = 1);
866
- size_y || (size_y = 1);
867
- var ga = this.gridmap;
868
- var cols_l = ga.length;
869
- var valid_pos = [];
1233
+ fn.set_widget_min_size = function($widget, min_size) {
1234
+ $widget = typeof $widget === 'number' ?
1235
+ this.$widgets.eq($widget) : $widget;
1236
+
1237
+ if (!$widget.length) { return this; }
1238
+
1239
+ var wgd = $widget.data('coords').grid;
1240
+ wgd.min_size_x = min_size[0];
1241
+ wgd.min_size_y = min_size[1];
1242
+
1243
+ return this;
1244
+ };
1245
+
1246
+
1247
+ /**
1248
+ * Change widget size limits.
1249
+ *
1250
+ * @method set_widget_max_size
1251
+ * @param {HTMLElement|Number} $widget The jQuery wrapped HTMLElement
1252
+ * representing the widget or an index representing the desired widget.
1253
+ * @param {Array} max_size Maximun size (in units) for width and height.
1254
+ * @return {HTMLElement} Returns instance of gridster Class.
1255
+ */
1256
+ fn.set_widget_max_size = function($widget, max_size) {
1257
+ $widget = typeof $widget === 'number' ?
1258
+ this.$widgets.eq($widget) : $widget;
1259
+
1260
+ if (!$widget.length) { return this; }
1261
+
1262
+ var wgd = $widget.data('coords').grid;
1263
+ wgd.max_size_x = max_size[0];
1264
+ wgd.max_size_y = max_size[1];
1265
+
1266
+ return this;
1267
+ };
1268
+
1269
+
1270
+ /**
1271
+ * Append the resize handle into a widget.
1272
+ *
1273
+ * @method add_resize_handle
1274
+ * @param {HTMLElement} $widget The jQuery wrapped HTMLElement
1275
+ * representing the widget.
1276
+ * @return {HTMLElement} Returns instance of gridster Class.
1277
+ */
1278
+ fn.add_resize_handle = function($w) {
1279
+ var append_to = this.options.resize.handle_append_to;
1280
+ $(this.resize_handle_tpl).appendTo( append_to ? $(append_to, $w) : $w);
1281
+
1282
+ return this;
1283
+ };
1284
+
1285
+
1286
+ /**
1287
+ * Change the size of a widget. Width is limited to the current grid width.
1288
+ *
1289
+ * @method resize_widget
1290
+ * @param {HTMLElement} $widget The jQuery wrapped HTMLElement
1291
+ * representing the widget.
1292
+ * @param {Number} size_x The number of columns that will occupy the widget.
1293
+ * By default <code>size_x</code> is limited to the space available from
1294
+ * the column where the widget begins, until the last column to the right.
1295
+ * @param {Number} size_y The number of rows that will occupy the widget.
1296
+ * @param {Function} [callback] Function executed when the widget is removed.
1297
+ * @return {HTMLElement} Returns $widget.
1298
+ */
1299
+ fn.resize_widget = function($widget, size_x, size_y, callback) {
1300
+ var wgd = $widget.coords().grid;
1301
+ var col = wgd.col;
1302
+ var max_cols = this.options.max_cols;
1303
+ var old_size_y = wgd.size_y;
1304
+ var old_col = wgd.col;
1305
+ var new_col = old_col;
1306
+
1307
+ size_x || (size_x = wgd.size_x);
1308
+ size_y || (size_y = wgd.size_y);
1309
+
1310
+ if (max_cols !== Infinity) {
1311
+ size_x = Math.min(size_x, max_cols - col + 1);
1312
+ }
1313
+
1314
+ if (size_y > old_size_y) {
1315
+ this.add_faux_rows(Math.max(size_y - old_size_y, 0));
1316
+ }
1317
+
1318
+ var player_rcol = (col + size_x - 1);
1319
+ if (player_rcol > this.cols) {
1320
+ this.add_faux_cols(player_rcol - this.cols);
1321
+ }
1322
+
1323
+ var new_grid_data = {
1324
+ col: new_col,
1325
+ row: wgd.row,
1326
+ size_x: size_x,
1327
+ size_y: size_y
1328
+ };
1329
+
1330
+ this.mutate_widget_in_gridmap($widget, wgd, new_grid_data);
1331
+
1332
+ this.set_dom_grid_height();
1333
+ this.set_dom_grid_width();
1334
+
1335
+ if (callback) {
1336
+ callback.call(this, new_grid_data.size_x, new_grid_data.size_y);
1337
+ }
1338
+
1339
+ return $widget;
1340
+ };
1341
+
1342
+
1343
+ /**
1344
+ * Mutate widget dimensions and position in the grid map.
1345
+ *
1346
+ * @method mutate_widget_in_gridmap
1347
+ * @param {HTMLElement} $widget The jQuery wrapped HTMLElement
1348
+ * representing the widget to mutate.
1349
+ * @param {Object} wgd Current widget grid data (col, row, size_x, size_y).
1350
+ * @param {Object} new_wgd New widget grid data.
1351
+ * @return {HTMLElement} Returns instance of gridster Class.
1352
+ */
1353
+ fn.mutate_widget_in_gridmap = function($widget, wgd, new_wgd) {
1354
+ var old_size_x = wgd.size_x;
1355
+ var old_size_y = wgd.size_y;
1356
+
1357
+ var old_cells_occupied = this.get_cells_occupied(wgd);
1358
+ var new_cells_occupied = this.get_cells_occupied(new_wgd);
1359
+
1360
+ var empty_cols = [];
1361
+ $.each(old_cells_occupied.cols, function(i, col) {
1362
+ if ($.inArray(col, new_cells_occupied.cols) === -1) {
1363
+ empty_cols.push(col);
1364
+ }
1365
+ });
1366
+
1367
+ var occupied_cols = [];
1368
+ $.each(new_cells_occupied.cols, function(i, col) {
1369
+ if ($.inArray(col, old_cells_occupied.cols) === -1) {
1370
+ occupied_cols.push(col);
1371
+ }
1372
+ });
1373
+
1374
+ var empty_rows = [];
1375
+ $.each(old_cells_occupied.rows, function(i, row) {
1376
+ if ($.inArray(row, new_cells_occupied.rows) === -1) {
1377
+ empty_rows.push(row);
1378
+ }
1379
+ });
1380
+
1381
+ var occupied_rows = [];
1382
+ $.each(new_cells_occupied.rows, function(i, row) {
1383
+ if ($.inArray(row, old_cells_occupied.rows) === -1) {
1384
+ occupied_rows.push(row);
1385
+ }
1386
+ });
1387
+
1388
+ this.remove_from_gridmap(wgd);
1389
+
1390
+ if (occupied_cols.length) {
1391
+ var cols_to_empty = [
1392
+ new_wgd.col, new_wgd.row, new_wgd.size_x, Math.min(old_size_y, new_wgd.size_y), $widget
1393
+ ];
1394
+ this.empty_cells.apply(this, cols_to_empty);
1395
+ }
1396
+
1397
+ if (occupied_rows.length) {
1398
+ var rows_to_empty = [new_wgd.col, new_wgd.row, new_wgd.size_x, new_wgd.size_y, $widget];
1399
+ this.empty_cells.apply(this, rows_to_empty);
1400
+ }
1401
+
1402
+ // not the same that wgd = new_wgd;
1403
+ wgd.col = new_wgd.col;
1404
+ wgd.row = new_wgd.row;
1405
+ wgd.size_x = new_wgd.size_x;
1406
+ wgd.size_y = new_wgd.size_y;
1407
+
1408
+ this.add_to_gridmap(new_wgd, $widget);
1409
+
1410
+ $widget.removeClass('player-revert');
1411
+
1412
+ //update coords instance attributes
1413
+ $widget.data('coords').update({
1414
+ width: (new_wgd.size_x * this.options.widget_base_dimensions[0] +
1415
+ ((new_wgd.size_x - 1) * this.options.widget_margins[0]) * 2),
1416
+ height: (new_wgd.size_y * this.options.widget_base_dimensions[1] +
1417
+ ((new_wgd.size_y - 1) * this.options.widget_margins[1]) * 2)
1418
+ });
1419
+
1420
+ $widget.attr({
1421
+ 'data-col': new_wgd.col,
1422
+ 'data-row': new_wgd.row,
1423
+ 'data-sizex': new_wgd.size_x,
1424
+ 'data-sizey': new_wgd.size_y
1425
+ });
1426
+
1427
+ if (empty_cols.length) {
1428
+ var cols_to_remove_holes = [
1429
+ empty_cols[0], new_wgd.row,
1430
+ empty_cols.length,
1431
+ Math.min(old_size_y, new_wgd.size_y),
1432
+ $widget
1433
+ ];
1434
+
1435
+ this.remove_empty_cells.apply(this, cols_to_remove_holes);
1436
+ }
1437
+
1438
+ if (empty_rows.length) {
1439
+ var rows_to_remove_holes = [
1440
+ new_wgd.col, new_wgd.row, new_wgd.size_x, new_wgd.size_y, $widget
1441
+ ];
1442
+ this.remove_empty_cells.apply(this, rows_to_remove_holes);
1443
+ }
1444
+
1445
+ this.move_widget_up($widget);
1446
+
1447
+ return this;
1448
+ };
1449
+
1450
+
1451
+ /**
1452
+ * Move down widgets in cells represented by the arguments col, row, size_x,
1453
+ * size_y
1454
+ *
1455
+ * @method empty_cells
1456
+ * @param {Number} col The column where the group of cells begin.
1457
+ * @param {Number} row The row where the group of cells begin.
1458
+ * @param {Number} size_x The number of columns that the group of cells
1459
+ * occupy.
1460
+ * @param {Number} size_y The number of rows that the group of cells
1461
+ * occupy.
1462
+ * @param {HTMLElement} $exclude Exclude widgets from being moved.
1463
+ * @return {Class} Returns the instance of the Gridster Class.
1464
+ */
1465
+ fn.empty_cells = function(col, row, size_x, size_y, $exclude) {
1466
+ var $nexts = this.widgets_below({
1467
+ col: col,
1468
+ row: row - size_y,
1469
+ size_x: size_x,
1470
+ size_y: size_y
1471
+ });
1472
+
1473
+ $nexts.not($exclude).each($.proxy(function(i, w) {
1474
+ var wgd = $(w).coords().grid;
1475
+ if ( !(wgd.row <= (row + size_y - 1))) { return; }
1476
+ var diff = (row + size_y) - wgd.row;
1477
+ this.move_widget_down($(w), diff);
1478
+ }, this));
1479
+
1480
+ this.set_dom_grid_height();
1481
+
1482
+ return this;
1483
+ };
1484
+
1485
+
1486
+ /**
1487
+ * Move up widgets below cells represented by the arguments col, row, size_x,
1488
+ * size_y.
1489
+ *
1490
+ * @method remove_empty_cells
1491
+ * @param {Number} col The column where the group of cells begin.
1492
+ * @param {Number} row The row where the group of cells begin.
1493
+ * @param {Number} size_x The number of columns that the group of cells
1494
+ * occupy.
1495
+ * @param {Number} size_y The number of rows that the group of cells
1496
+ * occupy.
1497
+ * @param {HTMLElement} exclude Exclude widgets from being moved.
1498
+ * @return {Class} Returns the instance of the Gridster Class.
1499
+ */
1500
+ fn.remove_empty_cells = function(col, row, size_x, size_y, exclude) {
1501
+ var $nexts = this.widgets_below({
1502
+ col: col,
1503
+ row: row,
1504
+ size_x: size_x,
1505
+ size_y: size_y
1506
+ });
1507
+
1508
+ $nexts.not(exclude).each($.proxy(function(i, widget) {
1509
+ this.move_widget_up( $(widget), size_y );
1510
+ }, this));
1511
+
1512
+ this.set_dom_grid_height();
1513
+
1514
+ return this;
1515
+ };
1516
+
1517
+
1518
+ /**
1519
+ * Get the most left column below to add a new widget.
1520
+ *
1521
+ * @method next_position
1522
+ * @param {Number} size_x The nº of rows the widget occupies horizontally.
1523
+ * @param {Number} size_y The nº of columns the widget occupies vertically.
1524
+ * @return {Object} Returns a grid coords object representing the future
1525
+ * widget coords.
1526
+ */
1527
+ fn.next_position = function(size_x, size_y) {
1528
+ size_x || (size_x = 1);
1529
+ size_y || (size_y = 1);
1530
+ var ga = this.gridmap;
1531
+ var cols_l = ga.length;
1532
+ var valid_pos = [];
1533
+ var rows_l;
870
1534
 
871
1535
  for (var c = 1; c < cols_l; c++) {
872
- var rows_l = ga[c].length;
1536
+ rows_l = ga[c].length;
873
1537
  for (var r = 1; r <= rows_l; r++) {
874
1538
  var can_move_to = this.can_move_to({
875
1539
  size_x: size_x,
@@ -888,7 +1552,7 @@
888
1552
  }
889
1553
 
890
1554
  if (valid_pos.length) {
891
- return this.sort_by_row_and_col_asc(valid_pos)[0];
1555
+ return Gridster.sort_by_row_and_col_asc(valid_pos)[0];
892
1556
  }
893
1557
  return false;
894
1558
  };
@@ -899,12 +1563,21 @@
899
1563
  *
900
1564
  * @method remove_widget
901
1565
  * @param {HTMLElement} el The jQuery wrapped HTMLElement you want to remove.
1566
+ * @param {Boolean|Function} silent If true, widgets below the removed one
1567
+ * will not move up. If a Function is passed it will be used as callback.
1568
+ * @param {Function} callback Function executed when the widget is removed.
902
1569
  * @return {Class} Returns the instance of the Gridster Class.
903
1570
  */
904
- fn.remove_widget = function(el, callback) {
905
- var $el = el instanceof jQuery ? el : $(el);
1571
+ fn.remove_widget = function(el, silent, callback) {
1572
+ var $el = el instanceof $ ? el : $(el);
906
1573
  var wgd = $el.coords().grid;
907
1574
 
1575
+ // if silent is a function assume it's a callback
1576
+ if ($.isFunction(silent)) {
1577
+ callback = silent;
1578
+ silent = false;
1579
+ }
1580
+
908
1581
  this.cells_occupied_by_placeholder = {};
909
1582
  this.$widgets = this.$widgets.not($el);
910
1583
 
@@ -915,9 +1588,11 @@
915
1588
  $el.fadeOut($.proxy(function() {
916
1589
  $el.remove();
917
1590
 
918
- $nexts.each($.proxy(function(i, widget) {
919
- this.move_widget_up( $(widget), wgd.size_y );
920
- }, this));
1591
+ if (!silent) {
1592
+ $nexts.each($.proxy(function(i, widget) {
1593
+ this.move_widget_up( $(widget), wgd.size_y );
1594
+ }, this));
1595
+ }
921
1596
 
922
1597
  this.set_dom_grid_height();
923
1598
 
@@ -925,6 +1600,24 @@
925
1600
  callback.call(this, el);
926
1601
  }
927
1602
  }, this));
1603
+
1604
+ return this;
1605
+ };
1606
+
1607
+
1608
+ /**
1609
+ * Remove all widgets from the grid.
1610
+ *
1611
+ * @method remove_all_widgets
1612
+ * @param {Function} callback Function executed for each widget removed.
1613
+ * @return {Class} Returns the instance of the Gridster Class.
1614
+ */
1615
+ fn.remove_all_widgets = function(callback) {
1616
+ this.$widgets.each($.proxy(function(i, el){
1617
+ this.remove_widget(el, true, callback);
1618
+ }, this));
1619
+
1620
+ return this;
928
1621
  };
929
1622
 
930
1623
 
@@ -940,13 +1633,11 @@
940
1633
  */
941
1634
  fn.serialize = function($widgets) {
942
1635
  $widgets || ($widgets = this.$widgets);
943
- var result = [];
944
- $widgets.each($.proxy(function(i, widget) {
945
- result.push(this.options.serialize_params(
946
- $(widget), $(widget).coords().grid ) );
947
- }, this));
948
1636
 
949
- return result;
1637
+ return $widgets.map($.proxy(function(i, widget) {
1638
+ var $w = $(widget);
1639
+ return this.options.serialize_params($w, $w.coords().grid);
1640
+ }, this)).get();
950
1641
  };
951
1642
 
952
1643
 
@@ -964,45 +1655,74 @@
964
1655
 
965
1656
 
966
1657
  /**
967
- * Creates the grid coords object representing the widget a add it to the
1658
+ * Convert widgets from DOM elements to "widget grid data" Objects.
1659
+ *
1660
+ * @method dom_to_coords
1661
+ * @param {HTMLElement} $widget The widget to be converted.
1662
+ */
1663
+ fn.dom_to_coords = function($widget) {
1664
+ return {
1665
+ 'col': parseInt($widget.attr('data-col'), 10),
1666
+ 'row': parseInt($widget.attr('data-row'), 10),
1667
+ 'size_x': parseInt($widget.attr('data-sizex'), 10) || 1,
1668
+ 'size_y': parseInt($widget.attr('data-sizey'), 10) || 1,
1669
+ 'max_size_x': parseInt($widget.attr('data-max-sizex'), 10) || false,
1670
+ 'max_size_y': parseInt($widget.attr('data-max-sizey'), 10) || false,
1671
+ 'min_size_x': parseInt($widget.attr('data-min-sizex'), 10) || false,
1672
+ 'min_size_y': parseInt($widget.attr('data-min-sizey'), 10) || false,
1673
+ 'el': $widget
1674
+ };
1675
+ };
1676
+
1677
+
1678
+ /**
1679
+ * Creates the grid coords object representing the widget an add it to the
968
1680
  * mapped array of positions.
969
1681
  *
970
1682
  * @method register_widget
971
- * @return {Array} Returns the instance of the Gridster class.
1683
+ * @param {HTMLElement|Object} $el jQuery wrapped HTMLElement representing
1684
+ * the widget, or an "widget grid data" Object with (col, row, el ...).
1685
+ * @return {Boolean} Returns true if the widget final position is different
1686
+ * than the original.
972
1687
  */
973
1688
  fn.register_widget = function($el) {
1689
+ var isDOM = $el instanceof jQuery;
1690
+ var wgd = isDOM ? this.dom_to_coords($el) : $el;
1691
+ var posChanged = false;
1692
+ isDOM || ($el = wgd.el);
974
1693
 
975
- var wgd = {
976
- 'col': parseInt($el.attr('data-col'), 10),
977
- 'row': parseInt($el.attr('data-row'), 10),
978
- 'size_x': parseInt($el.attr('data-sizex'), 10),
979
- 'size_y': parseInt($el.attr('data-sizey'), 10),
980
- 'el': $el
981
- };
1694
+ var empty_upper_row = this.can_go_widget_up(wgd);
1695
+ if (empty_upper_row) {
1696
+ wgd.row = empty_upper_row;
1697
+ $el.attr('data-row', empty_upper_row);
1698
+ this.$el.trigger('gridster:positionchanged', [wgd]);
1699
+ posChanged = true;
1700
+ }
982
1701
 
983
1702
  if (this.options.avoid_overlapped_widgets &&
984
1703
  !this.can_move_to(
985
1704
  {size_x: wgd.size_x, size_y: wgd.size_y}, wgd.col, wgd.row)
986
1705
  ) {
987
- wgd = this.next_position(wgd.size_x, wgd.size_y);
988
- wgd.el = $el;
1706
+ $.extend(wgd, this.next_position(wgd.size_x, wgd.size_y));
989
1707
  $el.attr({
990
1708
  'data-col': wgd.col,
991
1709
  'data-row': wgd.row,
992
1710
  'data-sizex': wgd.size_x,
993
1711
  'data-sizey': wgd.size_y
994
1712
  });
1713
+ posChanged = true;
995
1714
  }
996
1715
 
997
1716
  // attach Coord object to player data-coord attribute
998
1717
  $el.data('coords', $el.coords());
999
-
1000
1718
  // Extend Coord object with grid position info
1001
1719
  $el.data('coords').grid = wgd;
1002
1720
 
1003
1721
  this.add_to_gridmap(wgd, $el);
1004
- this.widgets.push($el);
1005
- return this;
1722
+
1723
+ this.options.resize.enabled && this.add_resize_handle($el);
1724
+
1725
+ return posChanged;
1006
1726
  };
1007
1727
 
1008
1728
 
@@ -1073,14 +1793,16 @@
1073
1793
  var self = this;
1074
1794
  var draggable_options = $.extend(true, {}, this.options.draggable, {
1075
1795
  offset_left: this.options.widget_margins[0],
1076
- items: '.gs_w',
1796
+ offset_top: this.options.widget_margins[1],
1797
+ container_width: this.cols * this.min_widget_width,
1798
+ limit: true,
1077
1799
  start: function(event, ui) {
1078
1800
  self.$widgets.filter('.player-revert')
1079
1801
  .removeClass('player-revert');
1080
1802
 
1081
1803
  self.$player = $(this);
1082
- self.$helper = self.options.draggable.helper === 'clone' ?
1083
- $(ui.helper) : self.$player;
1804
+ self.$helper = $(ui.$helper);
1805
+
1084
1806
  self.helper = !self.$helper.is(self.$player);
1085
1807
 
1086
1808
  self.on_start_drag.call(self, event, ui);
@@ -1096,7 +1818,59 @@
1096
1818
  }, 60)
1097
1819
  });
1098
1820
 
1099
- this.drag_api = this.$el.drag(draggable_options).data('drag');
1821
+ this.drag_api = this.$el.drag(draggable_options);
1822
+ return this;
1823
+ };
1824
+
1825
+
1826
+ /**
1827
+ * Bind resize events to get resize working.
1828
+ *
1829
+ * @method resizable
1830
+ * @return {Class} Returns instance of gridster Class.
1831
+ */
1832
+ fn.resizable = function() {
1833
+ this.resize_api = this.$el.drag({
1834
+ items: '.' + this.options.resize.handle_class,
1835
+ offset_left: this.options.widget_margins[0],
1836
+ container_width: this.container_width,
1837
+ move_element: false,
1838
+ resize: true,
1839
+ limit: this.options.autogrow_cols ? false : true,
1840
+ start: $.proxy(this.on_start_resize, this),
1841
+ stop: $.proxy(function(event, ui) {
1842
+ delay($.proxy(function() {
1843
+ this.on_stop_resize(event, ui);
1844
+ }, this), 120);
1845
+ }, this),
1846
+ drag: throttle($.proxy(this.on_resize, this), 60)
1847
+ });
1848
+
1849
+ return this;
1850
+ };
1851
+
1852
+
1853
+ /**
1854
+ * Setup things required for resizing. Like build templates for drag handles.
1855
+ *
1856
+ * @method setup_resize
1857
+ * @return {Class} Returns instance of gridster Class.
1858
+ */
1859
+ fn.setup_resize = function() {
1860
+ this.resize_handle_class = this.options.resize.handle_class;
1861
+ var axes = this.options.resize.axes;
1862
+ var handle_tpl = '<span class="' + this.resize_handle_class + ' ' +
1863
+ this.resize_handle_class + '-{type}" />';
1864
+
1865
+ this.resize_handle_tpl = $.map(axes, function(type) {
1866
+ return handle_tpl.replace('{type}', type);
1867
+ }).join('');
1868
+
1869
+ if ($.isArray(this.options.draggable.ignore_dragging)) {
1870
+ this.options.draggable.ignore_dragging.push(
1871
+ '.' + this.resize_handle_class);
1872
+ }
1873
+
1100
1874
  return this;
1101
1875
  };
1102
1876
 
@@ -1105,20 +1879,29 @@
1105
1879
  * This function is executed when the player begins to be dragged.
1106
1880
  *
1107
1881
  * @method on_start_drag
1108
- * @param {Event} The original browser event
1109
- * @param {Object} A prepared ui object.
1882
+ * @param {Event} event The original browser event
1883
+ * @param {Object} ui A prepared ui object with useful drag-related data
1110
1884
  */
1111
1885
  fn.on_start_drag = function(event, ui) {
1112
-
1113
1886
  this.$helper.add(this.$player).add(this.$wrapper).addClass('dragging');
1114
1887
 
1888
+ this.highest_col = this.get_highest_occupied_cell().col;
1889
+
1115
1890
  this.$player.addClass('player');
1116
1891
  this.player_grid_data = this.$player.coords().grid;
1117
1892
  this.placeholder_grid_data = $.extend({}, this.player_grid_data);
1118
1893
 
1119
- //set new grid height along the dragging period
1120
- this.$el.css('height', this.$el.height() +
1121
- (this.player_grid_data.size_y * this.min_widget_height));
1894
+ this.set_dom_grid_height(this.$el.height() +
1895
+ (this.player_grid_data.size_y * this.min_widget_height));
1896
+
1897
+ this.set_dom_grid_width(this.cols);
1898
+
1899
+ var pgd_sizex = this.player_grid_data.size_x;
1900
+ var cols_diff = this.cols - this.highest_col;
1901
+
1902
+ if (this.options.autogrow_cols && cols_diff <= pgd_sizex) {
1903
+ this.add_faux_cols(Math.min(pgd_sizex - cols_diff, 1));
1904
+ }
1122
1905
 
1123
1906
  var colliders = this.faux_grid;
1124
1907
  var coords = this.$player.data('coords').coords;
@@ -1131,12 +1914,11 @@
1131
1914
  this.last_cols = [];
1132
1915
  this.last_rows = [];
1133
1916
 
1134
-
1135
1917
  // see jquery.collision.js
1136
1918
  this.collision_api = this.$helper.collision(
1137
1919
  colliders, this.options.collision);
1138
1920
 
1139
- this.$preview_holder = $('<li />', {
1921
+ this.$preview_holder = $('<' + this.$player.get(0).tagName + ' />', {
1140
1922
  'class': 'preview-holder',
1141
1923
  'data-row': this.$player.attr('data-row'),
1142
1924
  'data-col': this.$player.attr('data-col'),
@@ -1156,32 +1938,44 @@
1156
1938
  * This function is executed when the player is being dragged.
1157
1939
  *
1158
1940
  * @method on_drag
1159
- * @param {Event} The original browser event
1160
- * @param {Object} A prepared ui object.
1941
+ * @param {Event} event The original browser event
1942
+ * @param {Object} ui A prepared ui object with useful drag-related data
1161
1943
  */
1162
1944
  fn.on_drag = function(event, ui) {
1163
1945
  //break if dragstop has been fired
1164
1946
  if (this.$player === null) {
1165
1947
  return false;
1166
- };
1948
+ }
1167
1949
 
1168
1950
  var abs_offset = {
1169
1951
  left: ui.position.left + this.baseX,
1170
1952
  top: ui.position.top + this.baseY
1171
1953
  };
1172
1954
 
1955
+ // auto grow cols
1956
+ if (this.options.autogrow_cols) {
1957
+ var prcol = this.placeholder_grid_data.col +
1958
+ this.placeholder_grid_data.size_x - 1;
1959
+
1960
+ // "- 1" due to adding at least 1 column in on_start_drag
1961
+ if (prcol >= this.cols - 1 && this.options.max_cols >= this.cols + 1) {
1962
+ this.add_faux_cols(1);
1963
+ this.set_dom_grid_width(this.cols + 1);
1964
+ this.drag_api.set_limits(this.container_width);
1965
+ }
1966
+
1967
+ this.collision_api.set_colliders(this.faux_grid);
1968
+ }
1969
+
1173
1970
  this.colliders_data = this.collision_api.get_closest_colliders(
1174
1971
  abs_offset);
1175
1972
 
1176
1973
  this.on_overlapped_column_change(
1177
- this.on_start_overlapping_column,
1178
- this.on_stop_overlapping_column
1179
- );
1974
+ this.on_start_overlapping_column, this.on_stop_overlapping_column);
1180
1975
 
1181
1976
  this.on_overlapped_row_change(
1182
- this.on_start_overlapping_row,
1183
- this.on_stop_overlapping_row
1184
- );
1977
+ this.on_start_overlapping_row, this.on_stop_overlapping_row);
1978
+
1185
1979
 
1186
1980
  if (this.helper && this.$player) {
1187
1981
  this.$player.css({
@@ -1195,12 +1989,13 @@
1195
1989
  }
1196
1990
  };
1197
1991
 
1992
+
1198
1993
  /**
1199
1994
  * This function is executed when the player stops being dragged.
1200
1995
  *
1201
1996
  * @method on_stop_drag
1202
- * @param {Event} The original browser event
1203
- * @param {Object} A prepared ui object.
1997
+ * @param {Event} event The original browser event
1998
+ * @param {Object} ui A prepared ui object with useful drag-related data
1204
1999
  */
1205
2000
  fn.on_stop_drag = function(event, ui) {
1206
2001
  this.$helper.add(this.$player).add(this.$wrapper)
@@ -1208,7 +2003,8 @@
1208
2003
 
1209
2004
  ui.position.left = ui.position.left + this.baseX;
1210
2005
  ui.position.top = ui.position.top + this.baseY;
1211
- this.colliders_data = this.collision_api.get_closest_colliders(ui.position);
2006
+ this.colliders_data = this.collision_api.get_closest_colliders(
2007
+ ui.position);
1212
2008
 
1213
2009
  this.on_overlapped_column_change(
1214
2010
  this.on_start_overlapping_column,
@@ -1229,24 +2025,233 @@
1229
2025
  'top': ''
1230
2026
  });
1231
2027
 
1232
- this.$changed = this.$changed.add(this.$player);
2028
+ this.$changed = this.$changed.add(this.$player);
2029
+
2030
+ this.cells_occupied_by_player = this.get_cells_occupied(
2031
+ this.placeholder_grid_data);
2032
+ this.set_cells_player_occupies(
2033
+ this.placeholder_grid_data.col, this.placeholder_grid_data.row);
2034
+
2035
+ this.$player.coords().grid.row = this.placeholder_grid_data.row;
2036
+ this.$player.coords().grid.col = this.placeholder_grid_data.col;
2037
+
2038
+ if (this.options.draggable.stop) {
2039
+ this.options.draggable.stop.call(this, event, ui);
2040
+ }
2041
+
2042
+ this.$preview_holder.remove();
2043
+
2044
+ this.$player = null;
2045
+ this.$helper = null;
2046
+ this.placeholder_grid_data = {};
2047
+ this.player_grid_data = {};
2048
+ this.cells_occupied_by_placeholder = {};
2049
+ this.cells_occupied_by_player = {};
2050
+
2051
+ this.set_dom_grid_height();
2052
+ this.set_dom_grid_width();
2053
+
2054
+ if (this.options.autogrow_cols) {
2055
+ this.drag_api.set_limits(this.cols * this.min_widget_width);
2056
+ }
2057
+ };
2058
+
2059
+
2060
+ /**
2061
+ * This function is executed every time a widget starts to be resized.
2062
+ *
2063
+ * @method on_start_resize
2064
+ * @param {Event} event The original browser event
2065
+ * @param {Object} ui A prepared ui object with useful drag-related data
2066
+ */
2067
+ fn.on_start_resize = function(event, ui) {
2068
+ this.$resized_widget = ui.$player.closest('.gs-w');
2069
+ this.resize_coords = this.$resized_widget.coords();
2070
+ this.resize_wgd = this.resize_coords.grid;
2071
+ this.resize_initial_width = this.resize_coords.coords.width;
2072
+ this.resize_initial_height = this.resize_coords.coords.height;
2073
+ this.resize_initial_sizex = this.resize_coords.grid.size_x;
2074
+ this.resize_initial_sizey = this.resize_coords.grid.size_y;
2075
+ this.resize_initial_col = this.resize_coords.grid.col;
2076
+ this.resize_last_sizex = this.resize_initial_sizex;
2077
+ this.resize_last_sizey = this.resize_initial_sizey;
2078
+
2079
+ this.resize_max_size_x = Math.min(this.resize_wgd.max_size_x ||
2080
+ this.options.resize.max_size[0],
2081
+ this.options.max_cols - this.resize_initial_col + 1);
2082
+ this.resize_max_size_y = this.resize_wgd.max_size_y ||
2083
+ this.options.resize.max_size[1];
2084
+
2085
+ this.resize_min_size_x = (this.resize_wgd.min_size_x ||
2086
+ this.options.resize.min_size[0] || 1);
2087
+ this.resize_min_size_y = (this.resize_wgd.min_size_y ||
2088
+ this.options.resize.min_size[1] || 1);
2089
+
2090
+ this.resize_initial_last_col = this.get_highest_occupied_cell().col;
2091
+
2092
+ this.set_dom_grid_width(this.cols);
2093
+
2094
+ this.resize_dir = {
2095
+ right: ui.$player.is('.' + this.resize_handle_class + '-x'),
2096
+ bottom: ui.$player.is('.' + this.resize_handle_class + '-y')
2097
+ };
2098
+
2099
+ this.$resized_widget.css({
2100
+ 'min-width': this.options.widget_base_dimensions[0],
2101
+ 'min-height': this.options.widget_base_dimensions[1]
2102
+ });
2103
+
2104
+ var nodeName = this.$resized_widget.get(0).tagName;
2105
+ this.$resize_preview_holder = $('<' + nodeName + ' />', {
2106
+ 'class': 'preview-holder resize-preview-holder',
2107
+ 'data-row': this.$resized_widget.attr('data-row'),
2108
+ 'data-col': this.$resized_widget.attr('data-col'),
2109
+ 'css': {
2110
+ 'width': this.resize_initial_width,
2111
+ 'height': this.resize_initial_height
2112
+ }
2113
+ }).appendTo(this.$el);
2114
+
2115
+ this.$resized_widget.addClass('resizing');
2116
+
2117
+ if (this.options.resize.start) {
2118
+ this.options.resize.start.call(this, event, ui, this.$resized_widget);
2119
+ }
2120
+
2121
+ this.$el.trigger('gridster:resizestart');
2122
+ };
2123
+
2124
+
2125
+ /**
2126
+ * This function is executed every time a widget stops being resized.
2127
+ *
2128
+ * @method on_stop_resize
2129
+ * @param {Event} event The original browser event
2130
+ * @param {Object} ui A prepared ui object with useful drag-related data
2131
+ */
2132
+ fn.on_stop_resize = function(event, ui) {
2133
+ this.$resized_widget
2134
+ .removeClass('resizing')
2135
+ .css({
2136
+ 'width': '',
2137
+ 'height': ''
2138
+ });
2139
+
2140
+ delay($.proxy(function() {
2141
+ this.$resize_preview_holder
2142
+ .remove()
2143
+ .css({
2144
+ 'min-width': '',
2145
+ 'min-height': ''
2146
+ });
2147
+
2148
+ if (this.options.resize.stop) {
2149
+ this.options.resize.stop.call(this, event, ui, this.$resized_widget);
2150
+ }
2151
+
2152
+ this.$el.trigger('gridster:resizestop');
2153
+ }, this), 300);
2154
+
2155
+ this.set_dom_grid_width();
2156
+
2157
+ if (this.options.autogrow_cols) {
2158
+ this.drag_api.set_limits(this.cols * this.min_widget_width);
2159
+ }
2160
+ };
2161
+
2162
+
2163
+ /**
2164
+ * This function is executed when a widget is being resized.
2165
+ *
2166
+ * @method on_resize
2167
+ * @param {Event} event The original browser event
2168
+ * @param {Object} ui A prepared ui object with useful drag-related data
2169
+ */
2170
+ fn.on_resize = function(event, ui) {
2171
+ var rel_x = (ui.pointer.diff_left);
2172
+ var rel_y = (ui.pointer.diff_top);
2173
+ var wbd_x = this.options.widget_base_dimensions[0];
2174
+ var wbd_y = this.options.widget_base_dimensions[1];
2175
+ var margin_x = this.options.widget_margins[0];
2176
+ var margin_y = this.options.widget_margins[1];
2177
+ var max_size_x = this.resize_max_size_x;
2178
+ var min_size_x = this.resize_min_size_x;
2179
+ var max_size_y = this.resize_max_size_y;
2180
+ var min_size_y = this.resize_min_size_y;
2181
+ var autogrow = this.options.autogrow_cols;
2182
+ var width;
2183
+ var max_width = Infinity;
2184
+ var max_height = Infinity;
2185
+
2186
+ var inc_units_x = Math.ceil((rel_x / (wbd_x + margin_x * 2)) - 0.2);
2187
+ var inc_units_y = Math.ceil((rel_y / (wbd_y + margin_y * 2)) - 0.2);
2188
+
2189
+ var size_x = Math.max(1, this.resize_initial_sizex + inc_units_x);
2190
+ var size_y = Math.max(1, this.resize_initial_sizey + inc_units_y);
2191
+
2192
+ var max_cols = (this.container_width / this.min_widget_width) -
2193
+ this.resize_initial_col + 1;
2194
+ var limit_width = ((max_cols * this.min_widget_width) - margin_x * 2);
2195
+
2196
+ size_x = Math.max(Math.min(size_x, max_size_x), min_size_x);
2197
+ size_x = Math.min(max_cols, size_x);
2198
+ width = (max_size_x * wbd_x) + ((size_x - 1) * margin_x * 2);
2199
+ max_width = Math.min(width, limit_width);
2200
+ min_width = (min_size_x * wbd_x) + ((size_x - 1) * margin_x * 2);
2201
+
2202
+ size_y = Math.max(Math.min(size_y, max_size_y), min_size_y);
2203
+ max_height = (max_size_y * wbd_y) + ((size_y - 1) * margin_y * 2);
2204
+ min_height = (min_size_y * wbd_y) + ((size_y - 1) * margin_y * 2);
2205
+
2206
+ if (this.resize_dir.right) {
2207
+ size_y = this.resize_initial_sizey;
2208
+ } else if (this.resize_dir.bottom) {
2209
+ size_x = this.resize_initial_sizex;
2210
+ }
2211
+
2212
+ if (autogrow) {
2213
+ var last_widget_col = this.resize_initial_col + size_x - 1;
2214
+ if (autogrow && this.resize_initial_last_col <= last_widget_col) {
2215
+ this.set_dom_grid_width(Math.max(last_widget_col + 1, this.cols));
2216
+
2217
+ if (this.cols < last_widget_col) {
2218
+ this.add_faux_cols(last_widget_col - this.cols);
2219
+ }
2220
+ }
2221
+ }
2222
+
2223
+ var css_props = {};
2224
+ !this.resize_dir.bottom && (css_props.width = Math.max(Math.min(
2225
+ this.resize_initial_width + rel_x, max_width), min_width));
2226
+ !this.resize_dir.right && (css_props.height = Math.max(Math.min(
2227
+ this.resize_initial_height + rel_y, max_height), min_height));
2228
+
2229
+ this.$resized_widget.css(css_props);
1233
2230
 
1234
- this.cells_occupied_by_player = this.get_cells_occupied(
1235
- this.placeholder_grid_data);
1236
- this.set_cells_player_occupies(
1237
- this.placeholder_grid_data.col, this.placeholder_grid_data.row);
2231
+ if (size_x !== this.resize_last_sizex ||
2232
+ size_y !== this.resize_last_sizey) {
1238
2233
 
1239
- this.$player.coords().grid.row = this.placeholder_grid_data.row;
1240
- this.$player.coords().grid.col = this.placeholder_grid_data.col;
2234
+ this.resize_widget(this.$resized_widget, size_x, size_y);
2235
+ this.set_dom_grid_width(this.cols);
1241
2236
 
1242
- if (this.options.draggable.stop) {
1243
- this.options.draggable.stop.call(this, event, ui);
2237
+ this.$resize_preview_holder.css({
2238
+ 'width': '',
2239
+ 'height': ''
2240
+ }).attr({
2241
+ 'data-row': this.$resized_widget.attr('data-row'),
2242
+ 'data-sizex': size_x,
2243
+ 'data-sizey': size_y
2244
+ });
1244
2245
  }
1245
2246
 
1246
- this.$preview_holder.remove();
1247
- this.$player = null;
2247
+ if (this.options.resize.resize) {
2248
+ this.options.resize.resize.call(this, event, ui, this.$resized_widget);
2249
+ }
1248
2250
 
1249
- this.set_dom_grid_height();
2251
+ this.$el.trigger('gridster:resize');
2252
+
2253
+ this.resize_last_sizex = size_x;
2254
+ this.resize_last_sizey = size_y;
1250
2255
  };
1251
2256
 
1252
2257
 
@@ -1263,7 +2268,7 @@
1263
2268
  */
1264
2269
  fn.on_overlapped_column_change = function(start_callback, stop_callback) {
1265
2270
  if (!this.colliders_data.length) {
1266
- return;
2271
+ return this;
1267
2272
  }
1268
2273
  var cols = this.get_targeted_columns(
1269
2274
  this.colliders_data[0].el.data.col);
@@ -1296,14 +2301,14 @@
1296
2301
  *
1297
2302
  * @param {Function} start_callback Function executed when a new row begins
1298
2303
  * to be overlapped. The row is passed as first argument.
1299
- * @param {Function} stop_callback Function executed when a row stops being
2304
+ * @param {Function} end_callback Function executed when a row stops being
1300
2305
  * overlapped. The row is passed as first argument.
1301
2306
  * @method on_overlapped_row_change
1302
2307
  * @return {Class} Returns the instance of the Gridster Class.
1303
2308
  */
1304
2309
  fn.on_overlapped_row_change = function(start_callback, end_callback) {
1305
2310
  if (!this.colliders_data.length) {
1306
- return;
2311
+ return this;
1307
2312
  }
1308
2313
  var rows = this.get_targeted_rows(this.colliders_data[0].el.data.row);
1309
2314
  var last_n_rows = this.last_rows.length;
@@ -1329,18 +2334,18 @@
1329
2334
  /**
1330
2335
  * Sets the current position of the player
1331
2336
  *
1332
- * @param {Function} start_callback Function executed when a new row begins
1333
- * to be overlapped. The row is passed as first argument.
1334
- * @param {Function} stop_callback Function executed when a row stops being
1335
- * overlapped. The row is passed as first argument.
2337
+ * @param {Number} col
2338
+ * @param {Number} row
2339
+ * @param {Boolean} no_player
1336
2340
  * @method set_player
1337
- * @return {Class} Returns the instance of the Gridster Class.
2341
+ * @return {object}
1338
2342
  */
1339
- fn.set_player = function(col, row) {
1340
- this.empty_cells_player_occupies();
1341
-
2343
+ fn.set_player = function(col, row, no_player) {
1342
2344
  var self = this;
1343
- var cell = self.colliders_data[0].el.data;
2345
+ if (!no_player) {
2346
+ this.empty_cells_player_occupies();
2347
+ }
2348
+ var cell = !no_player ? self.colliders_data[0].el.data : {col: col};
1344
2349
  var to_col = cell.col;
1345
2350
  var to_row = row || cell.row;
1346
2351
 
@@ -1384,9 +2389,9 @@
1384
2389
  * a upper row and which not.
1385
2390
  *
1386
2391
  * @method widgets_contraints
1387
- * @param {HTMLElements} $widgets A jQuery wrapped collection of
2392
+ * @param {jQuery} $widgets A jQuery wrapped collection of
1388
2393
  * HTMLElements.
1389
- * @return {Array} Returns a literal Object with two keys: `can_go_up` &
2394
+ * @return {object} Returns a literal Object with two keys: `can_go_up` &
1390
2395
  * `can_not_go_up`. Each contains a set of HTMLElements.
1391
2396
  */
1392
2397
  fn.widgets_constraints = function($widgets) {
@@ -1401,7 +2406,7 @@
1401
2406
  if (this.can_go_widget_up(wgd)) {
1402
2407
  $widgets_can_go_up = $widgets_can_go_up.add($w);
1403
2408
  wgd_can_go_up.push(wgd);
1404
- }else{
2409
+ } else {
1405
2410
  wgd_can_not_go_up.push(wgd);
1406
2411
  }
1407
2412
  }, this));
@@ -1409,97 +2414,18 @@
1409
2414
  $widgets_can_not_go_up = $widgets.not($widgets_can_go_up);
1410
2415
 
1411
2416
  return {
1412
- can_go_up: this.sort_by_row_asc(wgd_can_go_up),
1413
- can_not_go_up: this.sort_by_row_desc(wgd_can_not_go_up)
2417
+ can_go_up: Gridster.sort_by_row_asc(wgd_can_go_up),
2418
+ can_not_go_up: Gridster.sort_by_row_desc(wgd_can_not_go_up)
1414
2419
  };
1415
2420
  };
1416
2421
 
1417
2422
 
1418
- /**
1419
- * Sorts an Array of grid coords objects (representing the grid coords of
1420
- * each widget) in ascending way.
1421
- *
1422
- * @method sort_by_row_asc
1423
- * @param {Array} widgets Array of grid coords objects
1424
- * @return {Array} Returns the array sorted.
1425
- */
1426
- fn.sort_by_row_asc = function(widgets) {
1427
- widgets = widgets.sort(function(a, b) {
1428
- if (a.row > b.row) {
1429
- return 1;
1430
- }
1431
- return -1;
1432
- });
1433
-
1434
- return widgets;
1435
- };
1436
-
1437
-
1438
- /**
1439
- * Sorts an Array of grid coords objects (representing the grid coords of
1440
- * each widget) placing first the empty cells upper left.
1441
- *
1442
- * @method sort_by_row_and_col_asc
1443
- * @param {Array} widgets Array of grid coords objects
1444
- * @return {Array} Returns the array sorted.
1445
- */
1446
- fn.sort_by_row_and_col_asc = function(widgets) {
1447
- widgets = widgets.sort(function(a, b) {
1448
- if (a.row > b.row || a.row == b.row && a.col > b.col) {
1449
- return 1;
1450
- }
1451
- return -1;
1452
- });
1453
-
1454
- return widgets;
1455
- };
1456
-
1457
-
1458
- /**
1459
- * Sorts an Array of grid coords objects by column (representing the grid
1460
- * coords of each widget) in ascending way.
1461
- *
1462
- * @method sort_by_col_asc
1463
- * @param {Array} widgets Array of grid coords objects
1464
- * @return {Array} Returns the array sorted.
1465
- */
1466
- fn.sort_by_col_asc = function(widgets) {
1467
- widgets = widgets.sort(function(a, b) {
1468
- if (a.col > b.col) {
1469
- return 1;
1470
- }
1471
- return -1;
1472
- });
1473
-
1474
- return widgets;
1475
- };
1476
-
1477
-
1478
- /**
1479
- * Sorts an Array of grid coords objects (representing the grid coords of
1480
- * each widget) in descending way.
1481
- *
1482
- * @method sort_by_row_desc
1483
- * @param {Array} widgets Array of grid coords objects
1484
- * @return {Array} Returns the array sorted.
1485
- */
1486
- fn.sort_by_row_desc = function(widgets) {
1487
- widgets = widgets.sort(function(a, b) {
1488
- if (a.row + a.size_y < b.row + b.size_y) {
1489
- return 1;
1490
- }
1491
- return -1;
1492
- });
1493
- return widgets;
1494
- };
1495
-
1496
-
1497
2423
  /**
1498
2424
  * Sorts an Array of grid coords objects (representing the grid coords of
1499
2425
  * each widget) in descending way.
1500
2426
  *
1501
2427
  * @method manage_movements
1502
- * @param {HTMLElements} $widgets A jQuery collection of HTMLElements
2428
+ * @param {jQuery} $widgets A jQuery collection of HTMLElements
1503
2429
  * representing the widgets you want to move.
1504
2430
  * @param {Number} to_col The column to which we want to move the widgets.
1505
2431
  * @param {Number} to_row The row to which we want to move the widgets.
@@ -1607,13 +2533,15 @@
1607
2533
  * @return {Boolean} Returns true or false.
1608
2534
  */
1609
2535
  fn.is_empty = function(col, row) {
1610
- if (typeof this.gridmap[col] !== 'undefined' &&
1611
- typeof this.gridmap[col][row] !== 'undefined' &&
1612
- this.gridmap[col][row] === false
1613
- ) {
1614
- return true;
2536
+ if (typeof this.gridmap[col] !== 'undefined') {
2537
+ if(typeof this.gridmap[col][row] !== 'undefined' &&
2538
+ this.gridmap[col][row] === false
2539
+ ) {
2540
+ return true;
2541
+ }
2542
+ return false;
1615
2543
  }
1616
- return false;
2544
+ return true;
1617
2545
  };
1618
2546
 
1619
2547
 
@@ -1680,13 +2608,14 @@
1680
2608
 
1681
2609
 
1682
2610
  /**
1683
- * Get widgets overlapping with the player.
2611
+ * Get widgets overlapping with the player or with the object passed
2612
+ * representing the grid cells.
1684
2613
  *
1685
2614
  * @method get_widgets_under_player
1686
2615
  * @return {HTMLElement} Returns a jQuery collection of HTMLElements
1687
2616
  */
1688
- fn.get_widgets_under_player = function() {
1689
- var cells = this.cells_occupied_by_player;
2617
+ fn.get_widgets_under_player = function(cells) {
2618
+ cells || (cells = this.cells_occupied_by_player || {cols: [], rows: []});
1690
2619
  var $widgets = $([]);
1691
2620
 
1692
2621
  $.each(cells.cols, $.proxy(function(i, col) {
@@ -1720,7 +2649,7 @@
1720
2649
  size_x: phgd.size_x
1721
2650
  });
1722
2651
 
1723
- //Prevents widgets go out of the grid
2652
+ // Prevents widgets go out of the grid
1724
2653
  var right_col = (col + phgd.size_x - 1);
1725
2654
  if (right_col > this.cols) {
1726
2655
  col = col - (right_col - col);
@@ -1747,6 +2676,17 @@
1747
2676
  }, this));
1748
2677
  }
1749
2678
 
2679
+ var $widgets_under_ph = this.get_widgets_under_player(
2680
+ this.cells_occupied_by_placeholder);
2681
+
2682
+ if ($widgets_under_ph.length) {
2683
+ $widgets_under_ph.each($.proxy(function(i, widget) {
2684
+ var $w = $(widget);
2685
+ this.move_widget_down(
2686
+ $w, row + phgd.size_y - $w.data('coords').grid.row);
2687
+ }, this));
2688
+ }
2689
+
1750
2690
  };
1751
2691
 
1752
2692
 
@@ -1780,7 +2720,7 @@
1780
2720
  ) {
1781
2721
  upper_rows[tcol].push(r);
1782
2722
  min_row = r < min_row ? r : min_row;
1783
- }else{
2723
+ } else {
1784
2724
  break;
1785
2725
  }
1786
2726
  }
@@ -1790,7 +2730,9 @@
1790
2730
  return true; //break
1791
2731
  }
1792
2732
 
1793
- upper_rows[tcol].sort();
2733
+ upper_rows[tcol].sort(function(a, b) {
2734
+ return a - b;
2735
+ });
1794
2736
  });
1795
2737
 
1796
2738
  if (!result) { return false; }
@@ -1814,33 +2756,26 @@
1814
2756
  var upper_rows = [];
1815
2757
  var min_row = 10000;
1816
2758
 
1817
- if (widget_grid_data.col < this.player_grid_data.col &&
1818
- (widget_grid_data.col + widget_grid_data.size_y - 1) >
1819
- (this.player_grid_data.col + this.player_grid_data.size_y - 1)
1820
- ) {
1821
- return false;
1822
- };
1823
-
1824
- /* generate an array with columns as index and array with upper rows
2759
+ /* generate an array with columns as index and array with topmost rows
1825
2760
  * empty as value */
1826
2761
  this.for_each_column_occupied(widget_grid_data, function(tcol) {
1827
2762
  var grid_col = this.gridmap[tcol];
1828
2763
  upper_rows[tcol] = [];
1829
2764
 
1830
2765
  var r = p_bottom_row + 1;
1831
-
2766
+ // iterate over each row
1832
2767
  while (--r > 0) {
1833
2768
  if (this.is_widget(tcol, r) && !this.is_player_in(tcol, r)) {
1834
2769
  if (!grid_col[r].is(widget_grid_data.el)) {
1835
2770
  break;
1836
- };
2771
+ }
1837
2772
  }
1838
2773
 
1839
2774
  if (!this.is_player(tcol, r) &&
1840
2775
  !this.is_placeholder_in(tcol, r) &&
1841
2776
  !this.is_player_in(tcol, r)) {
1842
2777
  upper_rows[tcol].push(r);
1843
- };
2778
+ }
1844
2779
 
1845
2780
  if (r < min_row) {
1846
2781
  min_row = r;
@@ -1852,7 +2787,9 @@
1852
2787
  return true; //break
1853
2788
  }
1854
2789
 
1855
- upper_rows[tcol].sort();
2790
+ upper_rows[tcol].sort(function(a, b) {
2791
+ return a - b;
2792
+ });
1856
2793
  });
1857
2794
 
1858
2795
  if (!result) { return false; }
@@ -1902,7 +2839,7 @@
1902
2839
  if (valid_rows[0] !== p_top_row) {
1903
2840
  new_row = valid_rows[0] || false;
1904
2841
  }
1905
- }else{
2842
+ } else {
1906
2843
  if (valid_rows[0] !== p_top_row) {
1907
2844
  new_row = this.get_consecutive_numbers_index(
1908
2845
  valid_rows, size_y);
@@ -1926,7 +2863,7 @@
1926
2863
  break;
1927
2864
  }
1928
2865
  first = false;
1929
- }else{
2866
+ } else {
1930
2867
  result = [];
1931
2868
  first = true;
1932
2869
  }
@@ -1942,7 +2879,7 @@
1942
2879
  * Get widgets overlapping with the player.
1943
2880
  *
1944
2881
  * @method get_widgets_overlapped
1945
- * @return {HTMLElements} Returns a jQuery collection of HTMLElements.
2882
+ * @return {jQuery} Returns a jQuery collection of HTMLElements.
1946
2883
  */
1947
2884
  fn.get_widgets_overlapped = function() {
1948
2885
  var $w;
@@ -1956,7 +2893,6 @@
1956
2893
  // if there is a widget in the player position
1957
2894
  if (!this.gridmap[col]) { return true; } //next iteration
1958
2895
  var $w = this.gridmap[col][row];
1959
-
1960
2896
  if (this.is_occupied(col, row) && !this.is_player($w) &&
1961
2897
  $.inArray($w, used) === -1
1962
2898
  ) {
@@ -1976,7 +2912,7 @@
1976
2912
  *
1977
2913
  * @method on_start_overlapping_column
1978
2914
  * @param {Number} col The collided column.
1979
- * @return {HTMLElements} Returns a jQuery collection of HTMLElements.
2915
+ * @return {jQuery} Returns a jQuery collection of HTMLElements.
1980
2916
  */
1981
2917
  fn.on_start_overlapping_column = function(col) {
1982
2918
  this.set_player(col, false);
@@ -1987,8 +2923,8 @@
1987
2923
  * A callback executed when the player begins to collide with a row.
1988
2924
  *
1989
2925
  * @method on_start_overlapping_row
1990
- * @param {Number} col The collided row.
1991
- * @return {HTMLElements} Returns a jQuery collection of HTMLElements.
2926
+ * @param {Number} row The collided row.
2927
+ * @return {jQuery} Returns a jQuery collection of HTMLElements.
1992
2928
  */
1993
2929
  fn.on_start_overlapping_row = function(row) {
1994
2930
  this.set_player(false, row);
@@ -2000,7 +2936,7 @@
2000
2936
  *
2001
2937
  * @method on_stop_overlapping_column
2002
2938
  * @param {Number} col The collided row.
2003
- * @return {HTMLElements} Returns a jQuery collection of HTMLElements.
2939
+ * @return {jQuery} Returns a jQuery collection of HTMLElements.
2004
2940
  */
2005
2941
  fn.on_stop_overlapping_column = function(col) {
2006
2942
  this.set_player(col, false);
@@ -2018,7 +2954,7 @@
2018
2954
  *
2019
2955
  * @method on_stop_overlapping_row
2020
2956
  * @param {Number} row The collided row.
2021
- * @return {HTMLElements} Returns a jQuery collection of HTMLElements.
2957
+ * @return {jQuery} Returns a jQuery collection of HTMLElements.
2022
2958
  */
2023
2959
  fn.on_stop_overlapping_row = function(row) {
2024
2960
  this.set_player(false, row);
@@ -2128,16 +3064,20 @@
2128
3064
  * Move down the specified widget and all below it.
2129
3065
  *
2130
3066
  * @method move_widget_down
2131
- * @param {HTMLElement} $widget The jQuery object representing the widget
3067
+ * @param {jQuery} $widget The jQuery object representing the widget
2132
3068
  * you want to move.
2133
- * @param {Number} The number of cells that the widget has to move.
3069
+ * @param {Number} y_units The number of cells that the widget has to move.
2134
3070
  * @return {Class} Returns the instance of the Gridster Class.
2135
3071
  */
2136
3072
  fn.move_widget_down = function($widget, y_units) {
2137
- var el_grid_data = $widget.coords().grid;
2138
- var actual_row = el_grid_data.row;
2139
- var moved = [];
2140
- var y_diff = y_units;
3073
+ var el_grid_data, actual_row, moved, y_diff;
3074
+
3075
+ if (y_units <= 0) { return false; }
3076
+
3077
+ el_grid_data = $widget.coords().grid;
3078
+ actual_row = el_grid_data.row;
3079
+ moved = [];
3080
+ y_diff = y_units;
2141
3081
 
2142
3082
  if (!$widget) { return false; }
2143
3083
 
@@ -2201,7 +3141,7 @@
2201
3141
  !this.is_placeholder_in(tcol, r)
2202
3142
  ) {
2203
3143
  urc[tcol].push(r);
2204
- }else{
3144
+ } else {
2205
3145
  break;
2206
3146
  }
2207
3147
  }
@@ -2266,7 +3206,7 @@
2266
3206
  *
2267
3207
  * @method widgets_below
2268
3208
  * @param {HTMLElement} $el The jQuery wrapped HTMLElement.
2269
- * @return {HTMLElements} A jQuery collection of HTMLElements.
3209
+ * @return {jQuery} A jQuery collection of HTMLElements.
2270
3210
  */
2271
3211
  fn.widgets_below = function($el) {
2272
3212
  var el_grid_data = $.isPlainObject($el) ? $el : $el.coords().grid;
@@ -2276,17 +3216,15 @@
2276
3216
  var $nexts = $([]);
2277
3217
 
2278
3218
  this.for_each_column_occupied(el_grid_data, function(col) {
2279
- self.for_each_widget_below(col, next_row,
2280
- function(tcol, trow) {
2281
- if (!self.is_player(this) &&
2282
- $.inArray(this, $nexts) === -1) {
2283
- $nexts = $nexts.add(this);
2284
- return true; // break
2285
- }
2286
- });
3219
+ self.for_each_widget_below(col, next_row, function(tcol, trow) {
3220
+ if (!self.is_player(this) && $.inArray(this, $nexts) === -1) {
3221
+ $nexts = $nexts.add(this);
3222
+ return true; // break
3223
+ }
3224
+ });
2287
3225
  });
2288
3226
 
2289
- return this.sort_by_row_asc($nexts);
3227
+ return Gridster.sort_by_row_asc($nexts);
2290
3228
  };
2291
3229
 
2292
3230
 
@@ -2331,6 +3269,7 @@
2331
3269
 
2332
3270
  this.for_each_column_occupied(el_grid_data, function(col) {
2333
3271
  var $w = this.is_widget(col, prev_row);
3272
+
2334
3273
  if (this.is_occupied(col, prev_row) ||
2335
3274
  this.is_player(col, prev_row) ||
2336
3275
  this.is_placeholder_in(col, prev_row) ||
@@ -2345,7 +3284,6 @@
2345
3284
  };
2346
3285
 
2347
3286
 
2348
-
2349
3287
  /**
2350
3288
  * Check if it's possible to move a widget to a specific col/row. It takes
2351
3289
  * into account the dimensions (`size_y` and `size_x` attrs. of the grid
@@ -2356,9 +3294,10 @@
2356
3294
  * the widget.
2357
3295
  * @param {Object} col The col to check.
2358
3296
  * @param {Object} row The row to check.
3297
+ * @param {Number} [max_row] The max row allowed.
2359
3298
  * @return {Boolean} Returns true if all cells are empty, else return false.
2360
3299
  */
2361
- fn.can_move_to = function(widget_grid_data, col, row) {
3300
+ fn.can_move_to = function(widget_grid_data, col, row, max_row) {
2362
3301
  var ga = this.gridmap;
2363
3302
  var $w = widget_grid_data.el;
2364
3303
  var future_wd = {
@@ -2373,7 +3312,11 @@
2373
3312
  var right_col = col + widget_grid_data.size_x - 1;
2374
3313
  if (right_col > this.cols) {
2375
3314
  return false;
2376
- };
3315
+ }
3316
+
3317
+ if (max_row && max_row < row + widget_grid_data.size_y - 1) {
3318
+ return false;
3319
+ }
2377
3320
 
2378
3321
  this.for_each_cell_occupied(future_wd, function(tcol, trow) {
2379
3322
  var $tw = this.is_widget(tcol, trow);
@@ -2432,7 +3375,7 @@
2432
3375
  fn.get_cells_occupied = function(el_grid_data) {
2433
3376
  var cells = { cols: [], rows: []};
2434
3377
  var i;
2435
- if (arguments[1] instanceof jQuery) {
3378
+ if (arguments[1] instanceof $) {
2436
3379
  el_grid_data = arguments[1].coords().grid;
2437
3380
  }
2438
3381
 
@@ -2516,7 +3459,7 @@
2516
3459
 
2517
3460
  var cr, max;
2518
3461
  var action = type + '/' + direction;
2519
- if (arguments[2] instanceof jQuery) {
3462
+ if (arguments[2] instanceof $) {
2520
3463
  var el_grid_data = arguments[2].coords().grid;
2521
3464
  col = el_grid_data.col;
2522
3465
  row = el_grid_data.row;
@@ -2600,26 +3543,23 @@
2600
3543
  fn.get_highest_occupied_cell = function() {
2601
3544
  var r;
2602
3545
  var gm = this.gridmap;
2603
- var rows = [];
3546
+ var rl = gm[1].length;
3547
+ var rows = [], cols = [];
2604
3548
  var row_in_col = [];
2605
3549
  for (var c = gm.length - 1; c >= 1; c--) {
2606
- for (r = gm[c].length - 1; r >= 1; r--) {
3550
+ for (r = rl - 1; r >= 1; r--) {
2607
3551
  if (this.is_widget(c, r)) {
2608
3552
  rows.push(r);
2609
- row_in_col[r] = c;
3553
+ cols.push(c);
2610
3554
  break;
2611
3555
  }
2612
3556
  }
2613
3557
  }
2614
3558
 
2615
- var highest_row = Math.max.apply(Math, rows);
2616
-
2617
- this.highest_occupied_cell = {
2618
- col: row_in_col[highest_row],
2619
- row: highest_row
3559
+ return {
3560
+ col: Math.max.apply(Math, cols),
3561
+ row: Math.max.apply(Math, rows)
2620
3562
  };
2621
-
2622
- return this.highest_occupied_cell;
2623
3563
  };
2624
3564
 
2625
3565
 
@@ -2646,7 +3586,7 @@
2646
3586
  }
2647
3587
 
2648
3588
  return $widgets;
2649
- }
3589
+ };
2650
3590
 
2651
3591
 
2652
3592
  /**
@@ -2655,9 +3595,34 @@
2655
3595
  * @method set_dom_grid_height
2656
3596
  * @return {Object} Returns the instance of the Gridster class.
2657
3597
  */
2658
- fn.set_dom_grid_height = function() {
2659
- var r = this.get_highest_occupied_cell().row;
2660
- this.$el.css('height', r * this.min_widget_height);
3598
+ fn.set_dom_grid_height = function(height) {
3599
+ if (typeof height === 'undefined') {
3600
+ var r = this.get_highest_occupied_cell().row;
3601
+ height = r * this.min_widget_height;
3602
+ }
3603
+
3604
+ this.container_height = height;
3605
+ this.$el.css('height', this.container_height);
3606
+ return this;
3607
+ };
3608
+
3609
+ /**
3610
+ * Set the current width of the parent grid.
3611
+ *
3612
+ * @method set_dom_grid_width
3613
+ * @return {Object} Returns the instance of the Gridster class.
3614
+ */
3615
+ fn.set_dom_grid_width = function(cols) {
3616
+ if (typeof cols === 'undefined') {
3617
+ cols = this.get_highest_occupied_cell().col;
3618
+ }
3619
+
3620
+ var max_cols = (this.options.autogrow_cols ? this.options.max_cols :
3621
+ this.cols);
3622
+
3623
+ cols = Math.min(max_cols, Math.max(cols, this.options.min_cols));
3624
+ this.container_width = cols * this.min_widget_width;
3625
+ this.$el.css('width', this.container_width);
2661
3626
  return this;
2662
3627
  };
2663
3628
 
@@ -2672,16 +3637,16 @@
2672
3637
  */
2673
3638
  fn.generate_stylesheet = function(opts) {
2674
3639
  var styles = '';
2675
- var extra_cells = 10;
2676
- var max_size_y = this.options.max_size_y;
2677
- var max_size_x = this.options.max_size_x;
3640
+ var max_size_x = this.options.max_size_x || this.cols;
3641
+ var max_rows = 0;
3642
+ var max_cols = 0;
2678
3643
  var i;
2679
3644
  var rules;
2680
3645
 
2681
3646
  opts || (opts = {});
2682
3647
  opts.cols || (opts.cols = this.cols);
2683
3648
  opts.rows || (opts.rows = this.rows);
2684
- opts.namespace || (opts.namespace = '');
3649
+ opts.namespace || (opts.namespace = this.options.namespace);
2685
3650
  opts.widget_base_dimensions ||
2686
3651
  (opts.widget_base_dimensions = this.options.widget_base_dimensions);
2687
3652
  opts.widget_margins ||
@@ -2691,42 +3656,45 @@
2691
3656
  opts.min_widget_height = (opts.widget_margins[1] * 2) +
2692
3657
  opts.widget_base_dimensions[1];
2693
3658
 
2694
- var serialized_opts = $.param(opts);
2695
3659
  // don't duplicate stylesheets for the same configuration
3660
+ var serialized_opts = $.param(opts);
2696
3661
  if ($.inArray(serialized_opts, Gridster.generated_stylesheets) >= 0) {
2697
3662
  return false;
2698
3663
  }
2699
3664
 
3665
+ this.generated_stylesheets.push(serialized_opts);
2700
3666
  Gridster.generated_stylesheets.push(serialized_opts);
2701
3667
 
2702
3668
  /* generate CSS styles for cols */
2703
- for (i = opts.cols + extra_cells; i >= 0; i--) {
3669
+ for (i = opts.cols; i >= 0; i--) {
2704
3670
  styles += (opts.namespace + ' [data-col="'+ (i + 1) + '"] { left:' +
2705
3671
  ((i * opts.widget_base_dimensions[0]) +
2706
3672
  (i * opts.widget_margins[0]) +
2707
- ((i + 1) * opts.widget_margins[0])) + 'px;} ');
3673
+ ((i + 1) * opts.widget_margins[0])) + 'px; }\n');
2708
3674
  }
2709
3675
 
2710
3676
  /* generate CSS styles for rows */
2711
- for (i = opts.rows + extra_cells; i >= 0; i--) {
3677
+ for (i = opts.rows; i >= 0; i--) {
2712
3678
  styles += (opts.namespace + ' [data-row="' + (i + 1) + '"] { top:' +
2713
3679
  ((i * opts.widget_base_dimensions[1]) +
2714
3680
  (i * opts.widget_margins[1]) +
2715
- ((i + 1) * opts.widget_margins[1]) ) + 'px;} ');
3681
+ ((i + 1) * opts.widget_margins[1]) ) + 'px; }\n');
2716
3682
  }
2717
3683
 
2718
- for (var y = 1; y <= max_size_y; y++) {
3684
+ for (var y = 1; y <= opts.rows; y++) {
2719
3685
  styles += (opts.namespace + ' [data-sizey="' + y + '"] { height:' +
2720
3686
  (y * opts.widget_base_dimensions[1] +
2721
- (y - 1) * (opts.widget_margins[1] * 2)) + 'px;}');
3687
+ (y - 1) * (opts.widget_margins[1] * 2)) + 'px; }\n');
2722
3688
  }
2723
3689
 
2724
3690
  for (var x = 1; x <= max_size_x; x++) {
2725
3691
  styles += (opts.namespace + ' [data-sizex="' + x + '"] { width:' +
2726
3692
  (x * opts.widget_base_dimensions[0] +
2727
- (x - 1) * (opts.widget_margins[0] * 2)) + 'px;}');
3693
+ (x - 1) * (opts.widget_margins[0] * 2)) + 'px; }\n');
2728
3694
  }
2729
3695
 
3696
+ this.remove_style_tags();
3697
+
2730
3698
  return this.add_style_tag(styles);
2731
3699
  };
2732
3700
 
@@ -2739,18 +3707,39 @@
2739
3707
  * @return {Object} Returns the instance of the Gridster class.
2740
3708
  */
2741
3709
  fn.add_style_tag = function(css) {
2742
- var d = document;
2743
- var tag = d.createElement('style');
3710
+ var d = document;
3711
+ var tag = d.createElement('style');
3712
+
3713
+ d.getElementsByTagName('head')[0].appendChild(tag);
3714
+ tag.setAttribute('type', 'text/css');
3715
+
3716
+ if (tag.styleSheet) {
3717
+ tag.styleSheet.cssText = css;
3718
+ } else {
3719
+ tag.appendChild(document.createTextNode(css));
3720
+ }
3721
+
3722
+ this.$style_tags = this.$style_tags.add(tag);
3723
+
3724
+ return this;
3725
+ };
3726
+
2744
3727
 
2745
- d.getElementsByTagName('head')[0].appendChild(tag);
2746
- tag.setAttribute('type', 'text/css');
3728
+ /**
3729
+ * Remove the style tag with the associated id from the head of the document
3730
+ *
3731
+ * @method remove_style_tag
3732
+ * @return {Object} Returns the instance of the Gridster class.
3733
+ */
3734
+ fn.remove_style_tags = function() {
3735
+ var all_styles = Gridster.generated_stylesheets;
3736
+ var ins_styles = this.generated_stylesheets;
2747
3737
 
2748
- if (tag.styleSheet) {
2749
- tag.styleSheet.cssText = css;
2750
- }else{
2751
- tag.appendChild(document.createTextNode(css));
2752
- }
2753
- return this;
3738
+ this.$style_tags.remove();
3739
+
3740
+ Gridster.generated_stylesheets = $.map(all_styles, function(s) {
3741
+ if ($.inArray(s, ins_styles) === -1) { return s; }
3742
+ });
2754
3743
  };
2755
3744
 
2756
3745
 
@@ -2771,7 +3760,23 @@
2771
3760
  for (col = cols; col > 0; col--) {
2772
3761
  this.gridmap[col] = [];
2773
3762
  for (row = rows; row > 0; row--) {
2774
- var coords = $({
3763
+ this.add_faux_cell(row, col);
3764
+ }
3765
+ }
3766
+ return this;
3767
+ };
3768
+
3769
+
3770
+ /**
3771
+ * Add cell to the faux grid.
3772
+ *
3773
+ * @method add_faux_cell
3774
+ * @param {Number} row The row for the new faux cell.
3775
+ * @param {Number} col The col for the new faux cell.
3776
+ * @return {Object} Returns the instance of the Gridster class.
3777
+ */
3778
+ fn.add_faux_cell = function(row, col) {
3779
+ var coords = $({
2775
3780
  left: this.baseX + ((col - 1) * this.min_widget_width),
2776
3781
  top: this.baseY + (row -1) * this.min_widget_height,
2777
3782
  width: this.min_widget_width,
@@ -2782,10 +3787,67 @@
2782
3787
  original_row: row
2783
3788
  }).coords();
2784
3789
 
2785
- this.gridmap[col][row] = false;
2786
- this.faux_grid.push(coords);
3790
+ if (!$.isArray(this.gridmap[col])) {
3791
+ this.gridmap[col] = [];
3792
+ }
3793
+
3794
+ this.gridmap[col][row] = false;
3795
+ this.faux_grid.push(coords);
3796
+
3797
+ return this;
3798
+ };
3799
+
3800
+
3801
+ /**
3802
+ * Add rows to the faux grid.
3803
+ *
3804
+ * @method add_faux_rows
3805
+ * @param {Number} rows The number of rows you want to add to the faux grid.
3806
+ * @return {Object} Returns the instance of the Gridster class.
3807
+ */
3808
+ fn.add_faux_rows = function(rows) {
3809
+ var actual_rows = this.rows;
3810
+ var max_rows = actual_rows + (rows || 1);
3811
+
3812
+ for (var r = max_rows; r > actual_rows; r--) {
3813
+ for (var c = this.cols; c >= 1; c--) {
3814
+ this.add_faux_cell(r, c);
3815
+ }
3816
+ }
3817
+
3818
+ this.rows = max_rows;
3819
+
3820
+ if (this.options.autogenerate_stylesheet) {
3821
+ this.generate_stylesheet();
3822
+ }
3823
+
3824
+ return this;
3825
+ };
3826
+
3827
+ /**
3828
+ * Add cols to the faux grid.
3829
+ *
3830
+ * @method add_faux_cols
3831
+ * @param {Number} cols The number of cols you want to add to the faux grid.
3832
+ * @return {Object} Returns the instance of the Gridster class.
3833
+ */
3834
+ fn.add_faux_cols = function(cols) {
3835
+ var actual_cols = this.cols;
3836
+ var max_cols = actual_cols + (cols || 1);
3837
+ max_cols = Math.min(max_cols, this.options.max_cols);
3838
+
3839
+ for (var c = actual_cols + 1; c <= max_cols; c++) {
3840
+ for (var r = this.rows; r >= 1; r--) {
3841
+ this.add_faux_cell(r, c);
2787
3842
  }
2788
3843
  }
3844
+
3845
+ this.cols = max_cols;
3846
+
3847
+ if (this.options.autogenerate_stylesheet) {
3848
+ this.generate_stylesheet();
3849
+ }
3850
+
2789
3851
  return this;
2790
3852
  };
2791
3853
 
@@ -2807,7 +3869,6 @@
2807
3869
  left: this.baseX + (coords.data.col -1) * this.min_widget_width,
2808
3870
  top: this.baseY + (coords.data.row -1) * this.min_widget_height
2809
3871
  });
2810
-
2811
3872
  }, this));
2812
3873
 
2813
3874
  return this;
@@ -2821,9 +3882,21 @@
2821
3882
  * @return {Object} Returns the instance of the Gridster class.
2822
3883
  */
2823
3884
  fn.get_widgets_from_DOM = function() {
2824
- this.$widgets.each($.proxy(function(i, widget) {
2825
- this.register_widget($(widget));
3885
+ var widgets_coords = this.$widgets.map($.proxy(function(i, widget) {
3886
+ var $w = $(widget);
3887
+ return this.dom_to_coords($w);
2826
3888
  }, this));
3889
+
3890
+ widgets_coords = Gridster.sort_by_row_and_col_asc(widgets_coords);
3891
+
3892
+ var changes = $(widgets_coords).map($.proxy(function(i, wgd) {
3893
+ return this.register_widget(wgd) || null;
3894
+ }, this));
3895
+
3896
+ if (changes.length) {
3897
+ this.$el.trigger('gridster:positionschanged');
3898
+ }
3899
+
2827
3900
  return this;
2828
3901
  };
2829
3902
 
@@ -2837,32 +3910,33 @@
2837
3910
  */
2838
3911
  fn.generate_grid_and_stylesheet = function() {
2839
3912
  var aw = this.$wrapper.width();
2840
- var ah = this.$wrapper.height();
3913
+ var max_cols = this.options.max_cols;
2841
3914
 
2842
3915
  var cols = Math.floor(aw / this.min_widget_width) +
2843
3916
  this.options.extra_cols;
2844
- var rows = Math.floor(ah / this.min_widget_height) +
2845
- this.options.extra_rows;
2846
3917
 
2847
3918
  var actual_cols = this.$widgets.map(function() {
2848
3919
  return $(this).attr('data-col');
2849
- });
2850
- actual_cols = Array.prototype.slice.call(actual_cols, 0);
2851
- //needed to pass tests with phantomjs
2852
- actual_cols.length || (actual_cols = [0]);
3920
+ }).get();
2853
3921
 
2854
- var actual_rows = this.$widgets.map(function() {
2855
- return $(this).attr('data-row');
2856
- });
2857
- actual_rows = Array.prototype.slice.call(actual_rows, 0);
2858
3922
  //needed to pass tests with phantomjs
2859
- actual_rows.length || (actual_rows = [0]);
3923
+ actual_cols.length || (actual_cols = [0]);
2860
3924
 
2861
3925
  var min_cols = Math.max.apply(Math, actual_cols);
2862
- var min_rows = Math.max.apply(Math, actual_rows);
2863
3926
 
2864
3927
  this.cols = Math.max(min_cols, cols, this.options.min_cols);
2865
- this.rows = Math.max(min_rows, rows, this.options.min_rows);
3928
+
3929
+ if (max_cols !== Infinity && max_cols >= min_cols && max_cols < this.cols) {
3930
+ this.cols = max_cols;
3931
+ }
3932
+
3933
+ // get all rows that could be occupied by the current widgets
3934
+ var max_rows = this.options.extra_rows;
3935
+ this.$widgets.each(function(i, w) {
3936
+ max_rows += (+$(w).attr('data-sizey'));
3937
+ });
3938
+
3939
+ this.rows = Math.max(max_rows, this.options.min_rows);
2866
3940
 
2867
3941
  this.baseX = ($(window).width() - aw) / 2;
2868
3942
  this.baseY = this.$wrapper.offset().top;
@@ -2871,20 +3945,43 @@
2871
3945
  this.generate_stylesheet();
2872
3946
  }
2873
3947
 
2874
- /* more faux rows that needed are created so that there are cells
2875
- * where drag beyond the limits */
2876
3948
  return this.generate_faux_grid(this.rows, this.cols);
2877
3949
  };
2878
3950
 
3951
+ /**
3952
+ * Destroy this gridster by removing any sign of its presence, making it easy to avoid memory leaks
3953
+ *
3954
+ * @method destroy
3955
+ * @param {Boolean} remove If true, remove gridster from DOM.
3956
+ * @return {Object} Returns the instance of the Gridster class.
3957
+ */
3958
+ fn.destroy = function(remove) {
3959
+ this.$el.removeData('gridster');
3960
+
3961
+ // remove bound callback on window resize
3962
+ $(window).unbind('.gridster');
3963
+
3964
+ if (this.drag_api) {
3965
+ this.drag_api.destroy();
3966
+ }
3967
+
3968
+ this.remove_style_tags();
3969
+
3970
+ remove && this.$el.remove();
3971
+
3972
+ return this;
3973
+ };
3974
+
2879
3975
 
2880
3976
  //jQuery adapter
2881
3977
  $.fn.gridster = function(options) {
2882
- return this.each(function() {
2883
- if (!$(this).data('gridster')) {
2884
- $(this).data('gridster', new Gridster( this, options ));
2885
- }
2886
- });
3978
+ return this.each(function() {
3979
+ if (! $(this).data('gridster')) {
3980
+ $(this).data('gridster', new Gridster( this, options ));
3981
+ }
3982
+ });
2887
3983
  };
2888
3984
 
3985
+ return Gridster;
2889
3986
 
2890
- }(jQuery, window, document));
3987
+ }));