gridster-rails 0.2.1 → 0.5.6

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: 674696cba3853465f747492df84085e31e5468d7
4
- data.tar.gz: 35e4e3ef41c060e809e8251e66bf486e8cdbba02
3
+ metadata.gz: e0261906757a321a7e11caeb6d7ac0fe7723c15d
4
+ data.tar.gz: 6e579dbf8f89f425e6daf6b81e228b251e315bf8
5
5
  SHA512:
6
- metadata.gz: 072213c1adade7c2f507dfc95b8f7bd6185c2d11a44377abe6b6aebde174090fa076b423ee2742859388e0d62dda311a798a7c17fa64728a08bdd8a41a266d59
7
- data.tar.gz: fc8736e205c589d16bd04e0f3cb5e2c921630fa363f0c86f579b647e1d21ad572c34278e78f9605eaeb54a912f2a551e772c98712c94537ce8394000ce6c26fc
6
+ metadata.gz: b779e93128b9468e6b506d95fe450accec0ac670026d118ecb7d5e92f1bd38556ec25c1d228fa5e80aa3e59cb3fb62877ebde0c4410d4708423d098372ffba83
7
+ data.tar.gz: 8c7d9f1b7663ec3cc4526d059a5e821b0297cbd5f2903d87d3f4b75aedd9cc13735251465234733d482a6d44d09a62b35bf745b6119abdb1196f2ba13ff344dd
data/README.md CHANGED
@@ -18,7 +18,7 @@ This is [gridster.js](http://gridster.net) GEMified for the Rails >= 3.1 asset p
18
18
 
19
19
  * modify **lib/gridster-rails/version.rb** to match gridster.js version
20
20
 
21
- VERSION = "0.2.1"
21
+ VERSION = "0.5.6"
22
22
 
23
23
  * modify **lib/gridster-rails.rb** to subclass Rails::Engine
24
24
 
@@ -1,5 +1,5 @@
1
1
  module Gridster
2
2
  module Rails
3
- VERSION = "0.2.1"
3
+ VERSION = "0.5.6"
4
4
  end
5
5
  end
@@ -1,8 +1,16 @@
1
- /*! gridster.js - v0.2.1 - 2013-10-28
1
+ /*! gridster.js - v0.5.6 - 2014-09-25
2
2
  * http://gridster.net/
3
- * Copyright (c) 2013 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
+ }));
105
122
 
106
- ;(function($, window, document, undefined){
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
+ }
131
+
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);
@@ -308,24 +336,59 @@
308
336
  };
309
337
 
310
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
+
311
349
  //jQuery adapter
312
350
  $.fn.collision = function(collider, options) {
313
351
  return new Collision( this, collider, options );
314
352
  };
315
353
 
354
+ return Collision;
316
355
 
317
- }(jQuery, window, document));
356
+ }));
318
357
 
319
358
  ;(function(window, undefined) {
320
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
+ */
321
386
 
322
387
  window.delay = function(func, wait) {
323
388
  var args = Array.prototype.slice.call(arguments, 2);
324
389
  return setTimeout(function(){ return func.apply(null, args); }, wait);
325
390
  };
326
391
 
327
-
328
- /* Debounce and throttle functions taken from underscore.js */
329
392
  window.debounce = function(func, wait, immediate) {
330
393
  var timeout;
331
394
  return function() {
@@ -340,7 +403,6 @@
340
403
  };
341
404
  };
342
405
 
343
-
344
406
  window.throttle = function(func, wait) {
345
407
  var context, args, timeout, throttling, more, result;
346
408
  var whenDone = debounce(
@@ -366,7 +428,15 @@
366
428
 
367
429
  })(window);
368
430
 
369
- ;(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($) {
370
440
 
371
441
  var defaults = {
372
442
  items: 'li',
@@ -374,24 +444,30 @@
374
444
  limit: true,
375
445
  offset_left: 0,
376
446
  autoscroll: true,
377
- ignore_dragging: ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON'],
447
+ ignore_dragging: ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON'], // or function
378
448
  handle: null,
379
449
  container_width: 0, // 0 == auto
380
450
  move_element: true,
381
- helper: false // or 'clone'
451
+ helper: false, // or 'clone'
452
+ remove_helper: true
382
453
  // drag: function(e) {},
383
454
  // start : function(e, ui) {},
384
455
  // stop : function(e) {}
385
456
  };
386
457
 
387
458
  var $window = $(window);
459
+ var dir_map = { x : 'left', y : 'top' };
388
460
  var isTouch = !!('ontouchstart' in window);
389
- var pointer_events = {
390
- start: isTouch ? 'touchstart.gridster-draggable' : 'mousedown.gridster-draggable',
391
- move: isTouch ? 'touchmove.gridster-draggable' : 'mousemove.gridster-draggable',
392
- end: isTouch ? 'touchend.gridster-draggable' : 'mouseup.gridster-draggable'
461
+
462
+ var capitalize = function(str) {
463
+ return str.charAt(0).toUpperCase() + str.slice(1);
393
464
  };
394
465
 
466
+ var idCounter = 0;
467
+ var uniqId = function() {
468
+ return ++idCounter + '';
469
+ }
470
+
395
471
  /**
396
472
  * Basic drag implementation for DOM elements inside a container.
397
473
  * Provide start/stop/drag callbacks.
@@ -408,6 +484,9 @@
408
484
  * the mouse must move before dragging should start.
409
485
  * @param {Boolean} [options.limit] Constrains dragging to the width of
410
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.
411
490
  * @param {offset_left} [options.offset_left] Offset added to the item
412
491
  * that is being dragged.
413
492
  * @param {Number} [options.drag] Executes a callback when the mouse is
@@ -420,37 +499,52 @@
420
499
  */
421
500
  function Draggable(el, options) {
422
501
  this.options = $.extend({}, defaults, options);
423
- this.$body = $(document.body);
502
+ this.$document = $(document);
424
503
  this.$container = $(el);
425
504
  this.$dragitems = $(this.options.items, this.$container);
426
505
  this.is_dragging = false;
427
506
  this.player_min_left = 0 + this.options.offset_left;
507
+ this.id = uniqId();
508
+ this.ns = '.gridster-draggable-' + this.id;
428
509
  this.init();
429
510
  }
430
511
 
512
+ Draggable.defaults = defaults;
513
+
431
514
  var fn = Draggable.prototype;
432
515
 
433
516
  fn.init = function() {
434
- this.calculate_positions();
435
- this.$container.css('position', 'relative');
517
+ var pos = this.$container.css('position');
518
+ this.calculate_dimensions();
519
+ this.$container.css('position', pos === 'static' ? 'relative' : pos);
436
520
  this.disabled = false;
437
521
  this.events();
438
522
 
439
- $(window).bind('resize.gridster-draggable',
440
- throttle($.proxy(this.calculate_positions, this), 200));
523
+ $(window).bind(this.nsEvent('resize'),
524
+ throttle($.proxy(this.calculate_dimensions, this), 200));
525
+ };
526
+
527
+ fn.nsEvent = function(ev) {
528
+ return (ev || '') + this.ns;
441
529
  };
442
530
 
443
531
  fn.events = function() {
444
- this.$container.on('selectstart.gridster-draggable',
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'),
445
539
  $.proxy(this.on_select_start, this));
446
540
 
447
- this.$container.on(pointer_events.start, this.options.items,
541
+ this.$container.on(this.pointer_events.start, this.options.items,
448
542
  $.proxy(this.drag_handler, this));
449
543
 
450
- this.$body.on(pointer_events.end, $.proxy(function(e) {
544
+ this.$document.on(this.pointer_events.end, $.proxy(function(e) {
451
545
  this.is_dragging = false;
452
546
  if (this.disabled) { return; }
453
- this.$body.off(pointer_events.move);
547
+ this.$document.off(this.pointer_events.move);
454
548
  if (this.drag_start) {
455
549
  this.on_dragstop(e);
456
550
  }
@@ -464,7 +558,7 @@
464
558
 
465
559
 
466
560
  fn.get_mouse_pos = function(e) {
467
- if (isTouch) {
561
+ if (e.originalEvent && e.originalEvent.touches) {
468
562
  var oe = e.originalEvent;
469
563
  e = oe.touches.length ? oe.touches[0] : oe.changedTouches[0];
470
564
  }
@@ -483,9 +577,10 @@
483
577
  mouse_actual_pos.left - this.mouse_init_pos.left);
484
578
  var diff_y = Math.round(mouse_actual_pos.top - this.mouse_init_pos.top);
485
579
 
486
- var left = Math.round(this.el_init_offset.left + diff_x - this.baseX);
487
- var top = Math.round(
488
- 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);
489
584
 
490
585
  if (this.options.limit) {
491
586
  if (left > this.player_max_left) {
@@ -503,8 +598,8 @@
503
598
  pointer: {
504
599
  left: mouse_actual_pos.left,
505
600
  top: mouse_actual_pos.top,
506
- diff_left: diff_x,
507
- diff_top: diff_y + this.scrollOffset
601
+ diff_left: diff_x + ($(window).scrollLeft() - this.win_offset_x),
602
+ diff_top: diff_y + ($(window).scrollTop() - this.win_offset_y)
508
603
  }
509
604
  };
510
605
  };
@@ -519,47 +614,75 @@
519
614
  };
520
615
 
521
616
 
522
- fn.manage_scroll = function(data) {
523
- /* scroll document */
524
- var nextScrollTop;
525
- var scrollTop = $window.scrollTop();
526
- var min_window_y = scrollTop;
527
- var max_window_y = min_window_y + this.window_height;
528
-
529
- var mouse_down_zone = max_window_y - 50;
530
- var mouse_up_zone = min_window_y + 50;
531
-
532
- var abs_mouse_left = data.pointer.left;
533
- var abs_mouse_top = min_window_y + data.pointer.top;
534
-
535
- var max_player_y = (this.doc_height - this.window_height +
536
- this.player_height);
537
-
538
- if (abs_mouse_top >= mouse_down_zone) {
539
- nextScrollTop = scrollTop + 30;
540
- if (nextScrollTop < max_player_y) {
541
- $window.scrollTop(nextScrollTop);
542
- this.scrollOffset = this.scrollOffset + 30;
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();
638
+
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;
643
+
644
+ var mouse_next_zone = max_window_pos - area_size; // down/right
645
+ var mouse_prev_zone = min_window_pos + area_size; // up/left
646
+
647
+ var abs_mouse_pos = min_window_pos + data.pointer[dir_prop];
648
+
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;
543
656
  }
544
657
  }
545
658
 
546
- if (abs_mouse_top <= mouse_up_zone) {
547
- nextScrollTop = scrollTop - 30;
548
- if (nextScrollTop > 0) {
549
- $window.scrollTop(nextScrollTop);
550
- 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;
551
664
  }
552
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);
553
674
  };
554
675
 
555
676
 
556
- fn.calculate_positions = function(e) {
677
+ fn.calculate_dimensions = function(e) {
557
678
  this.window_height = $window.height();
679
+ this.window_width = $window.width();
558
680
  };
559
681
 
560
682
 
561
683
  fn.drag_handler = function(e) {
562
684
  var node = e.target.nodeName;
685
+ // skip if drag is disabled, or click was not done with the mouse primary button
563
686
  if (this.disabled || e.which !== 1 && !isTouch) {
564
687
  return;
565
688
  }
@@ -576,7 +699,7 @@
576
699
  this.mouse_init_pos = this.get_mouse_pos(e);
577
700
  this.offsetY = this.mouse_init_pos.top - this.el_init_pos.top;
578
701
 
579
- this.$body.on(pointer_events.move, function(mme) {
702
+ this.$document.on(this.pointer_events.move, function(mme) {
580
703
  var mouse_actual_pos = self.get_mouse_pos(mme);
581
704
  var diff_x = Math.abs(
582
705
  mouse_actual_pos.left - self.mouse_init_pos.left);
@@ -614,7 +737,7 @@
614
737
  var offset = this.$container.offset();
615
738
  this.baseX = Math.round(offset.left);
616
739
  this.baseY = Math.round(offset.top);
617
- this.doc_height = $(document).height();
740
+ this.initial_container_width = this.options.container_width || this.$container.width();
618
741
 
619
742
  if (this.options.helper === 'clone') {
620
743
  this.$helper = this.$player.clone()
@@ -624,14 +747,15 @@
624
747
  this.helper = false;
625
748
  }
626
749
 
627
- this.scrollOffset = 0;
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;
628
754
  this.el_init_offset = this.$player.offset();
629
755
  this.player_width = this.$player.width();
630
756
  this.player_height = this.$player.height();
631
757
 
632
- var container_width = this.options.container_width || this.$container.width();
633
- this.player_max_left = (container_width - this.player_width +
634
- this.options.offset_left);
758
+ this.set_limits(this.options.container_width);
635
759
 
636
760
  if (this.options.start) {
637
761
  this.options.start.call(this.$player, e, this.get_drag_data(e));
@@ -673,7 +797,7 @@
673
797
  this.options.stop.call(this.$player, e, data);
674
798
  }
675
799
 
676
- if (this.helper) {
800
+ if (this.helper && this.options.remove_helper) {
677
801
  this.$helper.remove();
678
802
  }
679
803
 
@@ -701,9 +825,9 @@
701
825
  fn.destroy = function() {
702
826
  this.disable();
703
827
 
704
- this.$container.off('.gridster-draggable');
705
- this.$body.off('.gridster-draggable');
706
- $(window).off('.gridster-draggable');
828
+ this.$container.off(this.ns);
829
+ this.$document.off(this.ns);
830
+ $(window).off(this.ns);
707
831
 
708
832
  $.removeData(this.$container, 'drag');
709
833
  };
@@ -713,6 +837,10 @@
713
837
  return !$(event.target).is(this.options.handle);
714
838
  }
715
839
 
840
+ if ($.isFunction(this.options.ignore_dragging)) {
841
+ return this.options.ignore_dragging(event);
842
+ }
843
+
716
844
  return $(event.target).is(this.options.ignore_dragging.join(', '));
717
845
  };
718
846
 
@@ -721,10 +849,20 @@
721
849
  return new Draggable(this, options);
722
850
  };
723
851
 
852
+ return Draggable;
853
+
854
+ }));
724
855
 
725
- }(jQuery, window, document));
856
+ ;(function(root, factory) {
726
857
 
727
- ;(function($, window, document, undefined) {
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
+ }
864
+
865
+ }(this, function($, Draggable, Collision) {
728
866
 
729
867
  var defaults = {
730
868
  namespace: '',
@@ -734,11 +872,13 @@
734
872
  extra_rows: 0,
735
873
  extra_cols: 0,
736
874
  min_cols: 1,
737
- max_cols: null,
875
+ max_cols: Infinity,
738
876
  min_rows: 15,
739
877
  max_size_x: false,
878
+ autogrow_cols: false,
740
879
  autogenerate_stylesheet: true,
741
880
  avoid_overlapped_widgets: true,
881
+ auto_init: true,
742
882
  serialize_params: function($w, wgd) {
743
883
  return {
744
884
  col: wgd.col,
@@ -750,14 +890,16 @@
750
890
  collision: {},
751
891
  draggable: {
752
892
  items: '.gs-w',
753
- distance: 4
893
+ distance: 4,
894
+ ignore_dragging: Draggable.defaults.ignore_dragging.slice(0)
754
895
  },
755
896
  resize: {
756
897
  enabled: false,
757
- axes: ['x', 'y', 'both'],
898
+ axes: ['both'],
758
899
  handle_append_to: '',
759
900
  handle_class: 'gs-resize-handle',
760
- max_size: [Infinity, Infinity]
901
+ max_size: [Infinity, Infinity],
902
+ min_size: [1, 1]
761
903
  }
762
904
  };
763
905
 
@@ -796,6 +938,8 @@
796
938
  * @param {Boolean} [options.avoid_overlapped_widgets] Avoid that widgets loaded
797
939
  * from the DOM can be overlapped. It is helpful if the positions were
798
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.
799
943
  * @param {Function} [options.serialize_params] Return the data you want
800
944
  * for each widget in the serialization. Two arguments are passed:
801
945
  * `$w`: the jQuery wrapped HTMLElement, and `wgd`: the grid
@@ -806,8 +950,10 @@
806
950
  * @param {Object} [options.draggable] An Object with all options for
807
951
  * Draggable class you want to overwrite. See Draggable docs for more
808
952
  * info.
809
- * @param {Object} [options.resize] An Object with resize config
810
- * options.
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.
811
957
  * @param {Boolean} [options.resize.enabled] Set to true to enable
812
958
  * resizing.
813
959
  * @param {Array} [options.resize.axes] Axes in which widgets can be
@@ -819,6 +965,9 @@
819
965
  * @param {Array} [options.resize.max_size] Limit widget dimensions
820
966
  * when resizing. Array values should be integers:
821
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]`
822
971
  * @param {Function} [options.resize.start] Function executed
823
972
  * when resizing starts.
824
973
  * @param {Function} [otions.resize.resize] Function executed
@@ -829,7 +978,7 @@
829
978
  * @constructor
830
979
  */
831
980
  function Gridster(el, options) {
832
- this.options = $.extend(true, defaults, options);
981
+ this.options = $.extend(true, {}, defaults, options);
833
982
  this.$el = $(el);
834
983
  this.$wrapper = this.$el.parent();
835
984
  this.$widgets = this.$el.children(
@@ -842,13 +991,103 @@
842
991
  this.min_widget_height = (this.options.widget_margins[1] * 2) +
843
992
  this.options.widget_base_dimensions[1];
844
993
 
994
+ this.generated_stylesheets = [];
845
995
  this.$style_tags = $([]);
846
996
 
847
- this.init();
997
+ this.options.auto_init && this.init();
848
998
  }
849
999
 
1000
+ Gridster.defaults = defaults;
850
1001
  Gridster.generated_stylesheets = [];
851
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
+
852
1091
  var fn = Gridster.prototype;
853
1092
 
854
1093
  fn.init = function() {
@@ -856,6 +1095,7 @@
856
1095
  this.generate_grid_and_stylesheet();
857
1096
  this.get_widgets_from_DOM();
858
1097
  this.set_dom_grid_height();
1098
+ this.set_dom_grid_width();
859
1099
  this.$wrapper.addClass('ready');
860
1100
  this.draggable();
861
1101
  this.options.resize.enabled && this.resizable();
@@ -928,20 +1168,23 @@
928
1168
  * @param {Number} [col] The column the widget should start in.
929
1169
  * @param {Number} [row] The row the widget should start in.
930
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.
931
1172
  * @return {HTMLElement} Returns the jQuery wrapped HTMLElement representing.
932
1173
  * the widget that was just created.
933
1174
  */
934
- fn.add_widget = function(html, size_x, size_y, col, row, max_size) {
1175
+ fn.add_widget = function(html, size_x, size_y, col, row, max_size, min_size) {
935
1176
  var pos;
936
1177
  size_x || (size_x = 1);
937
1178
  size_y || (size_y = 1);
938
1179
 
939
1180
  if (!col & !row) {
940
1181
  pos = this.next_position(size_x, size_y);
941
- }else{
1182
+ } else {
942
1183
  pos = {
943
1184
  col: col,
944
- row: row
1185
+ row: row,
1186
+ size_x: size_x,
1187
+ size_y: size_y
945
1188
  };
946
1189
 
947
1190
  this.empty_cells(col, row, size_x, size_y);
@@ -965,12 +1208,42 @@
965
1208
  this.set_widget_max_size($w, max_size);
966
1209
  }
967
1210
 
1211
+ if (min_size) {
1212
+ this.set_widget_min_size($w, min_size);
1213
+ }
1214
+
1215
+ this.set_dom_grid_width();
968
1216
  this.set_dom_grid_height();
969
1217
 
1218
+ this.drag_api.set_limits(this.cols * this.min_widget_width);
1219
+
970
1220
  return $w.fadeIn();
971
1221
  };
972
1222
 
973
1223
 
1224
+ /**
1225
+ * Change widget size limits.
1226
+ *
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.
1232
+ */
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
+
974
1247
  /**
975
1248
  * Change widget size limits.
976
1249
  *
@@ -1017,38 +1290,36 @@
1017
1290
  * @param {HTMLElement} $widget The jQuery wrapped HTMLElement
1018
1291
  * representing the widget.
1019
1292
  * @param {Number} size_x The number of columns that will occupy the widget.
1020
- * @param {Number} size_y The number of rows that will occupy the widget.
1021
- * @param {Boolean} [reposition] Set to false to not move the widget to
1022
- * the left if there is insufficient space on the right.
1023
1293
  * By default <code>size_x</code> is limited to the space available from
1024
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.
1025
1296
  * @param {Function} [callback] Function executed when the widget is removed.
1026
1297
  * @return {HTMLElement} Returns $widget.
1027
1298
  */
1028
- fn.resize_widget = function($widget, size_x, size_y, reposition, callback) {
1299
+ fn.resize_widget = function($widget, size_x, size_y, callback) {
1029
1300
  var wgd = $widget.coords().grid;
1030
- reposition !== false && (reposition = true);
1031
- size_x || (size_x = wgd.size_x);
1032
- size_y || (size_y = wgd.size_y);
1033
-
1034
- if (size_x > this.cols) {
1035
- size_x = this.cols;
1036
- }
1037
-
1301
+ var col = wgd.col;
1302
+ var max_cols = this.options.max_cols;
1038
1303
  var old_size_y = wgd.size_y;
1039
1304
  var old_col = wgd.col;
1040
1305
  var new_col = old_col;
1041
1306
 
1042
- if (reposition && old_col + size_x - 1 > this.cols) {
1043
- var diff = old_col + (size_x - 1) - this.cols;
1044
- var c = old_col - diff;
1045
- new_col = Math.max(1, c);
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);
1046
1312
  }
1047
1313
 
1048
1314
  if (size_y > old_size_y) {
1049
1315
  this.add_faux_rows(Math.max(size_y - old_size_y, 0));
1050
1316
  }
1051
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
+
1052
1323
  var new_grid_data = {
1053
1324
  col: new_col,
1054
1325
  row: wgd.row,
@@ -1059,6 +1330,7 @@
1059
1330
  this.mutate_widget_in_gridmap($widget, wgd, new_grid_data);
1060
1331
 
1061
1332
  this.set_dom_grid_height();
1333
+ this.set_dom_grid_width();
1062
1334
 
1063
1335
  if (callback) {
1064
1336
  callback.call(this, new_grid_data.size_x, new_grid_data.size_y);
@@ -1200,7 +1472,7 @@
1200
1472
 
1201
1473
  $nexts.not($exclude).each($.proxy(function(i, w) {
1202
1474
  var wgd = $(w).coords().grid;
1203
- if (!(wgd.row <= (row + size_y - 1))) { return; }
1475
+ if ( !(wgd.row <= (row + size_y - 1))) { return; }
1204
1476
  var diff = (row + size_y) - wgd.row;
1205
1477
  this.move_widget_down($(w), diff);
1206
1478
  }, this));
@@ -1280,7 +1552,7 @@
1280
1552
  }
1281
1553
 
1282
1554
  if (valid_pos.length) {
1283
- return this.sort_by_row_and_col_asc(valid_pos)[0];
1555
+ return Gridster.sort_by_row_and_col_asc(valid_pos)[0];
1284
1556
  }
1285
1557
  return false;
1286
1558
  };
@@ -1297,7 +1569,7 @@
1297
1569
  * @return {Class} Returns the instance of the Gridster Class.
1298
1570
  */
1299
1571
  fn.remove_widget = function(el, silent, callback) {
1300
- var $el = el instanceof jQuery ? el : $(el);
1572
+ var $el = el instanceof $ ? el : $(el);
1301
1573
  var wgd = $el.coords().grid;
1302
1574
 
1303
1575
  // if silent is a function assume it's a callback
@@ -1361,13 +1633,11 @@
1361
1633
  */
1362
1634
  fn.serialize = function($widgets) {
1363
1635
  $widgets || ($widgets = this.$widgets);
1364
- var result = [];
1365
- $widgets.each($.proxy(function(i, widget) {
1366
- result.push(this.options.serialize_params(
1367
- $(widget), $(widget).coords().grid ) );
1368
- }, this));
1369
1636
 
1370
- 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();
1371
1641
  };
1372
1642
 
1373
1643
 
@@ -1385,22 +1655,49 @@
1385
1655
 
1386
1656
 
1387
1657
  /**
1388
- * 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
1389
1680
  * mapped array of positions.
1390
1681
  *
1391
1682
  * @method register_widget
1392
- * @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.
1393
1687
  */
1394
1688
  fn.register_widget = function($el) {
1395
- var wgd = {
1396
- 'col': parseInt($el.attr('data-col'), 10),
1397
- 'row': parseInt($el.attr('data-row'), 10),
1398
- 'size_x': parseInt($el.attr('data-sizex'), 10),
1399
- 'size_y': parseInt($el.attr('data-sizey'), 10),
1400
- 'max_size_x': parseInt($el.attr('data-max-sizex'), 10) || false,
1401
- 'max_size_y': parseInt($el.attr('data-max-sizey'), 10) || false,
1402
- 'el': $el
1403
- };
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);
1693
+
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
+ }
1404
1701
 
1405
1702
  if (this.options.avoid_overlapped_widgets &&
1406
1703
  !this.can_move_to(
@@ -1413,6 +1710,7 @@
1413
1710
  'data-sizex': wgd.size_x,
1414
1711
  'data-sizey': wgd.size_y
1415
1712
  });
1713
+ posChanged = true;
1416
1714
  }
1417
1715
 
1418
1716
  // attach Coord object to player data-coord attribute
@@ -1424,7 +1722,7 @@
1424
1722
 
1425
1723
  this.options.resize.enabled && this.add_resize_handle($el);
1426
1724
 
1427
- return this;
1725
+ return posChanged;
1428
1726
  };
1429
1727
 
1430
1728
 
@@ -1495,9 +1793,9 @@
1495
1793
  var self = this;
1496
1794
  var draggable_options = $.extend(true, {}, this.options.draggable, {
1497
1795
  offset_left: this.options.widget_margins[0],
1498
- container_width: this.container_width,
1499
- ignore_dragging: ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON',
1500
- '.' + this.options.resize.handle_class],
1796
+ offset_top: this.options.widget_margins[1],
1797
+ container_width: this.cols * this.min_widget_width,
1798
+ limit: true,
1501
1799
  start: function(event, ui) {
1502
1800
  self.$widgets.filter('.player-revert')
1503
1801
  .removeClass('player-revert');
@@ -1537,6 +1835,8 @@
1537
1835
  offset_left: this.options.widget_margins[0],
1538
1836
  container_width: this.container_width,
1539
1837
  move_element: false,
1838
+ resize: true,
1839
+ limit: this.options.autogrow_cols ? false : true,
1540
1840
  start: $.proxy(this.on_start_resize, this),
1541
1841
  stop: $.proxy(function(event, ui) {
1542
1842
  delay($.proxy(function() {
@@ -1565,6 +1865,12 @@
1565
1865
  this.resize_handle_tpl = $.map(axes, function(type) {
1566
1866
  return handle_tpl.replace('{type}', type);
1567
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
+
1568
1874
  return this;
1569
1875
  };
1570
1876
 
@@ -1579,13 +1885,23 @@
1579
1885
  fn.on_start_drag = function(event, ui) {
1580
1886
  this.$helper.add(this.$player).add(this.$wrapper).addClass('dragging');
1581
1887
 
1888
+ this.highest_col = this.get_highest_occupied_cell().col;
1889
+
1582
1890
  this.$player.addClass('player');
1583
1891
  this.player_grid_data = this.$player.coords().grid;
1584
1892
  this.placeholder_grid_data = $.extend({}, this.player_grid_data);
1585
1893
 
1586
- //set new grid height along the dragging period
1587
- this.$el.css('height', this.$el.height() +
1588
- (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
+ }
1589
1905
 
1590
1906
  var colliders = this.faux_grid;
1591
1907
  var coords = this.$player.data('coords').coords;
@@ -1636,18 +1952,30 @@
1636
1952
  top: ui.position.top + this.baseY
1637
1953
  };
1638
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
+
1639
1970
  this.colliders_data = this.collision_api.get_closest_colliders(
1640
1971
  abs_offset);
1641
1972
 
1642
1973
  this.on_overlapped_column_change(
1643
- this.on_start_overlapping_column,
1644
- this.on_stop_overlapping_column
1645
- );
1974
+ this.on_start_overlapping_column, this.on_stop_overlapping_column);
1646
1975
 
1647
1976
  this.on_overlapped_row_change(
1648
- this.on_start_overlapping_row,
1649
- this.on_stop_overlapping_row
1650
- );
1977
+ this.on_start_overlapping_row, this.on_stop_overlapping_row);
1978
+
1651
1979
 
1652
1980
  if (this.helper && this.$player) {
1653
1981
  this.$player.css({
@@ -1661,6 +1989,7 @@
1661
1989
  }
1662
1990
  };
1663
1991
 
1992
+
1664
1993
  /**
1665
1994
  * This function is executed when the player stops being dragged.
1666
1995
  *
@@ -1720,8 +2049,12 @@
1720
2049
  this.cells_occupied_by_player = {};
1721
2050
 
1722
2051
  this.set_dom_grid_height();
1723
- };
2052
+ this.set_dom_grid_width();
1724
2053
 
2054
+ if (this.options.autogrow_cols) {
2055
+ this.drag_api.set_limits(this.cols * this.min_widget_width);
2056
+ }
2057
+ };
1725
2058
 
1726
2059
 
1727
2060
  /**
@@ -1739,13 +2072,25 @@
1739
2072
  this.resize_initial_height = this.resize_coords.coords.height;
1740
2073
  this.resize_initial_sizex = this.resize_coords.grid.size_x;
1741
2074
  this.resize_initial_sizey = this.resize_coords.grid.size_y;
2075
+ this.resize_initial_col = this.resize_coords.grid.col;
1742
2076
  this.resize_last_sizex = this.resize_initial_sizex;
1743
2077
  this.resize_last_sizey = this.resize_initial_sizey;
2078
+
1744
2079
  this.resize_max_size_x = Math.min(this.resize_wgd.max_size_x ||
1745
- this.options.resize.max_size[0], this.cols - this.resize_wgd.col + 1);
2080
+ this.options.resize.max_size[0],
2081
+ this.options.max_cols - this.resize_initial_col + 1);
1746
2082
  this.resize_max_size_y = this.resize_wgd.max_size_y ||
1747
2083
  this.options.resize.max_size[1];
1748
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
+
1749
2094
  this.resize_dir = {
1750
2095
  right: ui.$player.is('.' + this.resize_handle_class + '-x'),
1751
2096
  bottom: ui.$player.is('.' + this.resize_handle_class + '-y')
@@ -1769,9 +2114,11 @@
1769
2114
 
1770
2115
  this.$resized_widget.addClass('resizing');
1771
2116
 
1772
- if (this.options.resize.start) {
2117
+ if (this.options.resize.start) {
1773
2118
  this.options.resize.start.call(this, event, ui, this.$resized_widget);
1774
2119
  }
2120
+
2121
+ this.$el.trigger('gridster:resizestart');
1775
2122
  };
1776
2123
 
1777
2124
 
@@ -1797,13 +2144,22 @@
1797
2144
  'min-width': '',
1798
2145
  'min-height': ''
1799
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');
1800
2153
  }, this), 300);
1801
2154
 
1802
- if (this.options.resize.stop) {
1803
- this.options.resize.stop.call(this, event, ui, this.$resized_widget);
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);
1804
2159
  }
1805
2160
  };
1806
2161
 
2162
+
1807
2163
  /**
1808
2164
  * This function is executed when a widget is being resized.
1809
2165
  *
@@ -1816,28 +2172,36 @@
1816
2172
  var rel_y = (ui.pointer.diff_top);
1817
2173
  var wbd_x = this.options.widget_base_dimensions[0];
1818
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;
1819
2183
  var max_width = Infinity;
1820
2184
  var max_height = Infinity;
1821
2185
 
1822
- var inc_units_x = Math.ceil((rel_x /
1823
- (this.options.widget_base_dimensions[0] +
1824
- this.options.widget_margins[0] * 2)) - 0.2);
1825
-
1826
- var inc_units_y = Math.ceil((rel_y /
1827
- (this.options.widget_base_dimensions[1] +
1828
- this.options.widget_margins[1] * 2)) - 0.2);
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);
1829
2188
 
1830
2189
  var size_x = Math.max(1, this.resize_initial_sizex + inc_units_x);
1831
2190
  var size_y = Math.max(1, this.resize_initial_sizey + inc_units_y);
1832
2191
 
1833
- size_x = Math.min(size_x, this.resize_max_size_x);
1834
- max_width = (this.resize_max_size_x * wbd_x) +
1835
- ((size_x - 1) * this.options.widget_margins[0] * 2);
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);
1836
2195
 
1837
- size_y = Math.min(size_y, this.resize_max_size_y);
1838
- max_height = (this.resize_max_size_y * wbd_y) +
1839
- ((size_y - 1) * this.options.widget_margins[1] * 2);
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);
1840
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);
1841
2205
 
1842
2206
  if (this.resize_dir.right) {
1843
2207
  size_y = this.resize_initial_sizey;
@@ -1845,18 +2209,30 @@
1845
2209
  size_x = this.resize_initial_sizex;
1846
2210
  }
1847
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
+
1848
2223
  var css_props = {};
1849
- !this.resize_dir.bottom && (css_props.width = Math.min(
1850
- this.resize_initial_width + rel_x, max_width));
1851
- !this.resize_dir.right && (css_props.height = Math.min(
1852
- this.resize_initial_height + rel_y, max_height));
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));
1853
2228
 
1854
2229
  this.$resized_widget.css(css_props);
1855
2230
 
1856
2231
  if (size_x !== this.resize_last_sizex ||
1857
2232
  size_y !== this.resize_last_sizey) {
1858
2233
 
1859
- this.resize_widget(this.$resized_widget, size_x, size_y, false);
2234
+ this.resize_widget(this.$resized_widget, size_x, size_y);
2235
+ this.set_dom_grid_width(this.cols);
1860
2236
 
1861
2237
  this.$resize_preview_holder.css({
1862
2238
  'width': '',
@@ -1872,6 +2248,8 @@
1872
2248
  this.options.resize.resize.call(this, event, ui, this.$resized_widget);
1873
2249
  }
1874
2250
 
2251
+ this.$el.trigger('gridster:resize');
2252
+
1875
2253
  this.resize_last_sizex = size_x;
1876
2254
  this.resize_last_sizey = size_y;
1877
2255
  };
@@ -2028,7 +2406,7 @@
2028
2406
  if (this.can_go_widget_up(wgd)) {
2029
2407
  $widgets_can_go_up = $widgets_can_go_up.add($w);
2030
2408
  wgd_can_go_up.push(wgd);
2031
- }else{
2409
+ } else {
2032
2410
  wgd_can_not_go_up.push(wgd);
2033
2411
  }
2034
2412
  }, this));
@@ -2036,96 +2414,12 @@
2036
2414
  $widgets_can_not_go_up = $widgets.not($widgets_can_go_up);
2037
2415
 
2038
2416
  return {
2039
- can_go_up: this.sort_by_row_asc(wgd_can_go_up),
2040
- 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)
2041
2419
  };
2042
2420
  };
2043
2421
 
2044
2422
 
2045
- /**
2046
- * Sorts an Array of grid coords objects (representing the grid coords of
2047
- * each widget) in ascending way.
2048
- *
2049
- * @method sort_by_row_asc
2050
- * @param {Array} widgets Array of grid coords objects
2051
- * @return {Array} Returns the array sorted.
2052
- */
2053
- fn.sort_by_row_asc = function(widgets) {
2054
- widgets = widgets.sort(function(a, b) {
2055
- if (!a.row) {
2056
- a = $(a).coords().grid;
2057
- b = $(b).coords().grid;
2058
- }
2059
-
2060
- if (a.row > b.row) {
2061
- return 1;
2062
- }
2063
- return -1;
2064
- });
2065
-
2066
- return widgets;
2067
- };
2068
-
2069
-
2070
- /**
2071
- * Sorts an Array of grid coords objects (representing the grid coords of
2072
- * each widget) placing first the empty cells upper left.
2073
- *
2074
- * @method sort_by_row_and_col_asc
2075
- * @param {Array} widgets Array of grid coords objects
2076
- * @return {Array} Returns the array sorted.
2077
- */
2078
- fn.sort_by_row_and_col_asc = function(widgets) {
2079
- widgets = widgets.sort(function(a, b) {
2080
- if (a.row > b.row || a.row === b.row && a.col > b.col) {
2081
- return 1;
2082
- }
2083
- return -1;
2084
- });
2085
-
2086
- return widgets;
2087
- };
2088
-
2089
-
2090
- /**
2091
- * Sorts an Array of grid coords objects by column (representing the grid
2092
- * coords of each widget) in ascending way.
2093
- *
2094
- * @method sort_by_col_asc
2095
- * @param {Array} widgets Array of grid coords objects
2096
- * @return {Array} Returns the array sorted.
2097
- */
2098
- fn.sort_by_col_asc = function(widgets) {
2099
- widgets = widgets.sort(function(a, b) {
2100
- if (a.col > b.col) {
2101
- return 1;
2102
- }
2103
- return -1;
2104
- });
2105
-
2106
- return widgets;
2107
- };
2108
-
2109
-
2110
- /**
2111
- * Sorts an Array of grid coords objects (representing the grid coords of
2112
- * each widget) in descending way.
2113
- *
2114
- * @method sort_by_row_desc
2115
- * @param {Array} widgets Array of grid coords objects
2116
- * @return {Array} Returns the array sorted.
2117
- */
2118
- fn.sort_by_row_desc = function(widgets) {
2119
- widgets = widgets.sort(function(a, b) {
2120
- if (a.row + a.size_y < b.row + b.size_y) {
2121
- return 1;
2122
- }
2123
- return -1;
2124
- });
2125
- return widgets;
2126
- };
2127
-
2128
-
2129
2423
  /**
2130
2424
  * Sorts an Array of grid coords objects (representing the grid coords of
2131
2425
  * each widget) in descending way.
@@ -2240,14 +2534,14 @@
2240
2534
  */
2241
2535
  fn.is_empty = function(col, row) {
2242
2536
  if (typeof this.gridmap[col] !== 'undefined') {
2243
- if(typeof this.gridmap[col][row] !== 'undefined' &&
2244
- this.gridmap[col][row] === false
2245
- ) {
2246
- return true;
2247
- }
2248
- return false;
2249
- }
2250
- return true;
2537
+ if(typeof this.gridmap[col][row] !== 'undefined' &&
2538
+ this.gridmap[col][row] === false
2539
+ ) {
2540
+ return true;
2541
+ }
2542
+ return false;
2543
+ }
2544
+ return true;
2251
2545
  };
2252
2546
 
2253
2547
 
@@ -2426,7 +2720,7 @@
2426
2720
  ) {
2427
2721
  upper_rows[tcol].push(r);
2428
2722
  min_row = r < min_row ? r : min_row;
2429
- }else{
2723
+ } else {
2430
2724
  break;
2431
2725
  }
2432
2726
  }
@@ -2545,7 +2839,7 @@
2545
2839
  if (valid_rows[0] !== p_top_row) {
2546
2840
  new_row = valid_rows[0] || false;
2547
2841
  }
2548
- }else{
2842
+ } else {
2549
2843
  if (valid_rows[0] !== p_top_row) {
2550
2844
  new_row = this.get_consecutive_numbers_index(
2551
2845
  valid_rows, size_y);
@@ -2569,7 +2863,7 @@
2569
2863
  break;
2570
2864
  }
2571
2865
  first = false;
2572
- }else{
2866
+ } else {
2573
2867
  result = [];
2574
2868
  first = true;
2575
2869
  }
@@ -2847,7 +3141,7 @@
2847
3141
  !this.is_placeholder_in(tcol, r)
2848
3142
  ) {
2849
3143
  urc[tcol].push(r);
2850
- }else{
3144
+ } else {
2851
3145
  break;
2852
3146
  }
2853
3147
  }
@@ -2930,7 +3224,7 @@
2930
3224
  });
2931
3225
  });
2932
3226
 
2933
- return this.sort_by_row_asc($nexts);
3227
+ return Gridster.sort_by_row_asc($nexts);
2934
3228
  };
2935
3229
 
2936
3230
 
@@ -2990,7 +3284,6 @@
2990
3284
  };
2991
3285
 
2992
3286
 
2993
-
2994
3287
  /**
2995
3288
  * Check if it's possible to move a widget to a specific col/row. It takes
2996
3289
  * into account the dimensions (`size_y` and `size_x` attrs. of the grid
@@ -3082,7 +3375,7 @@
3082
3375
  fn.get_cells_occupied = function(el_grid_data) {
3083
3376
  var cells = { cols: [], rows: []};
3084
3377
  var i;
3085
- if (arguments[1] instanceof jQuery) {
3378
+ if (arguments[1] instanceof $) {
3086
3379
  el_grid_data = arguments[1].coords().grid;
3087
3380
  }
3088
3381
 
@@ -3166,7 +3459,7 @@
3166
3459
 
3167
3460
  var cr, max;
3168
3461
  var action = type + '/' + direction;
3169
- if (arguments[2] instanceof jQuery) {
3462
+ if (arguments[2] instanceof $) {
3170
3463
  var el_grid_data = arguments[2].coords().grid;
3171
3464
  col = el_grid_data.col;
3172
3465
  row = el_grid_data.row;
@@ -3250,26 +3543,23 @@
3250
3543
  fn.get_highest_occupied_cell = function() {
3251
3544
  var r;
3252
3545
  var gm = this.gridmap;
3253
- var rows = [];
3546
+ var rl = gm[1].length;
3547
+ var rows = [], cols = [];
3254
3548
  var row_in_col = [];
3255
3549
  for (var c = gm.length - 1; c >= 1; c--) {
3256
- for (r = gm[c].length - 1; r >= 1; r--) {
3550
+ for (r = rl - 1; r >= 1; r--) {
3257
3551
  if (this.is_widget(c, r)) {
3258
3552
  rows.push(r);
3259
- row_in_col[r] = c;
3553
+ cols.push(c);
3260
3554
  break;
3261
3555
  }
3262
3556
  }
3263
3557
  }
3264
3558
 
3265
- var highest_row = Math.max.apply(Math, rows);
3266
-
3267
- this.highest_occupied_cell = {
3268
- col: row_in_col[highest_row],
3269
- row: highest_row
3559
+ return {
3560
+ col: Math.max.apply(Math, cols),
3561
+ row: Math.max.apply(Math, rows)
3270
3562
  };
3271
-
3272
- return this.highest_occupied_cell;
3273
3563
  };
3274
3564
 
3275
3565
 
@@ -3305,9 +3595,34 @@
3305
3595
  * @method set_dom_grid_height
3306
3596
  * @return {Object} Returns the instance of the Gridster class.
3307
3597
  */
3308
- fn.set_dom_grid_height = function() {
3309
- var r = this.get_highest_occupied_cell().row;
3310
- 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);
3311
3626
  return this;
3312
3627
  };
3313
3628
 
@@ -3347,6 +3662,7 @@
3347
3662
  return false;
3348
3663
  }
3349
3664
 
3665
+ this.generated_stylesheets.push(serialized_opts);
3350
3666
  Gridster.generated_stylesheets.push(serialized_opts);
3351
3667
 
3352
3668
  /* generate CSS styles for cols */
@@ -3377,6 +3693,8 @@
3377
3693
  (x - 1) * (opts.widget_margins[0] * 2)) + 'px; }\n');
3378
3694
  }
3379
3695
 
3696
+ this.remove_style_tags();
3697
+
3380
3698
  return this.add_style_tag(styles);
3381
3699
  };
3382
3700
 
@@ -3389,21 +3707,21 @@
3389
3707
  * @return {Object} Returns the instance of the Gridster class.
3390
3708
  */
3391
3709
  fn.add_style_tag = function(css) {
3392
- var d = document;
3393
- var tag = d.createElement('style');
3710
+ var d = document;
3711
+ var tag = d.createElement('style');
3394
3712
 
3395
- d.getElementsByTagName('head')[0].appendChild(tag);
3396
- tag.setAttribute('type', 'text/css');
3713
+ d.getElementsByTagName('head')[0].appendChild(tag);
3714
+ tag.setAttribute('type', 'text/css');
3397
3715
 
3398
- if (tag.styleSheet) {
3399
- tag.styleSheet.cssText = css;
3400
- }else{
3401
- tag.appendChild(document.createTextNode(css));
3402
- }
3716
+ if (tag.styleSheet) {
3717
+ tag.styleSheet.cssText = css;
3718
+ } else {
3719
+ tag.appendChild(document.createTextNode(css));
3720
+ }
3403
3721
 
3404
- this.$style_tags = this.$style_tags.add(tag);
3722
+ this.$style_tags = this.$style_tags.add(tag);
3405
3723
 
3406
- return this;
3724
+ return this;
3407
3725
  };
3408
3726
 
3409
3727
 
@@ -3414,7 +3732,14 @@
3414
3732
  * @return {Object} Returns the instance of the Gridster class.
3415
3733
  */
3416
3734
  fn.remove_style_tags = function() {
3735
+ var all_styles = Gridster.generated_stylesheets;
3736
+ var ins_styles = this.generated_stylesheets;
3737
+
3417
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
+ });
3418
3743
  };
3419
3744
 
3420
3745
 
@@ -3509,8 +3834,9 @@
3509
3834
  fn.add_faux_cols = function(cols) {
3510
3835
  var actual_cols = this.cols;
3511
3836
  var max_cols = actual_cols + (cols || 1);
3837
+ max_cols = Math.min(max_cols, this.options.max_cols);
3512
3838
 
3513
- for (var c = actual_cols; c < max_cols; c++) {
3839
+ for (var c = actual_cols + 1; c <= max_cols; c++) {
3514
3840
  for (var r = this.rows; r >= 1; r--) {
3515
3841
  this.add_faux_cell(r, c);
3516
3842
  }
@@ -3543,7 +3869,6 @@
3543
3869
  left: this.baseX + (coords.data.col -1) * this.min_widget_width,
3544
3870
  top: this.baseY + (coords.data.row -1) * this.min_widget_height
3545
3871
  });
3546
-
3547
3872
  }, this));
3548
3873
 
3549
3874
  return this;
@@ -3557,9 +3882,21 @@
3557
3882
  * @return {Object} Returns the instance of the Gridster class.
3558
3883
  */
3559
3884
  fn.get_widgets_from_DOM = function() {
3560
- this.$widgets.each($.proxy(function(i, widget) {
3561
- 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);
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;
3562
3894
  }, this));
3895
+
3896
+ if (changes.length) {
3897
+ this.$el.trigger('gridster:positionschanged');
3898
+ }
3899
+
3563
3900
  return this;
3564
3901
  };
3565
3902
 
@@ -3573,7 +3910,6 @@
3573
3910
  */
3574
3911
  fn.generate_grid_and_stylesheet = function() {
3575
3912
  var aw = this.$wrapper.width();
3576
- var ah = this.$wrapper.height();
3577
3913
  var max_cols = this.options.max_cols;
3578
3914
 
3579
3915
  var cols = Math.floor(aw / this.min_widget_width) +
@@ -3588,28 +3924,23 @@
3588
3924
 
3589
3925
  var min_cols = Math.max.apply(Math, actual_cols);
3590
3926
 
3927
+ this.cols = Math.max(min_cols, cols, this.options.min_cols);
3928
+
3929
+ if (max_cols !== Infinity && max_cols >= min_cols && max_cols < this.cols) {
3930
+ this.cols = max_cols;
3931
+ }
3932
+
3591
3933
  // get all rows that could be occupied by the current widgets
3592
3934
  var max_rows = this.options.extra_rows;
3593
3935
  this.$widgets.each(function(i, w) {
3594
3936
  max_rows += (+$(w).attr('data-sizey'));
3595
3937
  });
3596
3938
 
3597
- this.cols = Math.max(min_cols, cols, this.options.min_cols);
3598
-
3599
- if (max_cols && max_cols >= min_cols && max_cols < this.cols) {
3600
- this.cols = max_cols;
3601
- }
3602
-
3603
3939
  this.rows = Math.max(max_rows, this.options.min_rows);
3604
3940
 
3605
3941
  this.baseX = ($(window).width() - aw) / 2;
3606
3942
  this.baseY = this.$wrapper.offset().top;
3607
3943
 
3608
- // left and right gutters not included
3609
- this.container_width = (this.cols *
3610
- this.options.widget_base_dimensions[0]) + ((this.cols - 1) * 2 *
3611
- this.options.widget_margins[0]);
3612
-
3613
3944
  if (this.options.autogenerate_stylesheet) {
3614
3945
  this.generate_stylesheet();
3615
3946
  }
@@ -3621,9 +3952,12 @@
3621
3952
  * Destroy this gridster by removing any sign of its presence, making it easy to avoid memory leaks
3622
3953
  *
3623
3954
  * @method destroy
3624
- * @return {undefined}
3955
+ * @param {Boolean} remove If true, remove gridster from DOM.
3956
+ * @return {Object} Returns the instance of the Gridster class.
3625
3957
  */
3626
- fn.destroy = function(){
3958
+ fn.destroy = function(remove) {
3959
+ this.$el.removeData('gridster');
3960
+
3627
3961
  // remove bound callback on window resize
3628
3962
  $(window).unbind('.gridster');
3629
3963
 
@@ -3633,10 +3967,7 @@
3633
3967
 
3634
3968
  this.remove_style_tags();
3635
3969
 
3636
- // lastly, remove gridster element
3637
- // this will additionally cause any data associated to this element to be removed, including this
3638
- // very gridster instance
3639
- this.$el.remove();
3970
+ remove && this.$el.remove();
3640
3971
 
3641
3972
  return this;
3642
3973
  };
@@ -3644,13 +3975,13 @@
3644
3975
 
3645
3976
  //jQuery adapter
3646
3977
  $.fn.gridster = function(options) {
3647
- return this.each(function() {
3648
- if (!$(this).data('gridster')) {
3649
- $(this).data('gridster', new Gridster( this, options ));
3650
- }
3651
- });
3978
+ return this.each(function() {
3979
+ if (! $(this).data('gridster')) {
3980
+ $(this).data('gridster', new Gridster( this, options ));
3981
+ }
3982
+ });
3652
3983
  };
3653
3984
 
3654
- $.Gridster = fn;
3985
+ return Gridster;
3655
3986
 
3656
- }(jQuery, window, document));
3987
+ }));