gridster-rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,44 @@
1
+ Copyright (c) 2013 William Van Etten
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ # gridster.js appended LICENSE #
25
+
26
+ Copyright (c) 2012 Ducksboard
27
+
28
+ Permission is hereby granted, free of charge, to any person obtaining a copy
29
+ of this software and associated documentation files (the "Software"), to deal
30
+ in the Software without restriction, including without limitation the rights
31
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
32
+ copies of the Software, and to permit persons to whom the Software is
33
+ furnished to do so, subject to the following conditions:
34
+
35
+ The above copyright notice and this permission notice shall be included in
36
+ all copies or substantial portions of the Software.
37
+
38
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
39
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
40
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
41
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
42
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
43
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
44
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # Gridster::Rails
2
+
3
+ This is gridster.js GEMified for the Rails >= 3.1 asset pipeline through the following:
4
+
5
+ bundle gem gridster-rails
6
+ cd gridster-rails
7
+ mkdir -p vendor/assets/javascripts
8
+ mkdir -p vendor/assets/stylesheets
9
+ curl https://raw.github.com/ducksboard/gridster.js/master/dist/jquery.gridster.js -o vendor/assets/javascripts/jquery.gridster.js
10
+ curl https://raw.github.com/ducksboard/gridster.js/master/dist/jquery.gridster.css -o vendor/assets/stylesheets/jquery.gridster.css
11
+ echo "" >> README.md; echo "# gridster.js appended README #" >> README.md; echo "" >> README.md
12
+ curl https://raw.github.com/ducksboard/gridster.js/master/README.md >> README.md
13
+ echo "" >> LICENSE; echo "# gridster.js appended LICENSE #" >> LICENSE; echo "" >> LICENSE
14
+ curl https://raw.github.com/ducksboard/gridster.js/master/LICENSE >> LICENSE
15
+ git add .
16
+ git commit -am "initial gridster-rails"
17
+ git remote add origin https://github.com/vanetten/gridster-rails.git
18
+
19
+ * modify lib/gridster-rails/version.rb to match gridster.js version
20
+ `VERSION = "0.1.0"`
21
+
22
+ * modify lib/gridster-rails.rb to subclass Rails::Engine
23
+
24
+ class Engine < ::Rails::Engine
25
+ end
26
+
27
+ * modify gridster-rails.gemspec
28
+
29
+ gem.description = "This gem provides jquery.gridster.js and jquery.gridster.css for your Rails 3 application."
30
+ gem.summary = "Use gridster with Rails 3"
31
+ gem.homepage = "http://rubygems.org/gems/gridster-rails"
32
+ gem.files = Dir["{lib,vendor}/**/*"] + ["LICENSE", "README.md"]
33
+ gem.add_dependency "railties", "~> 3.1"
34
+
35
+ * build
36
+ `rake build`
37
+
38
+ * release
39
+ `rake release`
40
+
41
+ ## Installation
42
+
43
+ Add this line to your application's Gemfile within the assets group:
44
+
45
+ gem 'gridster-rails'
46
+
47
+ And then execute:
48
+
49
+ $ bundle
50
+
51
+ Or install it yourself as:
52
+
53
+ $ gem install gridster-rails
54
+
55
+ ## Usage
56
+
57
+ Add to application.js
58
+ `//= require jquery.gridster.js`
59
+
60
+ Add to application.css
61
+ `*= require fullcalendar`
62
+
63
+ ## Contributing
64
+
65
+ 1. Fork it
66
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
67
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
68
+ 4. Push to the branch (`git push origin my-new-feature`)
69
+ 5. Create new Pull Request
70
+
71
+ # gridster.js appended README #
72
+
73
+ Gridster.js
74
+ ===========
75
+
76
+ Gridster is a jQuery plugin that makes building intuitive draggable
77
+ layouts from elements spanning multiple columns. You can even
78
+ dynamically add and remove elements from the grid.
79
+
80
+ More at [http://gridster.net/](http://gridster.net/).
81
+
82
+ License
83
+ =======
84
+
85
+ Distributed under the MIT license.
86
+
87
+ Whodunit
88
+ ========
89
+
90
+ Gridster is built by [Ducksboard](http://ducksboard.com/).
@@ -0,0 +1,8 @@
1
+ require "gridster-rails/version"
2
+
3
+ module Gridster
4
+ module Rails
5
+ class Engine < ::Rails::Engine
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module Gridster
2
+ module Rails
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,3232 @@
1
+ /*! gridster.js - v0.1.0 - 2012-10-20
2
+ * http://gridster.net/
3
+ * Copyright (c) 2012 ducksboard; Licensed MIT */
4
+
5
+ ;(function($, window, document, undefined){
6
+ /**
7
+ * Creates objects with coordinates (x1, y1, x2, y2, cx, cy, width, height)
8
+ * to simulate DOM elements on the screen.
9
+ * Coords is used by Gridster to create a faux grid with any DOM element can
10
+ * collide.
11
+ *
12
+ * @class Coords
13
+ * @param {HTMLElement|Object} obj The jQuery HTMLElement or a object with: left,
14
+ * top, width and height properties.
15
+ * @return {Object} Coords instance.
16
+ * @constructor
17
+ */
18
+ function Coords(obj) {
19
+ if (obj[0] && $.isPlainObject(obj[0])) {
20
+ this.data = obj[0];
21
+ }else {
22
+ this.el = obj;
23
+ }
24
+
25
+ this.isCoords = true;
26
+ this.coords = {};
27
+ this.init();
28
+ return this;
29
+ }
30
+
31
+
32
+ var fn = Coords.prototype;
33
+
34
+
35
+ fn.init = function(){
36
+ this.set();
37
+ this.original_coords = this.get();
38
+ };
39
+
40
+
41
+ fn.set = function(update, not_update_offsets) {
42
+ var el = this.el;
43
+
44
+ if (el && !update) {
45
+ this.data = el.offset();
46
+ this.data.width = el.width();
47
+ this.data.height = el.height();
48
+ }
49
+
50
+ if (el && update && !not_update_offsets) {
51
+ var offset = el.offset();
52
+ this.data.top = offset.top;
53
+ this.data.left = offset.left;
54
+ }
55
+
56
+ var d = this.data;
57
+
58
+ this.coords.x1 = d.left;
59
+ this.coords.y1 = d.top;
60
+ this.coords.x2 = d.left + d.width;
61
+ this.coords.y2 = d.top + d.height;
62
+ this.coords.cx = d.left + (d.width / 2);
63
+ this.coords.cy = d.top + (d.height / 2);
64
+ this.coords.width = d.width;
65
+ this.coords.height = d.height;
66
+ this.coords.el = el || false ;
67
+
68
+ return this;
69
+ };
70
+
71
+
72
+ fn.update = function(data){
73
+ if (!data && !this.el) {
74
+ return this;
75
+ }
76
+
77
+ if (data) {
78
+ var new_data = $.extend({}, this.data, data);
79
+ this.data = new_data;
80
+ return this.set(true, true);
81
+ }
82
+
83
+ this.set(true);
84
+ return this;
85
+ };
86
+
87
+
88
+ fn.get = function(){
89
+ return this.coords;
90
+ };
91
+
92
+
93
+ //jQuery adapter
94
+ $.fn.coords = function() {
95
+ if (this.data('coords') ) {
96
+ return this.data('coords');
97
+ }
98
+
99
+ var ins = new Coords(this, arguments[0]);
100
+ this.data('coords', ins);
101
+ return ins;
102
+ };
103
+
104
+ }(jQuery, window, document));
105
+
106
+ ;(function($, window, document, undefined){
107
+
108
+ var defaults = {
109
+ colliders_context: document.body
110
+ // ,on_overlap: function(collider_data){},
111
+ // on_overlap_start : function(collider_data){},
112
+ // on_overlap_stop : function(collider_data){}
113
+ };
114
+
115
+
116
+ /**
117
+ * Detects collisions between a DOM element against other DOM elements or
118
+ * Coords objects.
119
+ *
120
+ * @class Collision
121
+ * @uses Coords
122
+ * @param {HTMLElement} el The jQuery wrapped HTMLElement.
123
+ * @param {HTMLElement|Array} colliders Can be a jQuery collection
124
+ * of HTMLElements or an Array of Coords instances.
125
+ * @param {Object} [options] An Object with all options you want to
126
+ * overwrite:
127
+ * @param {Function} [options.on_overlap_start] Executes a function the first
128
+ * time each `collider ` is overlapped.
129
+ * @param {Function} [options.on_overlap_stop] Executes a function when a
130
+ * `collider` is no longer collided.
131
+ * @param {Function} [options.on_overlap] Executes a function when the
132
+ * mouse is moved during the collision.
133
+ * @return {Object} Collision instance.
134
+ * @constructor
135
+ */
136
+ function Collision(el, colliders, options) {
137
+ this.options = $.extend(defaults, options);
138
+ this.$element = el;
139
+ this.last_colliders = [];
140
+ 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
+ }
147
+
148
+ this.init();
149
+ }
150
+
151
+
152
+ var fn = Collision.prototype;
153
+
154
+
155
+ fn.init = function() {
156
+ this.find_collisions();
157
+ };
158
+
159
+
160
+ fn.overlaps = function(a, b) {
161
+ var x = false;
162
+ var y = false;
163
+
164
+ if ((b.x1 >= a.x1 && b.x1 <= a.x2) ||
165
+ (b.x2 >= a.x1 && b.x2 <= a.x2) ||
166
+ (a.x1 >= b.x1 && a.x2 <= b.x2)
167
+ ) { x = true; }
168
+
169
+ if ((b.y1 >= a.y1 && b.y1 <= a.y2) ||
170
+ (b.y2 >= a.y1 && b.y2 <= a.y2) ||
171
+ (a.y1 >= b.y1 && a.y2 <= b.y2)
172
+ ) { y = true; }
173
+
174
+ return (x && y);
175
+ };
176
+
177
+
178
+ fn.detect_overlapping_region = function(a, b){
179
+ var regionX = '';
180
+ var regionY = '';
181
+
182
+ if (a.y1 > b.cy && a.y1 < b.y2) { regionX = 'N'; }
183
+ if (a.y2 > b.y1 && a.y2 < b.cy) { regionX = 'S'; }
184
+ if (a.x1 > b.cx && a.x1 < b.x2) { regionY = 'W'; }
185
+ if (a.x2 > b.x1 && a.x2 < b.cx) { regionY = 'E'; }
186
+
187
+ return (regionX + regionY) || 'C';
188
+ };
189
+
190
+
191
+ fn.calculate_overlapped_area_coords = function(a, b){
192
+ var x1 = Math.max(a.x1, b.x1);
193
+ var y1 = Math.max(a.y1, b.y1);
194
+ var x2 = Math.min(a.x2, b.x2);
195
+ var y2 = Math.min(a.y2, b.y2);
196
+
197
+ return $({
198
+ left: x1,
199
+ top: y1,
200
+ width : (x2 - x1),
201
+ height: (y2 - y1)
202
+ }).coords().get();
203
+ };
204
+
205
+
206
+ fn.calculate_overlapped_area = function(coords){
207
+ return (coords.width * coords.height);
208
+ };
209
+
210
+
211
+ fn.manage_colliders_start_stop = function(new_colliders_coords, start_callback, stop_callback){
212
+ var last = this.last_colliders_coords;
213
+
214
+ for (var i = 0, il = last.length; i < il; i++) {
215
+ if ($.inArray(last[i], new_colliders_coords) === -1) {
216
+ start_callback.call(this, last[i]);
217
+ }
218
+ }
219
+
220
+ for (var j = 0, jl = new_colliders_coords.length; j < jl; j++) {
221
+ if ($.inArray(new_colliders_coords[j], last) === -1) {
222
+ stop_callback.call(this, new_colliders_coords[j]);
223
+ }
224
+
225
+ }
226
+ };
227
+
228
+
229
+ fn.find_collisions = function(player_data_coords){
230
+ var self = this;
231
+ var colliders_coords = [];
232
+ var colliders_data = [];
233
+ var $colliders = (this.colliders || this.$colliders);
234
+ var count = $colliders.length;
235
+ var player_coords = self.$element.coords()
236
+ .update(player_data_coords || false).get();
237
+
238
+ while(count--){
239
+ var $collider = self.$colliders ?
240
+ $($colliders[count]) : $colliders[count];
241
+ var $collider_coords_ins = ($collider.isCoords) ?
242
+ $collider : $collider.coords();
243
+ var collider_coords = $collider_coords_ins.get();
244
+ var overlaps = self.overlaps(player_coords, collider_coords);
245
+
246
+ if (!overlaps) {
247
+ continue;
248
+ }
249
+
250
+ var region = self.detect_overlapping_region(
251
+ player_coords, collider_coords);
252
+
253
+ //todo: make this an option
254
+ if (region === 'C'){
255
+ var area_coords = self.calculate_overlapped_area_coords(
256
+ player_coords, collider_coords);
257
+ var area = self.calculate_overlapped_area(area_coords);
258
+ var collider_data = {
259
+ area: area,
260
+ area_coords : area_coords,
261
+ region: region,
262
+ coords: collider_coords,
263
+ player_coords: player_coords,
264
+ el: $collider
265
+ };
266
+
267
+ if (self.options.on_overlap) {
268
+ self.options.on_overlap.call(this, collider_data);
269
+ }
270
+ colliders_coords.push($collider_coords_ins);
271
+ colliders_data.push(collider_data);
272
+ }
273
+ }
274
+
275
+ if (self.options.on_overlap_stop || self.options.on_overlap_start) {
276
+ this.manage_colliders_start_stop(colliders_coords,
277
+ self.options.on_overlap_stop, self.options.on_overlap_start);
278
+ }
279
+
280
+ this.last_colliders_coords = colliders_coords;
281
+
282
+ return colliders_data;
283
+ };
284
+
285
+
286
+ fn.get_closest_colliders = function(player_data_coords){
287
+ var colliders = this.find_collisions(player_data_coords);
288
+
289
+ colliders.sort(function(a, b) {
290
+ /* if colliders are being overlapped by the "C" (center) region,
291
+ * we have to set a lower index in the array to which they are placed
292
+ * above in the grid. */
293
+ if (a.region === 'C' && b.region === 'C') {
294
+ if (a.coords.y1 < b.coords.y1 || a.coords.x1 < b.coords.x1) {
295
+ return - 1;
296
+ }else{
297
+ return 1;
298
+ }
299
+ }
300
+
301
+ if (a.area < b.area) {
302
+ return 1;
303
+ }
304
+
305
+ return 1;
306
+ });
307
+ return colliders;
308
+ };
309
+
310
+
311
+ //jQuery adapter
312
+ $.fn.collision = function(collider, options) {
313
+ return new Collision( this, collider, options );
314
+ };
315
+
316
+
317
+ }(jQuery, window, document));
318
+
319
+ ;(function(window, undefined) {
320
+ /* Debounce and throttle functions taken from underscore.js */
321
+ window.debounce = function(func, wait, immediate) {
322
+ var timeout;
323
+ return function() {
324
+ var context = this, args = arguments;
325
+ var later = function() {
326
+ timeout = null;
327
+ if (!immediate) func.apply(context, args);
328
+ };
329
+ if (immediate && !timeout) func.apply(context, args);
330
+ clearTimeout(timeout);
331
+ timeout = setTimeout(later, wait);
332
+ };
333
+ };
334
+
335
+
336
+ window.throttle = function(func, wait) {
337
+ var context, args, timeout, throttling, more, result;
338
+ var whenDone = debounce(
339
+ function(){ more = throttling = false; }, wait);
340
+ return function() {
341
+ context = this; args = arguments;
342
+ var later = function() {
343
+ timeout = null;
344
+ if (more) func.apply(context, args);
345
+ whenDone();
346
+ };
347
+ if (!timeout) timeout = setTimeout(later, wait);
348
+ if (throttling) {
349
+ more = true;
350
+ } else {
351
+ result = func.apply(context, args);
352
+ }
353
+ whenDone();
354
+ throttling = true;
355
+ return result;
356
+ };
357
+ };
358
+
359
+ })(window);
360
+
361
+ ;(function($, window, document, undefined){
362
+
363
+ var defaults = {
364
+ items: '.gs_w',
365
+ distance: 1,
366
+ limit: true,
367
+ offset_left: 0,
368
+ autoscroll: true,
369
+ ignore_dragging: ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON'],
370
+ handle: null
371
+ // ,drag: function(e){},
372
+ // start : function(e, ui){},
373
+ // stop : function(e){}
374
+ };
375
+
376
+ var $window = $(window);
377
+ var isTouch = !!('ontouchstart' in window);
378
+ var pointer_events = {
379
+ start: isTouch ? 'touchstart' : 'mousedown.draggable',
380
+ move: isTouch ? 'touchmove' : 'mousemove.draggable',
381
+ end: isTouch ? 'touchend' : 'mouseup.draggable'
382
+ };
383
+
384
+ /**
385
+ * Basic drag implementation for DOM elements inside a container.
386
+ * Provide start/stop/drag callbacks.
387
+ *
388
+ * @class Draggable
389
+ * @param {HTMLElement} el The HTMLelement that contains all the widgets
390
+ * to be dragged.
391
+ * @param {Object} [options] An Object with all options you want to
392
+ * overwrite:
393
+ * @param {HTMLElement|String} [options.items] Define who will
394
+ * be the draggable items. Can be a CSS Selector String or a
395
+ * collection of HTMLElements.
396
+ * @param {Number} [options.distance] Distance in pixels after mousedown
397
+ * the mouse must move before dragging should start.
398
+ * @param {Boolean} [options.limit] Constrains dragging to the width of
399
+ * the container
400
+ * @param {offset_left} [options.offset_left] Offset added to the item
401
+ * that is being dragged.
402
+ * @param {Number} [options.drag] Executes a callback when the mouse is
403
+ * moved during the dragging.
404
+ * @param {Number} [options.start] Executes a callback when the drag
405
+ * starts.
406
+ * @param {Number} [options.stop] Executes a callback when the drag stops.
407
+ * @return {Object} Returns `el`.
408
+ * @constructor
409
+ */
410
+ function Draggable(el, options) {
411
+ this.options = $.extend({}, defaults, options);
412
+ this.$body = $(document.body);
413
+ this.$container = $(el);
414
+ this.$dragitems = $(this.options.items, this.$container);
415
+ this.is_dragging = false;
416
+ this.player_min_left = 0 + this.options.offset_left;
417
+ this.init();
418
+ }
419
+
420
+ var fn = Draggable.prototype;
421
+
422
+ fn.init = function() {
423
+ this.calculate_positions();
424
+ this.$container.css('position', 'relative');
425
+ this.disabled = false;
426
+ this.events();
427
+
428
+ $(window).bind('resize',
429
+ throttle($.proxy(this.calculate_positions, this), 200));
430
+ };
431
+
432
+ fn.events = function() {
433
+ this.$container.on('selectstart', $.proxy(this.on_select_start, this));
434
+
435
+ this.$container.on(pointer_events.start, this.options.items, $.proxy(
436
+ this.drag_handler, this));
437
+
438
+ this.$body.on(pointer_events.end, $.proxy(function(e) {
439
+ this.is_dragging = false;
440
+ if (this.disabled) { return; }
441
+ this.$body.off(pointer_events.move);
442
+ if (this.drag_start) {
443
+ this.on_dragstop(e);
444
+ }
445
+ }, this));
446
+ };
447
+
448
+ fn.get_actual_pos = function($el) {
449
+ var pos = $el.position();
450
+ return pos;
451
+ };
452
+
453
+
454
+ fn.get_mouse_pos = function(e) {
455
+ if (isTouch) {
456
+ var oe = e.originalEvent;
457
+ e = oe.touches.length ? oe.touches[0] : oe.changedTouches[0];
458
+ }
459
+
460
+ return {
461
+ left: e.clientX,
462
+ top: e.clientY
463
+ };
464
+ };
465
+
466
+
467
+ fn.get_offset = function(e) {
468
+ e.preventDefault();
469
+ var mouse_actual_pos = this.get_mouse_pos(e);
470
+ var diff_x = Math.round(
471
+ mouse_actual_pos.left - this.mouse_init_pos.left);
472
+ var diff_y = Math.round(mouse_actual_pos.top - this.mouse_init_pos.top);
473
+
474
+ var left = Math.round(this.el_init_offset.left + diff_x - this.baseX);
475
+ var top = Math.round(
476
+ this.el_init_offset.top + diff_y - this.baseY + this.scrollOffset);
477
+
478
+ if (this.options.limit) {
479
+ if (left > this.player_max_left) {
480
+ left = this.player_max_left;
481
+ }else if(left < this.player_min_left) {
482
+ left = this.player_min_left;
483
+ }
484
+ }
485
+
486
+ return {
487
+ left: left,
488
+ top: top,
489
+ mouse_left: mouse_actual_pos.left,
490
+ mouse_top: mouse_actual_pos.top
491
+ };
492
+ };
493
+
494
+
495
+ fn.manage_scroll = function(offset) {
496
+ /* scroll document */
497
+ var nextScrollTop;
498
+ var scrollTop = $window.scrollTop();
499
+ var min_window_y = scrollTop;
500
+ var max_window_y = min_window_y + this.window_height;
501
+
502
+ var mouse_down_zone = max_window_y - 50;
503
+ var mouse_up_zone = min_window_y + 50;
504
+
505
+ var abs_mouse_left = offset.mouse_left;
506
+ var abs_mouse_top = min_window_y + offset.mouse_top;
507
+
508
+ var max_player_y = (this.doc_height - this.window_height +
509
+ this.player_height);
510
+
511
+ if (abs_mouse_top >= mouse_down_zone) {
512
+ nextScrollTop = scrollTop + 30;
513
+ if (nextScrollTop < max_player_y) {
514
+ $window.scrollTop(nextScrollTop);
515
+ this.scrollOffset = this.scrollOffset + 30;
516
+ }
517
+ }
518
+
519
+ if (abs_mouse_top <= mouse_up_zone) {
520
+ nextScrollTop = scrollTop - 30;
521
+ if (nextScrollTop > 0) {
522
+ $window.scrollTop(nextScrollTop);
523
+ this.scrollOffset = this.scrollOffset - 30;
524
+ }
525
+ }
526
+ };
527
+
528
+
529
+ fn.calculate_positions = function(e) {
530
+ this.window_height = $window.height();
531
+ };
532
+
533
+
534
+ fn.drag_handler = function(e) {
535
+ var node = e.target.nodeName;
536
+ if (this.disabled || e.which !== 1 && !isTouch) {
537
+ return;
538
+ }
539
+
540
+ if (this.ignore_drag(e)) {
541
+ return;
542
+ }
543
+
544
+ var self = this;
545
+ var first = true;
546
+ this.$player = $(e.currentTarget);
547
+
548
+ this.el_init_pos = this.get_actual_pos(this.$player);
549
+ this.mouse_init_pos = this.get_mouse_pos(e);
550
+ this.offsetY = this.mouse_init_pos.top - this.el_init_pos.top;
551
+
552
+ this.$body.on(pointer_events.move, function(mme){
553
+ var mouse_actual_pos = self.get_mouse_pos(mme);
554
+ var diff_x = Math.abs(
555
+ mouse_actual_pos.left - self.mouse_init_pos.left);
556
+ var diff_y = Math.abs(
557
+ mouse_actual_pos.top - self.mouse_init_pos.top);
558
+ if (!(diff_x > self.options.distance ||
559
+ diff_y > self.options.distance)
560
+ ) {
561
+ return false;
562
+ }
563
+
564
+ if (first) {
565
+ first = false;
566
+ self.on_dragstart.call(self, mme);
567
+ return false;
568
+ }
569
+
570
+ if (self.is_dragging === true) {
571
+ self.on_dragmove.call(self, mme);
572
+ }
573
+
574
+ return false;
575
+ });
576
+
577
+ return false;
578
+ };
579
+
580
+
581
+ fn.on_dragstart = function(e) {
582
+ e.preventDefault();
583
+ this.drag_start = true;
584
+ this.is_dragging = true;
585
+ var offset = this.$container.offset();
586
+ this.baseX = Math.round(offset.left);
587
+ this.baseY = Math.round(offset.top);
588
+ this.doc_height = $(document).height();
589
+
590
+ if (this.options.helper === 'clone') {
591
+ this.$helper = this.$player.clone()
592
+ .appendTo(this.$container).addClass('helper');
593
+ this.helper = true;
594
+ }else{
595
+ this.helper = false;
596
+ }
597
+ this.scrollOffset = 0;
598
+ this.el_init_offset = this.$player.offset();
599
+ this.player_width = this.$player.width();
600
+ this.player_height = this.$player.height();
601
+ this.player_max_left = (this.$container.width() - this.player_width +
602
+ this.options.offset_left);
603
+
604
+ if (this.options.start) {
605
+ this.options.start.call(this.$player, e, {
606
+ helper: this.helper ? this.$helper : this.$player
607
+ });
608
+ }
609
+ return false;
610
+ };
611
+
612
+
613
+ fn.on_dragmove = function(e) {
614
+ var offset = this.get_offset(e);
615
+
616
+ this.options.autoscroll && this.manage_scroll(offset);
617
+
618
+ (this.helper ? this.$helper : this.$player).css({
619
+ 'position': 'absolute',
620
+ 'left' : offset.left,
621
+ 'top' : offset.top
622
+ });
623
+
624
+ var ui = {
625
+ 'position': {
626
+ 'left': offset.left,
627
+ 'top': offset.top
628
+ }
629
+ };
630
+
631
+ if (this.options.drag) {
632
+ this.options.drag.call(this.$player, e, ui);
633
+ }
634
+ return false;
635
+ };
636
+
637
+
638
+ fn.on_dragstop = function(e) {
639
+ var offset = this.get_offset(e);
640
+ this.drag_start = false;
641
+
642
+ var ui = {
643
+ 'position': {
644
+ 'left': offset.left,
645
+ 'top': offset.top
646
+ }
647
+ };
648
+
649
+ if (this.options.stop) {
650
+ this.options.stop.call(this.$player, e, ui);
651
+ }
652
+
653
+ if (this.helper) {
654
+ this.$helper.remove();
655
+ }
656
+
657
+ return false;
658
+ };
659
+
660
+ fn.on_select_start = function(e) {
661
+ if (this.disabled) { return; }
662
+
663
+ if (this.ignore_drag(e)) {
664
+ return;
665
+ }
666
+
667
+ return false;
668
+ };
669
+
670
+ fn.enable = function() {
671
+ this.disabled = false;
672
+ };
673
+
674
+ fn.disable = function() {
675
+ this.disabled = true;
676
+ };
677
+
678
+
679
+ fn.destroy = function(){
680
+ this.disable();
681
+ $.removeData(this.$container, 'drag');
682
+ };
683
+
684
+ fn.ignore_drag = function(event) {
685
+ if (this.options.handle) {
686
+ return !$(event.target).is(this.options.handle);
687
+ }
688
+
689
+ return $.inArray(event.target.nodeName, this.options.ignore_dragging) >= 0;
690
+ };
691
+
692
+ //jQuery adapter
693
+ $.fn.drag = function ( options ) {
694
+ return this.each(function () {
695
+ if (!$.data(this, 'drag')) {
696
+ $.data(this, 'drag', new Draggable( this, options ));
697
+ }
698
+ });
699
+ };
700
+
701
+
702
+ }(jQuery, window, document));
703
+
704
+ ;(function($, window, document, undefined) {
705
+
706
+ var defaults = {
707
+ namespace: '',
708
+ widget_selector: 'li',
709
+ widget_margins: [10, 10],
710
+ widget_base_dimensions: [400, 225],
711
+ extra_rows: 0,
712
+ extra_cols: 0,
713
+ min_cols: 1,
714
+ min_rows: 15,
715
+ max_size_x: 6,
716
+ autogenerate_stylesheet: true,
717
+ avoid_overlapped_widgets: true,
718
+ serialize_params: function($w, wgd) {
719
+ return {
720
+ col: wgd.col,
721
+ row: wgd.row,
722
+ size_x: wgd.size_x,
723
+ size_y: wgd.size_y
724
+ };
725
+ },
726
+ collision: {},
727
+ draggable: {
728
+ distance: 4
729
+ }
730
+ };
731
+
732
+
733
+ /**
734
+ * @class Gridster
735
+ * @uses Draggable
736
+ * @uses Collision
737
+ * @param {HTMLElement} el The HTMLelement that contains all the widgets.
738
+ * @param {Object} [options] An Object with all options you want to
739
+ * overwrite:
740
+ * @param {HTMLElement|String} [options.widget_selector] Define who will
741
+ * be the draggable widgets. Can be a CSS Selector String or a
742
+ * collection of HTMLElements
743
+ * @param {Array} [options.widget_margins] Margin between widgets.
744
+ * The first index for the horizontal margin (left, right) and
745
+ * the second for the vertical margin (top, bottom).
746
+ * @param {Array} [options.widget_base_dimensions] Base widget dimensions
747
+ * in pixels. The first index for the width and the second for the
748
+ * height.
749
+ * @param {Number} [options.extra_cols] Add more columns in addition to
750
+ * those that have been calculated.
751
+ * @param {Number} [options.extra_rows] Add more rows in addition to
752
+ * those that have been calculated.
753
+ * @param {Number} [options.min_cols] The minimum required columns.
754
+ * @param {Number} [options.min_rows] The minimum required rows.
755
+ * @param {Number} [options.max_size_x] The maximum number of columns
756
+ * that a widget can span.
757
+ * @param {Boolean} [options.autogenerate_stylesheet] If true, all the
758
+ * CSS required to position all widgets in their respective columns
759
+ * and rows will be generated automatically and injected to the
760
+ * `<head>` of the document. You can set this to false, and write
761
+ * your own CSS targeting rows and cols via data-attributes like so:
762
+ * `[data-col="1"] { left: 10px; }`
763
+ * @param {Boolean} [options.avoid_overlapped_widgets] Avoid that widgets loaded
764
+ * from the DOM can be overlapped. It is helpful if the positions were
765
+ * bad stored in the database or if there was any conflict.
766
+ * @param {Function} [options.serialize_params] Return the data you want
767
+ * for each widget in the serialization. Two arguments are passed:
768
+ * `$w`: the jQuery wrapped HTMLElement, and `wgd`: the grid
769
+ * coords object (`col`, `row`, `size_x`, `size_y`).
770
+ * @param {Object} [options.collision] An Object with all options for
771
+ * Collision class you want to overwrite. See Collision docs for
772
+ * more info.
773
+ * @param {Object} [options.draggable] An Object with all options for
774
+ * Draggable class you want to overwrite. See Draggable docs for more
775
+ * info.
776
+ *
777
+ * @constructor
778
+ */
779
+ function Gridster(el, options) {
780
+ this.options = $.extend(true, defaults, options);
781
+ this.$el = $(el);
782
+ this.$wrapper = this.$el.parent();
783
+ this.$widgets = this.$el.children(this.options.widget_selector).addClass('gs_w');
784
+ this.widgets = [];
785
+ this.$changed = $([]);
786
+ this.wrapper_width = this.$wrapper.width();
787
+ this.min_widget_width = (this.options.widget_margins[0] * 2) +
788
+ this.options.widget_base_dimensions[0];
789
+ this.min_widget_height = (this.options.widget_margins[1] * 2) +
790
+ this.options.widget_base_dimensions[1];
791
+ this.init();
792
+ }
793
+
794
+ Gridster.generated_stylesheets = [];
795
+
796
+ var fn = Gridster.prototype;
797
+
798
+ fn.init = function() {
799
+ this.generate_grid_and_stylesheet();
800
+ this.get_widgets_from_DOM();
801
+ this.set_dom_grid_height();
802
+ this.$wrapper.addClass('ready');
803
+ this.draggable();
804
+
805
+ $(window).bind(
806
+ 'resize', throttle($.proxy(this.recalculate_faux_grid, this), 200));
807
+ };
808
+
809
+
810
+ /**
811
+ * Disables dragging.
812
+ *
813
+ * @method disable
814
+ * @return {Class} Returns the instance of the Gridster Class.
815
+ */
816
+ fn.disable = function() {
817
+ this.$wrapper.find('.player-revert').removeClass('player-revert');
818
+ this.drag_api.disable();
819
+ return this;
820
+ };
821
+
822
+
823
+ /**
824
+ * Enables dragging.
825
+ *
826
+ * @method enable
827
+ * @return {Class} Returns the instance of the Gridster Class.
828
+ */
829
+ fn.enable = function() {
830
+ this.drag_api.enable();
831
+ return this;
832
+ };
833
+
834
+
835
+ /**
836
+ * Add a new widget to the grid.
837
+ *
838
+ * @method add_widget
839
+ * @param {String|HTMLElement} html The string representing the HTML of the widget
840
+ * or the HTMLElement.
841
+ * @param {Number} [size_x] The nº of rows the widget occupies horizontally.
842
+ * @param {Number} [size_y] The nº of columns the widget occupies vertically.
843
+ * @param {Number} [col] The column the widget should start in.
844
+ * @param {Number} [row] The row the widget should start in.
845
+ * @return {HTMLElement} Returns the jQuery wrapped HTMLElement representing.
846
+ * the widget that was just created.
847
+ */
848
+ fn.add_widget = function(html, size_x, size_y, col, row) {
849
+ var pos;
850
+ size_x || (size_x = 1);
851
+ size_y || (size_y = 1);
852
+
853
+ if (!col & !row) {
854
+ pos = this.next_position(size_x, size_y);
855
+ }else{
856
+ pos = {
857
+ col: col,
858
+ row: row
859
+ };
860
+
861
+ this.empty_cells(col, row, size_x, size_y);
862
+ }
863
+
864
+ var $w = $(html).attr({
865
+ 'data-col': pos.col,
866
+ 'data-row': pos.row,
867
+ 'data-sizex' : size_x,
868
+ 'data-sizey' : size_y
869
+ }).addClass('gs_w').appendTo(this.$el).hide();
870
+
871
+ this.$widgets = this.$widgets.add($w);
872
+
873
+ this.register_widget($w);
874
+
875
+ this.add_faux_rows(pos.size_y);
876
+ //this.add_faux_cols(pos.size_x);
877
+
878
+ this.set_dom_grid_height();
879
+
880
+ return $w.fadeIn();
881
+ };
882
+
883
+
884
+
885
+ /**
886
+ * Change the size of a widget.
887
+ *
888
+ * @method resize_widget
889
+ * @param {HTMLElement} $widget The jQuery wrapped HTMLElement
890
+ * representing the widget.
891
+ * @param {Number} size_x The number of columns that will occupy the widget.
892
+ * @param {Number} size_y The number of rows that will occupy the widget.
893
+ * @return {HTMLElement} Returns $widget.
894
+ */
895
+ fn.resize_widget = function($widget, size_x, size_y) {
896
+ var wgd = $widget.coords().grid;
897
+ size_x || (size_x = wgd.size_x);
898
+ size_y || (size_y = wgd.size_y);
899
+
900
+ if (size_x > this.cols) {
901
+ size_x = this.cols;
902
+ }
903
+
904
+ var old_cells_occupied = this.get_cells_occupied(wgd);
905
+ var old_size_x = wgd.size_x;
906
+ var old_size_y = wgd.size_y;
907
+ var old_col = wgd.col;
908
+ var new_col = old_col;
909
+ var wider = size_x > old_size_x;
910
+ var taller = size_y > old_size_y;
911
+
912
+ if (old_col + size_x - 1 > this.cols) {
913
+ var diff = old_col + (size_x - 1) - this.cols;
914
+ var c = old_col - diff;
915
+ new_col = Math.max(1, c);
916
+ }
917
+
918
+ var new_grid_data = {
919
+ col: new_col,
920
+ row: wgd.row,
921
+ size_x: size_x,
922
+ size_y: size_y
923
+ };
924
+
925
+ var new_cells_occupied = this.get_cells_occupied(new_grid_data);
926
+
927
+ var empty_cols = [];
928
+ $.each(old_cells_occupied.cols, function(i, col) {
929
+ if ($.inArray(col, new_cells_occupied.cols) === -1) {
930
+ empty_cols.push(col);
931
+ }
932
+ });
933
+
934
+ var occupied_cols = [];
935
+ $.each(new_cells_occupied.cols, function(i, col) {
936
+ if ($.inArray(col, old_cells_occupied.cols) === -1) {
937
+ occupied_cols.push(col);
938
+ }
939
+ });
940
+
941
+ var empty_rows = [];
942
+ $.each(old_cells_occupied.rows, function(i, row) {
943
+ if ($.inArray(row, new_cells_occupied.rows) === -1) {
944
+ empty_rows.push(row);
945
+ }
946
+ });
947
+
948
+ var occupied_rows = [];
949
+ $.each(new_cells_occupied.rows, function(i, row) {
950
+ if ($.inArray(row, old_cells_occupied.rows) === -1) {
951
+ occupied_rows.push(row);
952
+ }
953
+ });
954
+
955
+ this.remove_from_gridmap(wgd);
956
+
957
+ if (occupied_cols.length) {
958
+ var cols_to_empty = [
959
+ new_col, wgd.row, size_x, Math.min(old_size_y, size_y), $widget
960
+ ];
961
+ this.empty_cells.apply(this, cols_to_empty);
962
+ }
963
+
964
+ if (occupied_rows.length) {
965
+ var rows_to_empty = [new_col, wgd.row, size_x, size_y, $widget];
966
+ this.empty_cells.apply(this, rows_to_empty);
967
+ }
968
+
969
+ wgd.col = new_col;
970
+ wgd.size_x = size_x;
971
+ wgd.size_y = size_y;
972
+ this.add_to_gridmap(new_grid_data, $widget);
973
+
974
+ //update coords instance attributes
975
+ $widget.data('coords').update({
976
+ width: (size_x * this.options.widget_base_dimensions[0] +
977
+ ((size_x - 1) * this.options.widget_margins[0]) * 2),
978
+ height: (size_y * this.options.widget_base_dimensions[1] +
979
+ ((size_y - 1) * this.options.widget_margins[1]) * 2)
980
+ });
981
+
982
+ if (size_y > old_size_y) {
983
+ this.add_faux_rows(size_y - old_size_y);
984
+ }
985
+
986
+ if (size_x > old_size_x) {
987
+ this.add_faux_cols(size_x - old_size_x);
988
+ }
989
+
990
+ $widget.attr({
991
+ 'data-col': new_col,
992
+ 'data-sizex': size_x,
993
+ 'data-sizey': size_y
994
+ });
995
+
996
+ if (empty_cols.length) {
997
+ var cols_to_remove_holes = [
998
+ empty_cols[0], wgd.row,
999
+ empty_cols.length,
1000
+ Math.min(old_size_y, size_y),
1001
+ $widget
1002
+ ];
1003
+
1004
+ this.remove_empty_cells.apply(this, cols_to_remove_holes);
1005
+ }
1006
+
1007
+ if (empty_rows.length) {
1008
+ var rows_to_remove_holes = [
1009
+ new_col, wgd.row, size_x, size_y, $widget
1010
+ ];
1011
+ this.remove_empty_cells.apply(this, rows_to_remove_holes);
1012
+ }
1013
+
1014
+ return $widget;
1015
+ };
1016
+
1017
+ /**
1018
+ * Move down widgets in cells represented by the arguments col, row, size_x,
1019
+ * size_y
1020
+ *
1021
+ * @method empty_cells
1022
+ * @param {Number} col The column where the group of cells begin.
1023
+ * @param {Number} row The row where the group of cells begin.
1024
+ * @param {Number} size_x The number of columns that the group of cells
1025
+ * occupy.
1026
+ * @param {Number} size_y The number of rows that the group of cells
1027
+ * occupy.
1028
+ * @param {HTMLElement} $exclude Exclude widgets from being moved.
1029
+ * @return {Class} Returns the instance of the Gridster Class.
1030
+ */
1031
+ fn.empty_cells = function(col, row, size_x, size_y, $exclude) {
1032
+ var $nexts = this.widgets_below({
1033
+ col: col,
1034
+ row: row - size_y,
1035
+ size_x: size_x,
1036
+ size_y: size_y
1037
+ });
1038
+
1039
+ $nexts.not($exclude).each($.proxy(function(i, w) {
1040
+ var wgd = $(w).coords().grid;
1041
+ if (!(wgd.row <= (row + size_y - 1))) { return; }
1042
+ var diff = (row + size_y) - wgd.row;
1043
+ this.move_widget_down($(w), diff);
1044
+ }, this));
1045
+
1046
+ this.set_dom_grid_height();
1047
+
1048
+ return this;
1049
+ };
1050
+
1051
+
1052
+ /**
1053
+ * Move up widgets below cells represented by the arguments col, row, size_x,
1054
+ * size_y.
1055
+ *
1056
+ * @method remove_empty_cells
1057
+ * @param {Number} col The column where the group of cells begin.
1058
+ * @param {Number} row The row where the group of cells begin.
1059
+ * @param {Number} size_x The number of columns that the group of cells
1060
+ * occupy.
1061
+ * @param {Number} size_y The number of rows that the group of cells
1062
+ * occupy.
1063
+ * @param {HTMLElement} $exclude Exclude widgets from being moved.
1064
+ * @return {Class} Returns the instance of the Gridster Class.
1065
+ */
1066
+ fn.remove_empty_cells = function(col, row, size_x, size_y, exclude) {
1067
+ var $nexts = this.widgets_below({
1068
+ col: col,
1069
+ row: row,
1070
+ size_x: size_x,
1071
+ size_y: size_y
1072
+ });
1073
+
1074
+ $nexts.not(exclude).each($.proxy(function(i, widget) {
1075
+ this.move_widget_up( $(widget), size_y );
1076
+ }, this));
1077
+
1078
+ this.set_dom_grid_height();
1079
+
1080
+ return this;
1081
+ };
1082
+
1083
+
1084
+ /**
1085
+ * Get the most left column below to add a new widget.
1086
+ *
1087
+ * @method next_position
1088
+ * @param {Number} size_x The nº of rows the widget occupies horizontally.
1089
+ * @param {Number} size_y The nº of columns the widget occupies vertically.
1090
+ * @return {Object} Returns a grid coords object representing the future
1091
+ * widget coords.
1092
+ */
1093
+ fn.next_position = function(size_x, size_y) {
1094
+ size_x || (size_x = 1);
1095
+ size_y || (size_y = 1);
1096
+ var ga = this.gridmap;
1097
+ var cols_l = ga.length;
1098
+ var valid_pos = [];
1099
+ var rows_l;
1100
+
1101
+ for (var c = 1; c < cols_l; c++) {
1102
+ rows_l = ga[c].length;
1103
+ for (var r = 1; r <= rows_l; r++) {
1104
+ var can_move_to = this.can_move_to({
1105
+ size_x: size_x,
1106
+ size_y: size_y
1107
+ }, c, r);
1108
+
1109
+ if (can_move_to) {
1110
+ valid_pos.push({
1111
+ col: c,
1112
+ row: r,
1113
+ size_y: size_y,
1114
+ size_x: size_x
1115
+ });
1116
+ }
1117
+ }
1118
+ }
1119
+
1120
+ if (valid_pos.length) {
1121
+ return this.sort_by_row_and_col_asc(valid_pos)[0];
1122
+ }
1123
+ return false;
1124
+ };
1125
+
1126
+
1127
+ /**
1128
+ * Remove a widget from the grid.
1129
+ *
1130
+ * @method remove_widget
1131
+ * @param {HTMLElement} el The jQuery wrapped HTMLElement you want to remove.
1132
+ * @param {Boolean|Function} silent If true, widgets below the removed one
1133
+ * will not move up. If a Function is passed it will be used as callback.
1134
+ * @param {Function} callback Function executed when the widget is removed.
1135
+ * @return {Class} Returns the instance of the Gridster Class.
1136
+ */
1137
+ fn.remove_widget = function(el, silent, callback) {
1138
+ var $el = el instanceof jQuery ? el : $(el);
1139
+ var wgd = $el.coords().grid;
1140
+
1141
+ // if silent is a function assume it's a callback
1142
+ if ($.isFunction(silent)) {
1143
+ callback = silent;
1144
+ silent = false;
1145
+ }
1146
+
1147
+ this.cells_occupied_by_placeholder = {};
1148
+ this.$widgets = this.$widgets.not($el);
1149
+
1150
+ var $nexts = this.widgets_below($el);
1151
+
1152
+ this.remove_from_gridmap(wgd);
1153
+
1154
+ $el.fadeOut($.proxy(function() {
1155
+ $el.remove();
1156
+
1157
+ if (!silent) {
1158
+ $nexts.each($.proxy(function(i, widget) {
1159
+ this.move_widget_up( $(widget), wgd.size_y );
1160
+ }, this));
1161
+ }
1162
+
1163
+ this.set_dom_grid_height();
1164
+
1165
+ if (callback) {
1166
+ callback.call(this, el);
1167
+ }
1168
+ }, this));
1169
+ };
1170
+
1171
+
1172
+ /**
1173
+ * Remove all widgets from the grid.
1174
+ *
1175
+ * @method remove_all_widgets
1176
+ * @param {Function} callback Function executed for each widget removed.
1177
+ * @return {Class} Returns the instance of the Gridster Class.
1178
+ */
1179
+ fn.remove_all_widgets = function(callback) {
1180
+ this.$widgets.each($.proxy(function(i, el){
1181
+ this.remove_widget(el, true, callback);
1182
+ }, this));
1183
+
1184
+ return this;
1185
+ };
1186
+
1187
+
1188
+ /**
1189
+ * Returns a serialized array of the widgets in the grid.
1190
+ *
1191
+ * @method serialize
1192
+ * @param {HTMLElement} [$widgets] The collection of jQuery wrapped
1193
+ * HTMLElements you want to serialize. If no argument is passed all widgets
1194
+ * will be serialized.
1195
+ * @return {Array} Returns an Array of Objects with the data specified in
1196
+ * the serialize_params option.
1197
+ */
1198
+ fn.serialize = function($widgets) {
1199
+ $widgets || ($widgets = this.$widgets);
1200
+ var result = [];
1201
+ $widgets.each($.proxy(function(i, widget) {
1202
+ result.push(this.options.serialize_params(
1203
+ $(widget), $(widget).coords().grid ) );
1204
+ }, this));
1205
+
1206
+ return result;
1207
+ };
1208
+
1209
+
1210
+ /**
1211
+ * Returns a serialized array of the widgets that have changed their
1212
+ * position.
1213
+ *
1214
+ * @method serialize_changed
1215
+ * @return {Array} Returns an Array of Objects with the data specified in
1216
+ * the serialize_params option.
1217
+ */
1218
+ fn.serialize_changed = function() {
1219
+ return this.serialize(this.$changed);
1220
+ };
1221
+
1222
+
1223
+ /**
1224
+ * Creates the grid coords object representing the widget a add it to the
1225
+ * mapped array of positions.
1226
+ *
1227
+ * @method register_widget
1228
+ * @return {Array} Returns the instance of the Gridster class.
1229
+ */
1230
+ fn.register_widget = function($el) {
1231
+
1232
+ var wgd = {
1233
+ 'col': parseInt($el.attr('data-col'), 10),
1234
+ 'row': parseInt($el.attr('data-row'), 10),
1235
+ 'size_x': parseInt($el.attr('data-sizex'), 10),
1236
+ 'size_y': parseInt($el.attr('data-sizey'), 10),
1237
+ 'el': $el
1238
+ };
1239
+
1240
+ if (this.options.avoid_overlapped_widgets &&
1241
+ !this.can_move_to(
1242
+ {size_x: wgd.size_x, size_y: wgd.size_y}, wgd.col, wgd.row)
1243
+ ) {
1244
+ wgd = this.next_position(wgd.size_x, wgd.size_y);
1245
+ wgd.el = $el;
1246
+ $el.attr({
1247
+ 'data-col': wgd.col,
1248
+ 'data-row': wgd.row,
1249
+ 'data-sizex': wgd.size_x,
1250
+ 'data-sizey': wgd.size_y
1251
+ });
1252
+ }
1253
+
1254
+ // attach Coord object to player data-coord attribute
1255
+ $el.data('coords', $el.coords());
1256
+
1257
+ // Extend Coord object with grid position info
1258
+ $el.data('coords').grid = wgd;
1259
+
1260
+ this.add_to_gridmap(wgd, $el);
1261
+
1262
+ return this;
1263
+ };
1264
+
1265
+
1266
+ /**
1267
+ * Update in the mapped array of positions the value of cells represented by
1268
+ * the grid coords object passed in the `grid_data` param.
1269
+ *
1270
+ * @param {Object} grid_data The grid coords object representing the cells
1271
+ * to update in the mapped array.
1272
+ * @param {HTMLElement|Boolean} value Pass `false` or the jQuery wrapped
1273
+ * HTMLElement, depends if you want to delete an existing position or add
1274
+ * a new one.
1275
+ * @method update_widget_position
1276
+ * @return {Class} Returns the instance of the Gridster Class.
1277
+ */
1278
+ fn.update_widget_position = function(grid_data, value) {
1279
+ this.for_each_cell_occupied(grid_data, function(col, row) {
1280
+ if (!this.gridmap[col]) { return this; }
1281
+ this.gridmap[col][row] = value;
1282
+ });
1283
+ return this;
1284
+ };
1285
+
1286
+
1287
+ /**
1288
+ * Remove a widget from the mapped array of positions.
1289
+ *
1290
+ * @method remove_from_gridmap
1291
+ * @param {Object} grid_data The grid coords object representing the cells
1292
+ * to update in the mapped array.
1293
+ * @return {Class} Returns the instance of the Gridster Class.
1294
+ */
1295
+ fn.remove_from_gridmap = function(grid_data) {
1296
+ return this.update_widget_position(grid_data, false);
1297
+ };
1298
+
1299
+
1300
+ /**
1301
+ * Add a widget to the mapped array of positions.
1302
+ *
1303
+ * @method add_to_gridmap
1304
+ * @param {Object} grid_data The grid coords object representing the cells
1305
+ * to update in the mapped array.
1306
+ * @param {HTMLElement|Boolean} value The value to set in the specified
1307
+ * position .
1308
+ * @return {Class} Returns the instance of the Gridster Class.
1309
+ */
1310
+ fn.add_to_gridmap = function(grid_data, value) {
1311
+ this.update_widget_position(grid_data, value || grid_data.el);
1312
+
1313
+ if (grid_data.el) {
1314
+ var $widgets = this.widgets_below(grid_data.el);
1315
+ $widgets.each($.proxy(function(i, widget) {
1316
+ this.move_widget_up( $(widget));
1317
+ }, this));
1318
+ }
1319
+ };
1320
+
1321
+
1322
+ /**
1323
+ * Make widgets draggable.
1324
+ *
1325
+ * @uses Draggable
1326
+ * @method draggable
1327
+ * @return {Class} Returns the instance of the Gridster Class.
1328
+ */
1329
+ fn.draggable = function() {
1330
+ var self = this;
1331
+ var draggable_options = $.extend(true, {}, this.options.draggable, {
1332
+ offset_left: this.options.widget_margins[0],
1333
+ start: function(event, ui) {
1334
+ self.$widgets.filter('.player-revert')
1335
+ .removeClass('player-revert');
1336
+
1337
+ self.$player = $(this);
1338
+ self.$helper = self.options.draggable.helper === 'clone' ?
1339
+ $(ui.helper) : self.$player;
1340
+ self.helper = !self.$helper.is(self.$player);
1341
+
1342
+ self.on_start_drag.call(self, event, ui);
1343
+ self.$el.trigger('gridster:dragstart');
1344
+ },
1345
+ stop: function(event, ui) {
1346
+ self.on_stop_drag.call(self, event, ui);
1347
+ self.$el.trigger('gridster:dragstop');
1348
+ },
1349
+ drag: throttle(function(event, ui) {
1350
+ self.on_drag.call(self, event, ui);
1351
+ self.$el.trigger('gridster:drag');
1352
+ }, 60)
1353
+ });
1354
+
1355
+ this.drag_api = this.$el.drag(draggable_options).data('drag');
1356
+ return this;
1357
+ };
1358
+
1359
+
1360
+ /**
1361
+ * This function is executed when the player begins to be dragged.
1362
+ *
1363
+ * @method on_start_drag
1364
+ * @param {Event} The original browser event
1365
+ * @param {Object} A prepared ui object.
1366
+ */
1367
+ fn.on_start_drag = function(event, ui) {
1368
+
1369
+ this.$helper.add(this.$player).add(this.$wrapper).addClass('dragging');
1370
+
1371
+ this.$player.addClass('player');
1372
+ this.player_grid_data = this.$player.coords().grid;
1373
+ this.placeholder_grid_data = $.extend({}, this.player_grid_data);
1374
+
1375
+ //set new grid height along the dragging period
1376
+ this.$el.css('height', this.$el.height() +
1377
+ (this.player_grid_data.size_y * this.min_widget_height));
1378
+
1379
+ var colliders = this.faux_grid;
1380
+ var coords = this.$player.data('coords').coords;
1381
+
1382
+ this.cells_occupied_by_player = this.get_cells_occupied(
1383
+ this.player_grid_data);
1384
+ this.cells_occupied_by_placeholder = this.get_cells_occupied(
1385
+ this.placeholder_grid_data);
1386
+
1387
+ this.last_cols = [];
1388
+ this.last_rows = [];
1389
+
1390
+
1391
+ // see jquery.collision.js
1392
+ this.collision_api = this.$helper.collision(
1393
+ colliders, this.options.collision);
1394
+
1395
+ this.$preview_holder = $('<li />', {
1396
+ 'class': 'preview-holder',
1397
+ 'data-row': this.$player.attr('data-row'),
1398
+ 'data-col': this.$player.attr('data-col'),
1399
+ css: {
1400
+ width: coords.width,
1401
+ height: coords.height
1402
+ }
1403
+ }).appendTo(this.$el);
1404
+
1405
+ if (this.options.draggable.start) {
1406
+ this.options.draggable.start.call(this, event, ui);
1407
+ }
1408
+ };
1409
+
1410
+
1411
+ /**
1412
+ * This function is executed when the player is being dragged.
1413
+ *
1414
+ * @method on_drag
1415
+ * @param {Event} The original browser event
1416
+ * @param {Object} A prepared ui object.
1417
+ */
1418
+ fn.on_drag = function(event, ui) {
1419
+ //break if dragstop has been fired
1420
+ if (this.$player === null) {
1421
+ return false;
1422
+ }
1423
+
1424
+ var abs_offset = {
1425
+ left: ui.position.left + this.baseX,
1426
+ top: ui.position.top + this.baseY
1427
+ };
1428
+
1429
+ this.colliders_data = this.collision_api.get_closest_colliders(
1430
+ abs_offset);
1431
+
1432
+ this.on_overlapped_column_change(
1433
+ this.on_start_overlapping_column,
1434
+ this.on_stop_overlapping_column
1435
+ );
1436
+
1437
+ this.on_overlapped_row_change(
1438
+ this.on_start_overlapping_row,
1439
+ this.on_stop_overlapping_row
1440
+ );
1441
+
1442
+ if (this.helper && this.$player) {
1443
+ this.$player.css({
1444
+ 'left': ui.position.left,
1445
+ 'top': ui.position.top
1446
+ });
1447
+ }
1448
+
1449
+ if (this.options.draggable.drag) {
1450
+ this.options.draggable.drag.call(this, event, ui);
1451
+ }
1452
+ };
1453
+
1454
+ /**
1455
+ * This function is executed when the player stops being dragged.
1456
+ *
1457
+ * @method on_stop_drag
1458
+ * @param {Event} The original browser event
1459
+ * @param {Object} A prepared ui object.
1460
+ */
1461
+ fn.on_stop_drag = function(event, ui) {
1462
+ this.$helper.add(this.$player).add(this.$wrapper)
1463
+ .removeClass('dragging');
1464
+
1465
+ ui.position.left = ui.position.left + this.baseX;
1466
+ ui.position.top = ui.position.top + this.baseY;
1467
+ this.colliders_data = this.collision_api.get_closest_colliders(ui.position);
1468
+
1469
+ this.on_overlapped_column_change(
1470
+ this.on_start_overlapping_column,
1471
+ this.on_stop_overlapping_column
1472
+ );
1473
+
1474
+ this.on_overlapped_row_change(
1475
+ this.on_start_overlapping_row,
1476
+ this.on_stop_overlapping_row
1477
+ );
1478
+
1479
+ this.$player.addClass('player-revert').removeClass('player')
1480
+ .attr({
1481
+ 'data-col': this.placeholder_grid_data.col,
1482
+ 'data-row': this.placeholder_grid_data.row
1483
+ }).css({
1484
+ 'left': '',
1485
+ 'top': ''
1486
+ });
1487
+
1488
+ this.$changed = this.$changed.add(this.$player);
1489
+
1490
+ this.cells_occupied_by_player = this.get_cells_occupied(
1491
+ this.placeholder_grid_data);
1492
+ this.set_cells_player_occupies(
1493
+ this.placeholder_grid_data.col, this.placeholder_grid_data.row);
1494
+
1495
+ this.$player.coords().grid.row = this.placeholder_grid_data.row;
1496
+ this.$player.coords().grid.col = this.placeholder_grid_data.col;
1497
+
1498
+ if (this.options.draggable.stop) {
1499
+ this.options.draggable.stop.call(this, event, ui);
1500
+ }
1501
+
1502
+ this.$preview_holder.remove();
1503
+
1504
+ this.$player = null;
1505
+ this.$helper = null;
1506
+ this.placeholder_grid_data = {};
1507
+ this.player_grid_data = {};
1508
+ this.cells_occupied_by_placeholder = {};
1509
+ this.cells_occupied_by_player = {};
1510
+
1511
+ this.set_dom_grid_height();
1512
+ };
1513
+
1514
+
1515
+ /**
1516
+ * Executes the callbacks passed as arguments when a column begins to be
1517
+ * overlapped or stops being overlapped.
1518
+ *
1519
+ * @param {Function} start_callback Function executed when a new column
1520
+ * begins to be overlapped. The column is passed as first argument.
1521
+ * @param {Function} stop_callback Function executed when a column stops
1522
+ * being overlapped. The column is passed as first argument.
1523
+ * @method on_overlapped_column_change
1524
+ * @return {Class} Returns the instance of the Gridster Class.
1525
+ */
1526
+ fn.on_overlapped_column_change = function(start_callback, stop_callback) {
1527
+ if (!this.colliders_data.length) {
1528
+ return;
1529
+ }
1530
+ var cols = this.get_targeted_columns(
1531
+ this.colliders_data[0].el.data.col);
1532
+
1533
+ var last_n_cols = this.last_cols.length;
1534
+ var n_cols = cols.length;
1535
+ var i;
1536
+
1537
+ for (i = 0; i < n_cols; i++) {
1538
+ if ($.inArray(cols[i], this.last_cols) === -1) {
1539
+ (start_callback || $.noop).call(this, cols[i]);
1540
+ }
1541
+ }
1542
+
1543
+ for (i = 0; i< last_n_cols; i++) {
1544
+ if ($.inArray(this.last_cols[i], cols) === -1) {
1545
+ (stop_callback || $.noop).call(this, this.last_cols[i]);
1546
+ }
1547
+ }
1548
+
1549
+ this.last_cols = cols;
1550
+
1551
+ return this;
1552
+ };
1553
+
1554
+
1555
+ /**
1556
+ * Executes the callbacks passed as arguments when a row starts to be
1557
+ * overlapped or stops being overlapped.
1558
+ *
1559
+ * @param {Function} start_callback Function executed when a new row begins
1560
+ * to be overlapped. The row is passed as first argument.
1561
+ * @param {Function} stop_callback Function executed when a row stops being
1562
+ * overlapped. The row is passed as first argument.
1563
+ * @method on_overlapped_row_change
1564
+ * @return {Class} Returns the instance of the Gridster Class.
1565
+ */
1566
+ fn.on_overlapped_row_change = function(start_callback, end_callback) {
1567
+ if (!this.colliders_data.length) {
1568
+ return;
1569
+ }
1570
+ var rows = this.get_targeted_rows(this.colliders_data[0].el.data.row);
1571
+ var last_n_rows = this.last_rows.length;
1572
+ var n_rows = rows.length;
1573
+ var i;
1574
+
1575
+ for (i = 0; i < n_rows; i++) {
1576
+ if ($.inArray(rows[i], this.last_rows) === -1) {
1577
+ (start_callback || $.noop).call(this, rows[i]);
1578
+ }
1579
+ }
1580
+
1581
+ for (i = 0; i < last_n_rows; i++) {
1582
+ if ($.inArray(this.last_rows[i], rows) === -1) {
1583
+ (end_callback || $.noop).call(this, this.last_rows[i]);
1584
+ }
1585
+ }
1586
+
1587
+ this.last_rows = rows;
1588
+ };
1589
+
1590
+
1591
+ /**
1592
+ * Sets the current position of the player
1593
+ *
1594
+ * @param {Function} start_callback Function executed when a new row begins
1595
+ * to be overlapped. The row is passed as first argument.
1596
+ * @param {Function} stop_callback Function executed when a row stops being
1597
+ * overlapped. The row is passed as first argument.
1598
+ * @method set_player
1599
+ * @return {Class} Returns the instance of the Gridster Class.
1600
+ */
1601
+ fn.set_player = function(col, row, no_player) {
1602
+ var self = this;
1603
+ if (!no_player) {
1604
+ this.empty_cells_player_occupies();
1605
+ }
1606
+ var cell = !no_player ? self.colliders_data[0].el.data : {col: col};
1607
+ var to_col = cell.col;
1608
+ var to_row = row || cell.row;
1609
+
1610
+ this.player_grid_data = {
1611
+ col: to_col,
1612
+ row: to_row,
1613
+ size_y : this.player_grid_data.size_y,
1614
+ size_x : this.player_grid_data.size_x
1615
+ };
1616
+
1617
+ this.cells_occupied_by_player = this.get_cells_occupied(
1618
+ this.player_grid_data);
1619
+
1620
+ var $overlapped_widgets = this.get_widgets_overlapped(
1621
+ this.player_grid_data);
1622
+
1623
+ var constraints = this.widgets_constraints($overlapped_widgets);
1624
+
1625
+ this.manage_movements(constraints.can_go_up, to_col, to_row);
1626
+ this.manage_movements(constraints.can_not_go_up, to_col, to_row);
1627
+
1628
+ /* if there is not widgets overlapping in the new player position,
1629
+ * update the new placeholder position. */
1630
+ if (!$overlapped_widgets.length) {
1631
+ var pp = this.can_go_player_up(this.player_grid_data);
1632
+ if (pp !== false) {
1633
+ to_row = pp;
1634
+ }
1635
+ this.set_placeholder(to_col, to_row);
1636
+ }
1637
+
1638
+ return {
1639
+ col: to_col,
1640
+ row: to_row
1641
+ };
1642
+ };
1643
+
1644
+
1645
+ /**
1646
+ * See which of the widgets in the $widgets param collection can go to
1647
+ * a upper row and which not.
1648
+ *
1649
+ * @method widgets_contraints
1650
+ * @param {HTMLElements} $widgets A jQuery wrapped collection of
1651
+ * HTMLElements.
1652
+ * @return {Array} Returns a literal Object with two keys: `can_go_up` &
1653
+ * `can_not_go_up`. Each contains a set of HTMLElements.
1654
+ */
1655
+ fn.widgets_constraints = function($widgets) {
1656
+ var $widgets_can_go_up = $([]);
1657
+ var $widgets_can_not_go_up;
1658
+ var wgd_can_go_up = [];
1659
+ var wgd_can_not_go_up = [];
1660
+
1661
+ $widgets.each($.proxy(function(i, w) {
1662
+ var $w = $(w);
1663
+ var wgd = $w.coords().grid;
1664
+ if (this.can_go_widget_up(wgd)) {
1665
+ $widgets_can_go_up = $widgets_can_go_up.add($w);
1666
+ wgd_can_go_up.push(wgd);
1667
+ }else{
1668
+ wgd_can_not_go_up.push(wgd);
1669
+ }
1670
+ }, this));
1671
+
1672
+ $widgets_can_not_go_up = $widgets.not($widgets_can_go_up);
1673
+
1674
+ return {
1675
+ can_go_up: this.sort_by_row_asc(wgd_can_go_up),
1676
+ can_not_go_up: this.sort_by_row_desc(wgd_can_not_go_up)
1677
+ };
1678
+ };
1679
+
1680
+
1681
+ /**
1682
+ * Sorts an Array of grid coords objects (representing the grid coords of
1683
+ * each widget) in ascending way.
1684
+ *
1685
+ * @method sort_by_row_asc
1686
+ * @param {Array} widgets Array of grid coords objects
1687
+ * @return {Array} Returns the array sorted.
1688
+ */
1689
+ fn.sort_by_row_asc = function(widgets) {
1690
+ widgets = widgets.sort(function(a, b) {
1691
+ if (!a.row) {
1692
+ a = $(a).coords().grid;
1693
+ b = $(b).coords().grid;
1694
+ }
1695
+
1696
+ if (a.row > b.row) {
1697
+ return 1;
1698
+ }
1699
+ return -1;
1700
+ });
1701
+
1702
+ return widgets;
1703
+ };
1704
+
1705
+
1706
+ /**
1707
+ * Sorts an Array of grid coords objects (representing the grid coords of
1708
+ * each widget) placing first the empty cells upper left.
1709
+ *
1710
+ * @method sort_by_row_and_col_asc
1711
+ * @param {Array} widgets Array of grid coords objects
1712
+ * @return {Array} Returns the array sorted.
1713
+ */
1714
+ fn.sort_by_row_and_col_asc = function(widgets) {
1715
+ widgets = widgets.sort(function(a, b) {
1716
+ if (a.row > b.row || a.row === b.row && a.col > b.col) {
1717
+ return 1;
1718
+ }
1719
+ return -1;
1720
+ });
1721
+
1722
+ return widgets;
1723
+ };
1724
+
1725
+
1726
+ /**
1727
+ * Sorts an Array of grid coords objects by column (representing the grid
1728
+ * coords of each widget) in ascending way.
1729
+ *
1730
+ * @method sort_by_col_asc
1731
+ * @param {Array} widgets Array of grid coords objects
1732
+ * @return {Array} Returns the array sorted.
1733
+ */
1734
+ fn.sort_by_col_asc = function(widgets) {
1735
+ widgets = widgets.sort(function(a, b) {
1736
+ if (a.col > b.col) {
1737
+ return 1;
1738
+ }
1739
+ return -1;
1740
+ });
1741
+
1742
+ return widgets;
1743
+ };
1744
+
1745
+
1746
+ /**
1747
+ * Sorts an Array of grid coords objects (representing the grid coords of
1748
+ * each widget) in descending way.
1749
+ *
1750
+ * @method sort_by_row_desc
1751
+ * @param {Array} widgets Array of grid coords objects
1752
+ * @return {Array} Returns the array sorted.
1753
+ */
1754
+ fn.sort_by_row_desc = function(widgets) {
1755
+ widgets = widgets.sort(function(a, b) {
1756
+ if (a.row + a.size_y < b.row + b.size_y) {
1757
+ return 1;
1758
+ }
1759
+ return -1;
1760
+ });
1761
+ return widgets;
1762
+ };
1763
+
1764
+
1765
+ /**
1766
+ * Sorts an Array of grid coords objects (representing the grid coords of
1767
+ * each widget) in descending way.
1768
+ *
1769
+ * @method manage_movements
1770
+ * @param {HTMLElements} $widgets A jQuery collection of HTMLElements
1771
+ * representing the widgets you want to move.
1772
+ * @param {Number} to_col The column to which we want to move the widgets.
1773
+ * @param {Number} to_row The row to which we want to move the widgets.
1774
+ * @return {Class} Returns the instance of the Gridster Class.
1775
+ */
1776
+ fn.manage_movements = function($widgets, to_col, to_row) {
1777
+ $.each($widgets, $.proxy(function(i, w) {
1778
+ var wgd = w;
1779
+ var $w = wgd.el;
1780
+
1781
+ var can_go_widget_up = this.can_go_widget_up(wgd);
1782
+
1783
+ if (can_go_widget_up) {
1784
+ //target CAN go up
1785
+ //so move widget up
1786
+ this.move_widget_to($w, can_go_widget_up);
1787
+ this.set_placeholder(to_col, can_go_widget_up + wgd.size_y);
1788
+
1789
+ } else {
1790
+ //target can't go up
1791
+ var can_go_player_up = this.can_go_player_up(
1792
+ this.player_grid_data);
1793
+
1794
+ if (!can_go_player_up) {
1795
+ // target can't go up
1796
+ // player cant't go up
1797
+ // so we need to move widget down to a position that dont
1798
+ // overlaps player
1799
+ var y = (to_row + this.player_grid_data.size_y) - wgd.row;
1800
+
1801
+ this.move_widget_down($w, y);
1802
+ this.set_placeholder(to_col, to_row);
1803
+ }
1804
+ }
1805
+ }, this));
1806
+
1807
+ return this;
1808
+ };
1809
+
1810
+ /**
1811
+ * Determines if there is a widget in the row and col given. Or if the
1812
+ * HTMLElement passed as first argument is the player.
1813
+ *
1814
+ * @method is_player
1815
+ * @param {Number|HTMLElement} col_or_el A jQuery wrapped collection of
1816
+ * HTMLElements.
1817
+ * @param {Number} [row] The column to which we want to move the widgets.
1818
+ * @return {Boolean} Returns true or false.
1819
+ */
1820
+ fn.is_player = function(col_or_el, row) {
1821
+ if (row && !this.gridmap[col_or_el]) { return false; }
1822
+ var $w = row ? this.gridmap[col_or_el][row] : col_or_el;
1823
+ return $w && ($w.is(this.$player) || $w.is(this.$helper));
1824
+ };
1825
+
1826
+
1827
+ /**
1828
+ * Determines if the widget that is being dragged is currently over the row
1829
+ * and col given.
1830
+ *
1831
+ * @method is_player_in
1832
+ * @param {Number} col The column to check.
1833
+ * @param {Number} row The row to check.
1834
+ * @return {Boolean} Returns true or false.
1835
+ */
1836
+ fn.is_player_in = function(col, row) {
1837
+ var c = this.cells_occupied_by_player || {};
1838
+ return $.inArray(col, c.cols) >= 0 && $.inArray(row, c.rows) >= 0;
1839
+ };
1840
+
1841
+
1842
+ /**
1843
+ * Determines if the placeholder is currently over the row and col given.
1844
+ *
1845
+ * @method is_placeholder_in
1846
+ * @param {Number} col The column to check.
1847
+ * @param {Number} row The row to check.
1848
+ * @return {Boolean} Returns true or false.
1849
+ */
1850
+ fn.is_placeholder_in = function(col, row) {
1851
+ var c = this.cells_occupied_by_placeholder || {};
1852
+ return this.is_placeholder_in_col(col) && $.inArray(row, c.rows) >= 0;
1853
+ };
1854
+
1855
+
1856
+ /**
1857
+ * Determines if the placeholder is currently over the column given.
1858
+ *
1859
+ * @method is_placeholder_in_col
1860
+ * @param {Number} col The column to check.
1861
+ * @return {Boolean} Returns true or false.
1862
+ */
1863
+ fn.is_placeholder_in_col = function(col) {
1864
+ var c = this.cells_occupied_by_placeholder || [];
1865
+ return $.inArray(col, c.cols) >= 0;
1866
+ };
1867
+
1868
+
1869
+ /**
1870
+ * Determines if the cell represented by col and row params is empty.
1871
+ *
1872
+ * @method is_empty
1873
+ * @param {Number} col The column to check.
1874
+ * @param {Number} row The row to check.
1875
+ * @return {Boolean} Returns true or false.
1876
+ */
1877
+ fn.is_empty = function(col, row) {
1878
+ if (typeof this.gridmap[col] !== 'undefined' &&
1879
+ typeof this.gridmap[col][row] !== 'undefined' &&
1880
+ this.gridmap[col][row] === false
1881
+ ) {
1882
+ return true;
1883
+ }
1884
+ return false;
1885
+ };
1886
+
1887
+
1888
+ /**
1889
+ * Determines if the cell represented by col and row params is occupied.
1890
+ *
1891
+ * @method is_occupied
1892
+ * @param {Number} col The column to check.
1893
+ * @param {Number} row The row to check.
1894
+ * @return {Boolean} Returns true or false.
1895
+ */
1896
+ fn.is_occupied = function(col, row) {
1897
+ if (!this.gridmap[col]) {
1898
+ return false;
1899
+ }
1900
+
1901
+ if (this.gridmap[col][row]) {
1902
+ return true;
1903
+ }
1904
+ return false;
1905
+ };
1906
+
1907
+
1908
+ /**
1909
+ * Determines if there is a widget in the cell represented by col/row params.
1910
+ *
1911
+ * @method is_widget
1912
+ * @param {Number} col The column to check.
1913
+ * @param {Number} row The row to check.
1914
+ * @return {Boolean|HTMLElement} Returns false if there is no widget,
1915
+ * else returns the jQuery HTMLElement
1916
+ */
1917
+ fn.is_widget = function(col, row) {
1918
+ var cell = this.gridmap[col];
1919
+ if (!cell) {
1920
+ return false;
1921
+ }
1922
+
1923
+ cell = cell[row];
1924
+
1925
+ if (cell) {
1926
+ return cell;
1927
+ }
1928
+
1929
+ return false;
1930
+ };
1931
+
1932
+
1933
+ /**
1934
+ * Determines if there is a widget in the cell represented by col/row
1935
+ * params and if this is under the widget that is being dragged.
1936
+ *
1937
+ * @method is_widget_under_player
1938
+ * @param {Number} col The column to check.
1939
+ * @param {Number} row The row to check.
1940
+ * @return {Boolean} Returns true or false.
1941
+ */
1942
+ fn.is_widget_under_player = function(col, row) {
1943
+ if (this.is_widget(col, row)) {
1944
+ return this.is_player_in(col, row);
1945
+ }
1946
+ return false;
1947
+ };
1948
+
1949
+
1950
+ /**
1951
+ * Get widgets overlapping with the player or with the object passed
1952
+ * representing the grid cells.
1953
+ *
1954
+ * @method get_widgets_under_player
1955
+ * @return {HTMLElement} Returns a jQuery collection of HTMLElements
1956
+ */
1957
+ fn.get_widgets_under_player = function(cells) {
1958
+ cells || (cells = this.cells_occupied_by_player || {cols: [], rows: []});
1959
+ var $widgets = $([]);
1960
+
1961
+ $.each(cells.cols, $.proxy(function(i, col) {
1962
+ $.each(cells.rows, $.proxy(function(i, row) {
1963
+ if(this.is_widget(col, row)) {
1964
+ $widgets = $widgets.add(this.gridmap[col][row]);
1965
+ }
1966
+ }, this));
1967
+ }, this));
1968
+
1969
+ return $widgets;
1970
+ };
1971
+
1972
+
1973
+ /**
1974
+ * Put placeholder at the row and column specified.
1975
+ *
1976
+ * @method set_placeholder
1977
+ * @param {Number} col The column to which we want to move the
1978
+ * placeholder.
1979
+ * @param {Number} row The row to which we want to move the
1980
+ * placeholder.
1981
+ * @return {Class} Returns the instance of the Gridster Class.
1982
+ */
1983
+ fn.set_placeholder = function(col, row) {
1984
+ var phgd = $.extend({}, this.placeholder_grid_data);
1985
+ var $nexts = this.widgets_below({
1986
+ col: phgd.col,
1987
+ row: phgd.row,
1988
+ size_y: phgd.size_y,
1989
+ size_x: phgd.size_x
1990
+ });
1991
+
1992
+ // Prevents widgets go out of the grid
1993
+ var right_col = (col + phgd.size_x - 1);
1994
+ if (right_col > this.cols) {
1995
+ col = col - (right_col - col);
1996
+ }
1997
+
1998
+ var moved_down = this.placeholder_grid_data.row < row;
1999
+ var changed_column = this.placeholder_grid_data.col !== col;
2000
+
2001
+ this.placeholder_grid_data.col = col;
2002
+ this.placeholder_grid_data.row = row;
2003
+
2004
+ this.cells_occupied_by_placeholder = this.get_cells_occupied(
2005
+ this.placeholder_grid_data);
2006
+
2007
+ this.$preview_holder.attr({
2008
+ 'data-row' : row,
2009
+ 'data-col' : col
2010
+ });
2011
+
2012
+ if (moved_down || changed_column) {
2013
+ $nexts.each($.proxy(function(i, widget) {
2014
+ this.move_widget_up(
2015
+ $(widget), this.placeholder_grid_data.col - col + phgd.size_y);
2016
+ }, this));
2017
+ }
2018
+
2019
+
2020
+ var $widgets_under_ph = this.get_widgets_under_player(this.cells_occupied_by_placeholder);
2021
+ if ($widgets_under_ph.length) {
2022
+ $widgets_under_ph.each($.proxy(function(i, widget) {
2023
+ var $w = $(widget);
2024
+ this.move_widget_down(
2025
+ $w, row + phgd.size_y - $w.data('coords').grid.row);
2026
+ }, this));
2027
+ }
2028
+
2029
+ };
2030
+
2031
+
2032
+ /**
2033
+ * Determines whether the player can move to a position above.
2034
+ *
2035
+ * @method can_go_player_up
2036
+ * @param {Object} widget_grid_data The actual grid coords object of the
2037
+ * player.
2038
+ * @return {Number|Boolean} If the player can be moved to an upper row
2039
+ * returns the row number, else returns false.
2040
+ */
2041
+ fn.can_go_player_up = function(widget_grid_data) {
2042
+ var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1;
2043
+ var result = true;
2044
+ var upper_rows = [];
2045
+ var min_row = 10000;
2046
+ var $widgets_under_player = this.get_widgets_under_player();
2047
+
2048
+ /* generate an array with columns as index and array with upper rows
2049
+ * empty as value */
2050
+ this.for_each_column_occupied(widget_grid_data, function(tcol) {
2051
+ var grid_col = this.gridmap[tcol];
2052
+ var r = p_bottom_row + 1;
2053
+ upper_rows[tcol] = [];
2054
+
2055
+ while (--r > 0) {
2056
+ if (this.is_empty(tcol, r) || this.is_player(tcol, r) ||
2057
+ this.is_widget(tcol, r) &&
2058
+ grid_col[r].is($widgets_under_player)
2059
+ ) {
2060
+ upper_rows[tcol].push(r);
2061
+ min_row = r < min_row ? r : min_row;
2062
+ }else{
2063
+ break;
2064
+ }
2065
+ }
2066
+
2067
+ if (upper_rows[tcol].length === 0) {
2068
+ result = false;
2069
+ return true; //break
2070
+ }
2071
+
2072
+ upper_rows[tcol].sort();
2073
+ });
2074
+
2075
+ if (!result) { return false; }
2076
+
2077
+ return this.get_valid_rows(widget_grid_data, upper_rows, min_row);
2078
+ };
2079
+
2080
+
2081
+ /**
2082
+ * Determines whether a widget can move to a position above.
2083
+ *
2084
+ * @method can_go_widget_up
2085
+ * @param {Object} widget_grid_data The actual grid coords object of the
2086
+ * widget we want to check.
2087
+ * @return {Number|Boolean} If the widget can be moved to an upper row
2088
+ * returns the row number, else returns false.
2089
+ */
2090
+ fn.can_go_widget_up = function(widget_grid_data) {
2091
+ var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1;
2092
+ var result = true;
2093
+ var upper_rows = [];
2094
+ var min_row = 10000;
2095
+
2096
+ /* generate an array with columns as index and array with topmost rows
2097
+ * empty as value */
2098
+ this.for_each_column_occupied(widget_grid_data, function(tcol) {
2099
+ var grid_col = this.gridmap[tcol];
2100
+ upper_rows[tcol] = [];
2101
+
2102
+ var r = p_bottom_row + 1;
2103
+ // iterate over each row
2104
+ while (--r > 0) {
2105
+ if (this.is_widget(tcol, r) && !this.is_player_in(tcol, r)) {
2106
+ if (!grid_col[r].is(widget_grid_data.el)) {
2107
+ break;
2108
+ }
2109
+ }
2110
+
2111
+ if (!this.is_player(tcol, r) &&
2112
+ !this.is_placeholder_in(tcol, r) &&
2113
+ !this.is_player_in(tcol, r)) {
2114
+ upper_rows[tcol].push(r);
2115
+ }
2116
+
2117
+ if (r < min_row) {
2118
+ min_row = r;
2119
+ }
2120
+ }
2121
+
2122
+ if (upper_rows[tcol].length === 0) {
2123
+ result = false;
2124
+ return true; //break
2125
+ }
2126
+
2127
+ upper_rows[tcol].sort();
2128
+ });
2129
+
2130
+ if (!result) { return false; }
2131
+
2132
+ return this.get_valid_rows(widget_grid_data, upper_rows, min_row);
2133
+ };
2134
+
2135
+
2136
+ /**
2137
+ * Search a valid row for the widget represented by `widget_grid_data' in
2138
+ * the `upper_rows` array. Iteration starts from row specified in `min_row`.
2139
+ *
2140
+ * @method get_valid_rows
2141
+ * @param {Object} widget_grid_data The actual grid coords object of the
2142
+ * player.
2143
+ * @param {Array} upper_rows An array with columns as index and arrays
2144
+ * of valid rows as values.
2145
+ * @param {Number} min_row The upper row from which the iteration will start.
2146
+ * @return {Number|Boolean} Returns the upper row valid from the `upper_rows`
2147
+ * for the widget in question.
2148
+ */
2149
+ fn.get_valid_rows = function(widget_grid_data, upper_rows, min_row) {
2150
+ var p_top_row = widget_grid_data.row;
2151
+ var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1;
2152
+ var size_y = widget_grid_data.size_y;
2153
+ var r = min_row - 1;
2154
+ var valid_rows = [];
2155
+
2156
+ while (++r <= p_bottom_row ) {
2157
+ var common = true;
2158
+ $.each(upper_rows, function(col, rows) {
2159
+ if ($.isArray(rows) && $.inArray(r, rows) === -1) {
2160
+ common = false;
2161
+ }
2162
+ });
2163
+
2164
+ if (common === true) {
2165
+ valid_rows.push(r);
2166
+ if (valid_rows.length === size_y) {
2167
+ break;
2168
+ }
2169
+ }
2170
+ }
2171
+
2172
+ var new_row = false;
2173
+ if (size_y === 1) {
2174
+ if (valid_rows[0] !== p_top_row) {
2175
+ new_row = valid_rows[0] || false;
2176
+ }
2177
+ }else{
2178
+ if (valid_rows[0] !== p_top_row) {
2179
+ new_row = this.get_consecutive_numbers_index(
2180
+ valid_rows, size_y);
2181
+ }
2182
+ }
2183
+
2184
+ return new_row;
2185
+ };
2186
+
2187
+
2188
+ fn.get_consecutive_numbers_index = function(arr, size_y) {
2189
+ var max = arr.length;
2190
+ var result = [];
2191
+ var first = true;
2192
+ var prev = -1; // or null?
2193
+
2194
+ for (var i=0; i < max; i++) {
2195
+ if (first || arr[i] === prev + 1) {
2196
+ result.push(i);
2197
+ if (result.length === size_y) {
2198
+ break;
2199
+ }
2200
+ first = false;
2201
+ }else{
2202
+ result = [];
2203
+ first = true;
2204
+ }
2205
+
2206
+ prev = arr[i];
2207
+ }
2208
+
2209
+ return result.length >= size_y ? arr[result[0]] : false;
2210
+ };
2211
+
2212
+
2213
+ /**
2214
+ * Get widgets overlapping with the player.
2215
+ *
2216
+ * @method get_widgets_overlapped
2217
+ * @return {HTMLElements} Returns a jQuery collection of HTMLElements.
2218
+ */
2219
+ fn.get_widgets_overlapped = function() {
2220
+ var $w;
2221
+ var $widgets = $([]);
2222
+ var used = [];
2223
+ var rows_from_bottom = this.cells_occupied_by_player.rows.slice(0);
2224
+ rows_from_bottom.reverse();
2225
+
2226
+ $.each(this.cells_occupied_by_player.cols, $.proxy(function(i, col) {
2227
+ $.each(rows_from_bottom, $.proxy(function(i, row) {
2228
+ // if there is a widget in the player position
2229
+ if (!this.gridmap[col]) { return true; } //next iteration
2230
+ var $w = this.gridmap[col][row];
2231
+ if (this.is_occupied(col, row) && !this.is_player($w) &&
2232
+ $.inArray($w, used) === -1
2233
+ ) {
2234
+ $widgets = $widgets.add($w);
2235
+ used.push($w);
2236
+ }
2237
+
2238
+ }, this));
2239
+ }, this));
2240
+
2241
+ return $widgets;
2242
+ };
2243
+
2244
+
2245
+ /**
2246
+ * This callback is executed when the player begins to collide with a column.
2247
+ *
2248
+ * @method on_start_overlapping_column
2249
+ * @param {Number} col The collided column.
2250
+ * @return {HTMLElements} Returns a jQuery collection of HTMLElements.
2251
+ */
2252
+ fn.on_start_overlapping_column = function(col) {
2253
+ this.set_player(col, false);
2254
+ };
2255
+
2256
+
2257
+ /**
2258
+ * A callback executed when the player begins to collide with a row.
2259
+ *
2260
+ * @method on_start_overlapping_row
2261
+ * @param {Number} col The collided row.
2262
+ * @return {HTMLElements} Returns a jQuery collection of HTMLElements.
2263
+ */
2264
+ fn.on_start_overlapping_row = function(row) {
2265
+ this.set_player(false, row);
2266
+ };
2267
+
2268
+
2269
+ /**
2270
+ * A callback executed when the the player ends to collide with a column.
2271
+ *
2272
+ * @method on_stop_overlapping_column
2273
+ * @param {Number} col The collided row.
2274
+ * @return {HTMLElements} Returns a jQuery collection of HTMLElements.
2275
+ */
2276
+ fn.on_stop_overlapping_column = function(col) {
2277
+ this.set_player(col, false);
2278
+
2279
+ var self = this;
2280
+ this.for_each_widget_below(col, this.cells_occupied_by_player.rows[0],
2281
+ function(tcol, trow) {
2282
+ self.move_widget_up(this, self.player_grid_data.size_y);
2283
+ });
2284
+ };
2285
+
2286
+
2287
+ /**
2288
+ * This callback is executed when the player ends to collide with a row.
2289
+ *
2290
+ * @method on_stop_overlapping_row
2291
+ * @param {Number} row The collided row.
2292
+ * @return {HTMLElements} Returns a jQuery collection of HTMLElements.
2293
+ */
2294
+ fn.on_stop_overlapping_row = function(row) {
2295
+ this.set_player(false, row);
2296
+
2297
+ var self = this;
2298
+ var cols = this.cells_occupied_by_player.cols;
2299
+ for (var c = 0, cl = cols.length; c < cl; c++) {
2300
+ this.for_each_widget_below(cols[c], row, function(tcol, trow) {
2301
+ self.move_widget_up(this, self.player_grid_data.size_y);
2302
+ });
2303
+ }
2304
+ };
2305
+
2306
+
2307
+ /**
2308
+ * Move a widget to a specific row. The cell or cells must be empty.
2309
+ * If the widget has widgets below, all of these widgets will be moved also
2310
+ * if they can.
2311
+ *
2312
+ * @method move_widget_to
2313
+ * @param {HTMLElement} $widget The jQuery wrapped HTMLElement of the
2314
+ * widget is going to be moved.
2315
+ * @return {Class} Returns the instance of the Gridster Class.
2316
+ */
2317
+ fn.move_widget_to = function($widget, row) {
2318
+ var self = this;
2319
+ var widget_grid_data = $widget.coords().grid;
2320
+ var diff = row - widget_grid_data.row;
2321
+ var $next_widgets = this.widgets_below($widget);
2322
+
2323
+ var can_move_to_new_cell = this.can_move_to(
2324
+ widget_grid_data, widget_grid_data.col, row, $widget);
2325
+
2326
+ if (can_move_to_new_cell === false) {
2327
+ return false;
2328
+ }
2329
+
2330
+ this.remove_from_gridmap(widget_grid_data);
2331
+ widget_grid_data.row = row;
2332
+ this.add_to_gridmap(widget_grid_data);
2333
+ $widget.attr('data-row', row);
2334
+ this.$changed = this.$changed.add($widget);
2335
+
2336
+
2337
+ $next_widgets.each(function(i, widget) {
2338
+ var $w = $(widget);
2339
+ var wgd = $w.coords().grid;
2340
+ var can_go_up = self.can_go_widget_up(wgd);
2341
+ if (can_go_up && can_go_up !== wgd.row) {
2342
+ self.move_widget_to($w, can_go_up);
2343
+ }
2344
+ });
2345
+
2346
+ return this;
2347
+ };
2348
+
2349
+
2350
+ /**
2351
+ * Move up the specified widget and all below it.
2352
+ *
2353
+ * @method move_widget_up
2354
+ * @param {HTMLElement} $widget The widget you want to move.
2355
+ * @param {Number} [y_units] The number of cells that the widget has to move.
2356
+ * @return {Class} Returns the instance of the Gridster Class.
2357
+ */
2358
+ fn.move_widget_up = function($widget, y_units) {
2359
+ var el_grid_data = $widget.coords().grid;
2360
+ var actual_row = el_grid_data.row;
2361
+ var moved = [];
2362
+ var can_go_up = true;
2363
+ y_units || (y_units = 1);
2364
+
2365
+ if (!this.can_go_up($widget)) { return false; } //break;
2366
+
2367
+ this.for_each_column_occupied(el_grid_data, function(col) {
2368
+ // can_go_up
2369
+ if ($.inArray($widget, moved) === -1) {
2370
+ var widget_grid_data = $widget.coords().grid;
2371
+ var next_row = actual_row - y_units;
2372
+ next_row = this.can_go_up_to_row(
2373
+ widget_grid_data, col, next_row);
2374
+
2375
+ if (!next_row) {
2376
+ return true;
2377
+ }
2378
+
2379
+ var $next_widgets = this.widgets_below($widget);
2380
+
2381
+ this.remove_from_gridmap(widget_grid_data);
2382
+ widget_grid_data.row = next_row;
2383
+ this.add_to_gridmap(widget_grid_data);
2384
+ $widget.attr('data-row', widget_grid_data.row);
2385
+ this.$changed = this.$changed.add($widget);
2386
+
2387
+ moved.push($widget);
2388
+
2389
+ $next_widgets.each($.proxy(function(i, widget) {
2390
+ this.move_widget_up($(widget), y_units);
2391
+ }, this));
2392
+ }
2393
+ });
2394
+
2395
+ };
2396
+
2397
+
2398
+ /**
2399
+ * Move down the specified widget and all below it.
2400
+ *
2401
+ * @method move_widget_down
2402
+ * @param {HTMLElement} $widget The jQuery object representing the widget
2403
+ * you want to move.
2404
+ * @param {Number} The number of cells that the widget has to move.
2405
+ * @return {Class} Returns the instance of the Gridster Class.
2406
+ */
2407
+ fn.move_widget_down = function($widget, y_units) {
2408
+ var el_grid_data = $widget.coords().grid;
2409
+ var actual_row = el_grid_data.row;
2410
+ var moved = [];
2411
+ var y_diff = y_units;
2412
+
2413
+ if (!$widget) { return false; }
2414
+
2415
+ if ($.inArray($widget, moved) === -1) {
2416
+
2417
+ var widget_grid_data = $widget.coords().grid;
2418
+ var next_row = actual_row + y_units;
2419
+ var $next_widgets = this.widgets_below($widget);
2420
+
2421
+ this.remove_from_gridmap(widget_grid_data);
2422
+
2423
+ $next_widgets.each($.proxy(function(i, widget) {
2424
+ var $w = $(widget);
2425
+ var wd = $w.coords().grid;
2426
+ var tmp_y = this.displacement_diff(
2427
+ wd, widget_grid_data, y_diff);
2428
+
2429
+ if (tmp_y > 0) {
2430
+ this.move_widget_down($w, tmp_y);
2431
+ }
2432
+ }, this));
2433
+
2434
+ widget_grid_data.row = next_row;
2435
+ this.update_widget_position(widget_grid_data, $widget);
2436
+ $widget.attr('data-row', widget_grid_data.row);
2437
+ this.$changed = this.$changed.add($widget);
2438
+
2439
+ moved.push($widget);
2440
+ }
2441
+ };
2442
+
2443
+
2444
+ /**
2445
+ * Check if the widget can move to the specified row, else returns the
2446
+ * upper row possible.
2447
+ *
2448
+ * @method can_go_up_to_row
2449
+ * @param {Number} widget_grid_data The current grid coords object of the
2450
+ * widget.
2451
+ * @param {Number} col The target column.
2452
+ * @param {Number} row The target row.
2453
+ * @return {Boolean|Number} Returns the row number if the widget can move
2454
+ * to the target position, else returns false.
2455
+ */
2456
+ fn.can_go_up_to_row = function(widget_grid_data, col, row) {
2457
+ var ga = this.gridmap;
2458
+ var result = true;
2459
+ var urc = []; // upper_rows_in_columns
2460
+ var actual_row = widget_grid_data.row;
2461
+ var r;
2462
+
2463
+ /* generate an array with columns as index and array with
2464
+ * upper rows empty in the column */
2465
+ this.for_each_column_occupied(widget_grid_data, function(tcol) {
2466
+ var grid_col = ga[tcol];
2467
+ urc[tcol] = [];
2468
+
2469
+ r = actual_row;
2470
+ while (r--) {
2471
+ if (this.is_empty(tcol, r) &&
2472
+ !this.is_placeholder_in(tcol, r)
2473
+ ) {
2474
+ urc[tcol].push(r);
2475
+ }else{
2476
+ break;
2477
+ }
2478
+ }
2479
+
2480
+ if (!urc[tcol].length) {
2481
+ result = false;
2482
+ return true;
2483
+ }
2484
+
2485
+ });
2486
+
2487
+ if (!result) { return false; }
2488
+
2489
+ /* get common rows starting from upper position in all the columns
2490
+ * that widget occupies */
2491
+ r = row;
2492
+ for (r = 1; r < actual_row; r++) {
2493
+ var common = true;
2494
+
2495
+ for (var uc = 0, ucl = urc.length; uc < ucl; uc++) {
2496
+ if (urc[uc] && $.inArray(r, urc[uc]) === -1) {
2497
+ common = false;
2498
+ }
2499
+ }
2500
+
2501
+ if (common === true) {
2502
+ result = r;
2503
+ break;
2504
+ }
2505
+ }
2506
+
2507
+ return result;
2508
+ };
2509
+
2510
+
2511
+ fn.displacement_diff = function(widget_grid_data, parent_bgd, y_units) {
2512
+ var actual_row = widget_grid_data.row;
2513
+ var diffs = [];
2514
+ var parent_max_y = parent_bgd.row + parent_bgd.size_y;
2515
+
2516
+ this.for_each_column_occupied(widget_grid_data, function(col) {
2517
+ var temp_y_units = 0;
2518
+
2519
+ for (var r = parent_max_y; r < actual_row; r++) {
2520
+ if (this.is_empty(col, r)) {
2521
+ temp_y_units = temp_y_units + 1;
2522
+ }
2523
+ }
2524
+
2525
+ diffs.push(temp_y_units);
2526
+ });
2527
+
2528
+ var max_diff = Math.max.apply(Math, diffs);
2529
+ y_units = (y_units - max_diff);
2530
+
2531
+ return y_units > 0 ? y_units : 0;
2532
+ };
2533
+
2534
+
2535
+ /**
2536
+ * Get widgets below a widget.
2537
+ *
2538
+ * @method widgets_below
2539
+ * @param {HTMLElement} $el The jQuery wrapped HTMLElement.
2540
+ * @return {HTMLElements} A jQuery collection of HTMLElements.
2541
+ */
2542
+ fn.widgets_below = function($el) {
2543
+ var el_grid_data = $.isPlainObject($el) ? $el : $el.coords().grid;
2544
+ var self = this;
2545
+ var ga = this.gridmap;
2546
+ var next_row = el_grid_data.row + el_grid_data.size_y - 1;
2547
+ var $nexts = $([]);
2548
+
2549
+ this.for_each_column_occupied(el_grid_data, function(col) {
2550
+ self.for_each_widget_below(col, next_row, function(tcol, trow) {
2551
+ if (!self.is_player(this) && $.inArray(this, $nexts) === -1) {
2552
+ $nexts = $nexts.add(this);
2553
+ return true; // break
2554
+ }
2555
+ });
2556
+ });
2557
+
2558
+ return this.sort_by_row_asc($nexts);
2559
+ };
2560
+
2561
+
2562
+ /**
2563
+ * Update the array of mapped positions with the new player position.
2564
+ *
2565
+ * @method set_cells_player_occupies
2566
+ * @param {Number} col The new player col.
2567
+ * @param {Number} col The new player row.
2568
+ * @return {Class} Returns the instance of the Gridster Class.
2569
+ */
2570
+ fn.set_cells_player_occupies = function(col, row) {
2571
+ this.remove_from_gridmap(this.placeholder_grid_data);
2572
+ this.placeholder_grid_data.col = col;
2573
+ this.placeholder_grid_data.row = row;
2574
+ this.add_to_gridmap(this.placeholder_grid_data, this.$player);
2575
+ return this;
2576
+ };
2577
+
2578
+
2579
+ /**
2580
+ * Remove from the array of mapped positions the reference to the player.
2581
+ *
2582
+ * @method empty_cells_player_occupies
2583
+ * @return {Class} Returns the instance of the Gridster Class.
2584
+ */
2585
+ fn.empty_cells_player_occupies = function() {
2586
+ this.remove_from_gridmap(this.placeholder_grid_data);
2587
+ return this;
2588
+ };
2589
+
2590
+
2591
+ fn.can_go_up = function($el) {
2592
+ var el_grid_data = $el.coords().grid;
2593
+ var initial_row = el_grid_data.row;
2594
+ var prev_row = initial_row - 1;
2595
+ var ga = this.gridmap;
2596
+ var upper_rows_by_column = [];
2597
+
2598
+ var result = true;
2599
+ if (initial_row === 1) { return false; }
2600
+
2601
+ this.for_each_column_occupied(el_grid_data, function(col) {
2602
+ var $w = this.is_widget(col, prev_row);
2603
+
2604
+ if (this.is_occupied(col, prev_row) ||
2605
+ this.is_player(col, prev_row) ||
2606
+ this.is_placeholder_in(col, prev_row) ||
2607
+ this.is_player_in(col, prev_row)
2608
+ ) {
2609
+ result = false;
2610
+ return true; //break
2611
+ }
2612
+ });
2613
+
2614
+ return result;
2615
+ };
2616
+
2617
+
2618
+
2619
+ /**
2620
+ * Check if it's possible to move a widget to a specific col/row. It takes
2621
+ * into account the dimensions (`size_y` and `size_x` attrs. of the grid
2622
+ * coords object) the widget occupies.
2623
+ *
2624
+ * @method can_move_to
2625
+ * @param {Object} widget_grid_data The grid coords object that represents
2626
+ * the widget.
2627
+ * @param {Object} col The col to check.
2628
+ * @param {Object} row The row to check.
2629
+ * @param {Number} [max_row] The max row allowed.
2630
+ * @return {Boolean} Returns true if all cells are empty, else return false.
2631
+ */
2632
+ fn.can_move_to = function(widget_grid_data, col, row, max_row) {
2633
+ var ga = this.gridmap;
2634
+ var $w = widget_grid_data.el;
2635
+ var future_wd = {
2636
+ size_y: widget_grid_data.size_y,
2637
+ size_x: widget_grid_data.size_x,
2638
+ col: col,
2639
+ row: row
2640
+ };
2641
+ var result = true;
2642
+
2643
+ //Prevents widgets go out of the grid
2644
+ var right_col = col + widget_grid_data.size_x - 1;
2645
+ if (right_col > this.cols) {
2646
+ return false;
2647
+ }
2648
+
2649
+ if (max_row && max_row < row + widget_grid_data.size_y - 1) {
2650
+ return false;
2651
+ }
2652
+
2653
+ this.for_each_cell_occupied(future_wd, function(tcol, trow) {
2654
+ var $tw = this.is_widget(tcol, trow);
2655
+ if ($tw && (!widget_grid_data.el || $tw.is($w))) {
2656
+ result = false;
2657
+ }
2658
+ });
2659
+
2660
+ return result;
2661
+ };
2662
+
2663
+
2664
+ /**
2665
+ * Given the leftmost column returns all columns that are overlapping
2666
+ * with the player.
2667
+ *
2668
+ * @method get_targeted_columns
2669
+ * @param {Number} [from_col] The leftmost column.
2670
+ * @return {Array} Returns an array with column numbers.
2671
+ */
2672
+ fn.get_targeted_columns = function(from_col) {
2673
+ var max = (from_col || this.player_grid_data.col) +
2674
+ (this.player_grid_data.size_x - 1);
2675
+ var cols = [];
2676
+ for (var col = from_col; col <= max; col++) {
2677
+ cols.push(col);
2678
+ }
2679
+ return cols;
2680
+ };
2681
+
2682
+
2683
+ /**
2684
+ * Given the upper row returns all rows that are overlapping with the player.
2685
+ *
2686
+ * @method get_targeted_rows
2687
+ * @param {Number} [from_row] The upper row.
2688
+ * @return {Array} Returns an array with row numbers.
2689
+ */
2690
+ fn.get_targeted_rows = function(from_row) {
2691
+ var max = (from_row || this.player_grid_data.row) +
2692
+ (this.player_grid_data.size_y - 1);
2693
+ var rows = [];
2694
+ for (var row = from_row; row <= max; row++) {
2695
+ rows.push(row);
2696
+ }
2697
+ return rows;
2698
+ };
2699
+
2700
+ /**
2701
+ * Get all columns and rows that a widget occupies.
2702
+ *
2703
+ * @method get_cells_occupied
2704
+ * @param {Object} el_grid_data The grid coords object of the widget.
2705
+ * @return {Object} Returns an object like `{ cols: [], rows: []}`.
2706
+ */
2707
+ fn.get_cells_occupied = function(el_grid_data) {
2708
+ var cells = { cols: [], rows: []};
2709
+ var i;
2710
+ if (arguments[1] instanceof jQuery) {
2711
+ el_grid_data = arguments[1].coords().grid;
2712
+ }
2713
+
2714
+ for (i = 0; i < el_grid_data.size_x; i++) {
2715
+ var col = el_grid_data.col + i;
2716
+ cells.cols.push(col);
2717
+ }
2718
+
2719
+ for (i = 0; i < el_grid_data.size_y; i++) {
2720
+ var row = el_grid_data.row + i;
2721
+ cells.rows.push(row);
2722
+ }
2723
+
2724
+ return cells;
2725
+ };
2726
+
2727
+
2728
+ /**
2729
+ * Iterate over the cells occupied by a widget executing a function for
2730
+ * each one.
2731
+ *
2732
+ * @method for_each_cell_occupied
2733
+ * @param {Object} el_grid_data The grid coords object that represents the
2734
+ * widget.
2735
+ * @param {Function} callback The function to execute on each column
2736
+ * iteration. Column and row are passed as arguments.
2737
+ * @return {Class} Returns the instance of the Gridster Class.
2738
+ */
2739
+ fn.for_each_cell_occupied = function(grid_data, callback) {
2740
+ this.for_each_column_occupied(grid_data, function(col) {
2741
+ this.for_each_row_occupied(grid_data, function(row) {
2742
+ callback.call(this, col, row);
2743
+ });
2744
+ });
2745
+ return this;
2746
+ };
2747
+
2748
+
2749
+ /**
2750
+ * Iterate over the columns occupied by a widget executing a function for
2751
+ * each one.
2752
+ *
2753
+ * @method for_each_column_occupied
2754
+ * @param {Object} el_grid_data The grid coords object that represents
2755
+ * the widget.
2756
+ * @param {Function} callback The function to execute on each column
2757
+ * iteration. The column number is passed as first argument.
2758
+ * @return {Class} Returns the instance of the Gridster Class.
2759
+ */
2760
+ fn.for_each_column_occupied = function(el_grid_data, callback) {
2761
+ for (var i = 0; i < el_grid_data.size_x; i++) {
2762
+ var col = el_grid_data.col + i;
2763
+ callback.call(this, col, el_grid_data);
2764
+ }
2765
+ };
2766
+
2767
+
2768
+ /**
2769
+ * Iterate over the rows occupied by a widget executing a function for
2770
+ * each one.
2771
+ *
2772
+ * @method for_each_row_occupied
2773
+ * @param {Object} el_grid_data The grid coords object that represents
2774
+ * the widget.
2775
+ * @param {Function} callback The function to execute on each column
2776
+ * iteration. The row number is passed as first argument.
2777
+ * @return {Class} Returns the instance of the Gridster Class.
2778
+ */
2779
+ fn.for_each_row_occupied = function(el_grid_data, callback) {
2780
+ for (var i = 0; i < el_grid_data.size_y; i++) {
2781
+ var row = el_grid_data.row + i;
2782
+ callback.call(this, row, el_grid_data);
2783
+ }
2784
+ };
2785
+
2786
+
2787
+
2788
+ fn._traversing_widgets = function(type, direction, col, row, callback) {
2789
+ var ga = this.gridmap;
2790
+ if (!ga[col]) { return; }
2791
+
2792
+ var cr, max;
2793
+ var action = type + '/' + direction;
2794
+ if (arguments[2] instanceof jQuery) {
2795
+ var el_grid_data = arguments[2].coords().grid;
2796
+ col = el_grid_data.col;
2797
+ row = el_grid_data.row;
2798
+ callback = arguments[3];
2799
+ }
2800
+ var matched = [];
2801
+ var trow = row;
2802
+
2803
+
2804
+ var methods = {
2805
+ 'for_each/above': function() {
2806
+ while (trow--) {
2807
+ if (trow > 0 && this.is_widget(col, trow) &&
2808
+ $.inArray(ga[col][trow], matched) === -1
2809
+ ) {
2810
+ cr = callback.call(ga[col][trow], col, trow);
2811
+ matched.push(ga[col][trow]);
2812
+ if (cr) { break; }
2813
+ }
2814
+ }
2815
+ },
2816
+ 'for_each/below': function() {
2817
+ for (trow = row + 1, max = ga[col].length; trow < max; trow++) {
2818
+ if (this.is_widget(col, trow) &&
2819
+ $.inArray(ga[col][trow], matched) === -1
2820
+ ) {
2821
+ cr = callback.call(ga[col][trow], col, trow);
2822
+ matched.push(ga[col][trow]);
2823
+ if (cr) { break; }
2824
+ }
2825
+ }
2826
+ }
2827
+ };
2828
+
2829
+ if (methods[action]) {
2830
+ methods[action].call(this);
2831
+ }
2832
+ };
2833
+
2834
+
2835
+ /**
2836
+ * Iterate over each widget above the column and row specified.
2837
+ *
2838
+ * @method for_each_widget_above
2839
+ * @param {Number} col The column to start iterating.
2840
+ * @param {Number} row The row to start iterating.
2841
+ * @param {Function} callback The function to execute on each widget
2842
+ * iteration. The value of `this` inside the function is the jQuery
2843
+ * wrapped HTMLElement.
2844
+ * @return {Class} Returns the instance of the Gridster Class.
2845
+ */
2846
+ fn.for_each_widget_above = function(col, row, callback) {
2847
+ this._traversing_widgets('for_each', 'above', col, row, callback);
2848
+ return this;
2849
+ };
2850
+
2851
+
2852
+ /**
2853
+ * Iterate over each widget below the column and row specified.
2854
+ *
2855
+ * @method for_each_widget_below
2856
+ * @param {Number} col The column to start iterating.
2857
+ * @param {Number} row The row to start iterating.
2858
+ * @param {Function} callback The function to execute on each widget
2859
+ * iteration. The value of `this` inside the function is the jQuery wrapped
2860
+ * HTMLElement.
2861
+ * @return {Class} Returns the instance of the Gridster Class.
2862
+ */
2863
+ fn.for_each_widget_below = function(col, row, callback) {
2864
+ this._traversing_widgets('for_each', 'below', col, row, callback);
2865
+ return this;
2866
+ };
2867
+
2868
+
2869
+ /**
2870
+ * Returns the highest occupied cell in the grid.
2871
+ *
2872
+ * @method get_highest_occupied_cell
2873
+ * @return {Object} Returns an object with `col` and `row` numbers.
2874
+ */
2875
+ fn.get_highest_occupied_cell = function() {
2876
+ var r;
2877
+ var gm = this.gridmap;
2878
+ var rows = [];
2879
+ var row_in_col = [];
2880
+ for (var c = gm.length - 1; c >= 1; c--) {
2881
+ for (r = gm[c].length - 1; r >= 1; r--) {
2882
+ if (this.is_widget(c, r)) {
2883
+ rows.push(r);
2884
+ row_in_col[r] = c;
2885
+ break;
2886
+ }
2887
+ }
2888
+ }
2889
+
2890
+ var highest_row = Math.max.apply(Math, rows);
2891
+
2892
+ this.highest_occupied_cell = {
2893
+ col: row_in_col[highest_row],
2894
+ row: highest_row
2895
+ };
2896
+
2897
+ return this.highest_occupied_cell;
2898
+ };
2899
+
2900
+
2901
+ fn.get_widgets_from = function(col, row) {
2902
+ var ga = this.gridmap;
2903
+ var $widgets = $();
2904
+
2905
+ if (col) {
2906
+ $widgets = $widgets.add(
2907
+ this.$widgets.filter(function() {
2908
+ var tcol = $(this).attr('data-col');
2909
+ return (tcol === col || tcol > col);
2910
+ })
2911
+ );
2912
+ }
2913
+
2914
+ if (row) {
2915
+ $widgets = $widgets.add(
2916
+ this.$widgets.filter(function() {
2917
+ var trow = $(this).attr('data-row');
2918
+ return (trow === row || trow > row);
2919
+ })
2920
+ );
2921
+ }
2922
+
2923
+ return $widgets;
2924
+ };
2925
+
2926
+
2927
+ /**
2928
+ * Set the current height of the parent grid.
2929
+ *
2930
+ * @method set_dom_grid_height
2931
+ * @return {Object} Returns the instance of the Gridster class.
2932
+ */
2933
+ fn.set_dom_grid_height = function() {
2934
+ var r = this.get_highest_occupied_cell().row;
2935
+ this.$el.css('height', r * this.min_widget_height);
2936
+ return this;
2937
+ };
2938
+
2939
+
2940
+ /**
2941
+ * It generates the neccessary styles to position the widgets.
2942
+ *
2943
+ * @method generate_stylesheet
2944
+ * @param {Number} rows Number of columns.
2945
+ * @param {Number} cols Number of rows.
2946
+ * @return {Object} Returns the instance of the Gridster class.
2947
+ */
2948
+ fn.generate_stylesheet = function(opts) {
2949
+ var styles = '';
2950
+ var max_size_x = this.options.max_size_x;
2951
+ var max_rows = 0;
2952
+ var max_cols = 0;
2953
+ var i;
2954
+ var rules;
2955
+
2956
+ opts || (opts = {});
2957
+ opts.cols || (opts.cols = this.cols);
2958
+ opts.rows || (opts.rows = this.rows);
2959
+ opts.namespace || (opts.namespace = this.options.namespace);
2960
+ opts.widget_base_dimensions ||
2961
+ (opts.widget_base_dimensions = this.options.widget_base_dimensions);
2962
+ opts.widget_margins ||
2963
+ (opts.widget_margins = this.options.widget_margins);
2964
+ opts.min_widget_width = (opts.widget_margins[0] * 2) +
2965
+ opts.widget_base_dimensions[0];
2966
+ opts.min_widget_height = (opts.widget_margins[1] * 2) +
2967
+ opts.widget_base_dimensions[1];
2968
+
2969
+ // don't duplicate stylesheets for the same configuration
2970
+ var serialized_opts = $.param(opts);
2971
+ if ($.inArray(serialized_opts, Gridster.generated_stylesheets) >= 0) {
2972
+ return false;
2973
+ }
2974
+
2975
+ Gridster.generated_stylesheets.push(serialized_opts);
2976
+
2977
+ /* generate CSS styles for cols */
2978
+ for (i = opts.cols; i >= 0; i--) {
2979
+ styles += (opts.namespace + ' [data-col="'+ (i + 1) + '"] { left:' +
2980
+ ((i * opts.widget_base_dimensions[0]) +
2981
+ (i * opts.widget_margins[0]) +
2982
+ ((i + 1) * opts.widget_margins[0])) + 'px;} ');
2983
+ }
2984
+
2985
+ /* generate CSS styles for rows */
2986
+ for (i = opts.rows; i >= 0; i--) {
2987
+ styles += (opts.namespace + ' [data-row="' + (i + 1) + '"] { top:' +
2988
+ ((i * opts.widget_base_dimensions[1]) +
2989
+ (i * opts.widget_margins[1]) +
2990
+ ((i + 1) * opts.widget_margins[1]) ) + 'px;} ');
2991
+ }
2992
+
2993
+ for (var y = 1; y <= opts.rows; y++) {
2994
+ styles += (opts.namespace + ' [data-sizey="' + y + '"] { height:' +
2995
+ (y * opts.widget_base_dimensions[1] +
2996
+ (y - 1) * (opts.widget_margins[1] * 2)) + 'px;}');
2997
+ }
2998
+
2999
+ for (var x = 1; x <= max_size_x; x++) {
3000
+ styles += (opts.namespace + ' [data-sizex="' + x + '"] { width:' +
3001
+ (x * opts.widget_base_dimensions[0] +
3002
+ (x - 1) * (opts.widget_margins[0] * 2)) + 'px;}');
3003
+ }
3004
+
3005
+ return this.add_style_tag(styles);
3006
+ };
3007
+
3008
+
3009
+ /**
3010
+ * Injects the given CSS as string to the head of the document.
3011
+ *
3012
+ * @method add_style_tag
3013
+ * @param {String} css The styles to apply.
3014
+ * @return {Object} Returns the instance of the Gridster class.
3015
+ */
3016
+ fn.add_style_tag = function(css) {
3017
+ var d = document;
3018
+ var tag = d.createElement('style');
3019
+
3020
+ d.getElementsByTagName('head')[0].appendChild(tag);
3021
+ tag.setAttribute('type', 'text/css');
3022
+
3023
+ if (tag.styleSheet) {
3024
+ tag.styleSheet.cssText = css;
3025
+ }else{
3026
+ tag.appendChild(document.createTextNode(css));
3027
+ }
3028
+ return this;
3029
+ };
3030
+
3031
+
3032
+ /**
3033
+ * Generates a faux grid to collide with it when a widget is dragged and
3034
+ * detect row or column that we want to go.
3035
+ *
3036
+ * @method generate_faux_grid
3037
+ * @param {Number} rows Number of columns.
3038
+ * @param {Number} cols Number of rows.
3039
+ * @return {Object} Returns the instance of the Gridster class.
3040
+ */
3041
+ fn.generate_faux_grid = function(rows, cols) {
3042
+ this.faux_grid = [];
3043
+ this.gridmap = [];
3044
+ var col;
3045
+ var row;
3046
+ for (col = cols; col > 0; col--) {
3047
+ this.gridmap[col] = [];
3048
+ for (row = rows; row > 0; row--) {
3049
+ this.add_faux_cell(row, col);
3050
+ }
3051
+ }
3052
+ return this;
3053
+ };
3054
+
3055
+
3056
+ /**
3057
+ * Add cell to the faux grid.
3058
+ *
3059
+ * @method add_faux_cell
3060
+ * @param {Number} row The row for the new faux cell.
3061
+ * @param {Number} col The col for the new faux cell.
3062
+ * @return {Object} Returns the instance of the Gridster class.
3063
+ */
3064
+ fn.add_faux_cell = function(row, col) {
3065
+ var coords = $({
3066
+ left: this.baseX + ((col - 1) * this.min_widget_width),
3067
+ top: this.baseY + (row -1) * this.min_widget_height,
3068
+ width: this.min_widget_width,
3069
+ height: this.min_widget_height,
3070
+ col: col,
3071
+ row: row,
3072
+ original_col: col,
3073
+ original_row: row
3074
+ }).coords();
3075
+
3076
+ if (!$.isArray(this.gridmap[col])) {
3077
+ this.gridmap[col] = [];
3078
+ }
3079
+
3080
+ this.gridmap[col][row] = false;
3081
+ this.faux_grid.push(coords);
3082
+
3083
+ return this;
3084
+ };
3085
+
3086
+
3087
+ /**
3088
+ * Add rows to the faux grid.
3089
+ *
3090
+ * @method add_faux_rows
3091
+ * @param {Number} rows The number of rows you want to add to the faux grid.
3092
+ * @return {Object} Returns the instance of the Gridster class.
3093
+ */
3094
+ fn.add_faux_rows = function(rows) {
3095
+ var actual_rows = this.rows;
3096
+ var max_rows = actual_rows + (rows || 1);
3097
+
3098
+ for (var r = max_rows; r > actual_rows; r--) {
3099
+ for (var c = this.cols; c >= 1; c--) {
3100
+ this.add_faux_cell(r, c);
3101
+ }
3102
+ }
3103
+
3104
+ this.rows = max_rows;
3105
+
3106
+ if (this.options.autogenerate_stylesheet) {
3107
+ this.generate_stylesheet();
3108
+ }
3109
+
3110
+ return this;
3111
+ };
3112
+
3113
+ /**
3114
+ * Add cols to the faux grid.
3115
+ *
3116
+ * @method add_faux_cols
3117
+ * @param {Number} cols The number of cols you want to add to the faux grid.
3118
+ * @return {Object} Returns the instance of the Gridster class.
3119
+ */
3120
+ fn.add_faux_cols = function(cols) {
3121
+ var actual_cols = this.cols;
3122
+ var max_cols = actual_cols + (cols || 1);
3123
+
3124
+ for (var c = actual_cols; c < max_cols; c++) {
3125
+ for (var r = this.rows; r >= 1; r--) {
3126
+ this.add_faux_cell(r, c);
3127
+ }
3128
+ }
3129
+
3130
+ this.cols = max_cols;
3131
+
3132
+ if (this.options.autogenerate_stylesheet) {
3133
+ this.generate_stylesheet();
3134
+ }
3135
+
3136
+ return this;
3137
+ };
3138
+
3139
+
3140
+ /**
3141
+ * Recalculates the offsets for the faux grid. You need to use it when
3142
+ * the browser is resized.
3143
+ *
3144
+ * @method recalculate_faux_grid
3145
+ * @return {Object} Returns the instance of the Gridster class.
3146
+ */
3147
+ fn.recalculate_faux_grid = function() {
3148
+ var aw = this.$wrapper.width();
3149
+ this.baseX = ($(window).width() - aw) / 2;
3150
+ this.baseY = this.$wrapper.offset().top;
3151
+
3152
+ $.each(this.faux_grid, $.proxy(function(i, coords) {
3153
+ this.faux_grid[i] = coords.update({
3154
+ left: this.baseX + (coords.data.col -1) * this.min_widget_width,
3155
+ top: this.baseY + (coords.data.row -1) * this.min_widget_height
3156
+ });
3157
+
3158
+ }, this));
3159
+
3160
+ return this;
3161
+ };
3162
+
3163
+
3164
+ /**
3165
+ * Get all widgets in the DOM and register them.
3166
+ *
3167
+ * @method get_widgets_from_DOM
3168
+ * @return {Object} Returns the instance of the Gridster class.
3169
+ */
3170
+ fn.get_widgets_from_DOM = function() {
3171
+ this.$widgets.each($.proxy(function(i, widget) {
3172
+ this.register_widget($(widget));
3173
+ }, this));
3174
+ return this;
3175
+ };
3176
+
3177
+
3178
+ /**
3179
+ * Calculate columns and rows to be set based on the configuration
3180
+ * parameters, grid dimensions, etc ...
3181
+ *
3182
+ * @method generate_grid_and_stylesheet
3183
+ * @return {Object} Returns the instance of the Gridster class.
3184
+ */
3185
+ fn.generate_grid_and_stylesheet = function() {
3186
+ var aw = this.$wrapper.width();
3187
+ var ah = this.$wrapper.height();
3188
+
3189
+ var cols = Math.floor(aw / this.min_widget_width) +
3190
+ this.options.extra_cols;
3191
+
3192
+ var actual_cols = this.$widgets.map(function() {
3193
+ return $(this).attr('data-col');
3194
+ });
3195
+ actual_cols = Array.prototype.slice.call(actual_cols, 0);
3196
+ //needed to pass tests with phantomjs
3197
+ actual_cols.length || (actual_cols = [0]);
3198
+
3199
+ var min_cols = Math.max.apply(Math, actual_cols);
3200
+
3201
+ // get all rows that could be occupied by the current widgets
3202
+ var max_rows = this.options.extra_rows;
3203
+ this.$widgets.each(function(i, w) {
3204
+ max_rows += (+$(w).attr('data-sizey'));
3205
+ });
3206
+
3207
+ this.cols = Math.max(min_cols, cols, this.options.min_cols);
3208
+ this.rows = Math.max(max_rows, this.options.min_rows);
3209
+
3210
+ this.baseX = ($(window).width() - aw) / 2;
3211
+ this.baseY = this.$wrapper.offset().top;
3212
+
3213
+ if (this.options.autogenerate_stylesheet) {
3214
+ this.generate_stylesheet();
3215
+ }
3216
+
3217
+ return this.generate_faux_grid(this.rows, this.cols);
3218
+ };
3219
+
3220
+
3221
+ //jQuery adapter
3222
+ $.fn.gridster = function(options) {
3223
+ return this.each(function() {
3224
+ if (!$(this).data('gridster')) {
3225
+ $(this).data('gridster', new Gridster( this, options ));
3226
+ }
3227
+ });
3228
+ };
3229
+
3230
+ $.Gridster = fn;
3231
+
3232
+ }(jQuery, window, document));