dashing-rails 2.4.3 → 2.4.4

Sign up to get free protection for your applications and to get access to all the features.
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
+ }));